com.wallstop-studios.unity-helpers
Version:
Treasure chest of Unity developer tools
267 lines (231 loc) • 9.85 kB
HTML
<!-- Custom head content for Unity Helpers documentation -->
<!--
Theme CSS is imported via style.scss (@import url("theme.css")).
Adding a link tag here would load theme.css twice.
-->
<!-- Theme initialization - runs before page renders to prevent flash -->
<script>
(function () {
// Check for saved theme preference or default to dark
const savedTheme = localStorage.getItem("unity-helpers-theme");
const theme = savedTheme || "dark";
document.documentElement.setAttribute("data-theme", theme);
})();
</script>
<!-- Mermaid diagrams with theme support -->
{% include mermaid.html %}
<style>
/* Mermaid diagram container - minimal styling, relies on built-in themes */
.mermaid {
text-align: center;
margin: 1em 0;
}
/* Ensure mermaid SVGs are responsive */
.mermaid svg {
max-width: 100%;
height: auto;
}
</style>
<!-- Sortable tables (Issue #126) -->
<!--
Note: Tables with rowspan or colspan attributes may not sort correctly
because the sorting logic assumes a simple grid structure where each
row has the same number of cells aligned to column headers.
-->
<script>
document.addEventListener("DOMContentLoaded", () => {
// Initialize sortable tables
const sortableTables = document.querySelectorAll("table[data-sortable]");
sortableTables.forEach((table) => {
const headers = table.querySelectorAll("th");
headers.forEach((header, columnIndex) => {
header.setAttribute("role", "columnheader");
header.setAttribute("tabindex", "0");
header.setAttribute("aria-sort", "none");
// Click handler
header.addEventListener("click", () => {
sortTableByColumn(table, columnIndex, header);
});
// Keyboard handler for accessibility
header.addEventListener("keydown", (event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
sortTableByColumn(table, columnIndex, header);
}
});
});
});
function sortTableByColumn(table, columnIndex, header) {
let tbody = table.querySelector("tbody");
if (!tbody) {
// If no tbody, create one from tr elements
const rows = Array.from(table.querySelectorAll("tr"));
if (rows.length <= 1) return;
tbody = document.createElement("tbody");
const headerRow = rows.shift();
const thead = document.createElement("thead");
thead.appendChild(headerRow);
rows.forEach((row) => {
tbody.appendChild(row);
});
table.innerHTML = "";
table.appendChild(thead);
table.appendChild(tbody);
}
const rows = Array.from(tbody.querySelectorAll("tr"));
if (rows.length === 0) return;
// Determine current sort direction
const currentDir = table.dataset.sortDir || "none";
const currentCol = table.dataset.sortCol;
let newDir;
if (currentCol === String(columnIndex)) {
// Toggle direction on same column
newDir = currentDir === "asc" ? "desc" : "asc";
} else {
// New column, default to ascending
newDir = "asc";
}
// Update sort state
table.dataset.sortDir = newDir;
table.dataset.sortCol = columnIndex;
// Reset all header aria-sort attributes
const allHeaders = table.querySelectorAll("th");
allHeaders.forEach((h) => {
h.setAttribute("aria-sort", "none");
// Remove existing sort indicators
const indicator = h.querySelector(".sort-indicator");
if (indicator) {
indicator.textContent = "";
}
});
// Update current header
header.setAttribute("aria-sort", newDir === "asc" ? "ascending" : "descending");
// Add or update sort indicator
let indicator = header.querySelector(".sort-indicator");
if (!indicator) {
indicator = document.createElement("span");
indicator.className = "sort-indicator";
indicator.setAttribute("aria-hidden", "true");
header.appendChild(indicator);
}
indicator.textContent = newDir === "asc" ? " \u25B2" : " \u25BC";
// Sort the rows
rows.sort((rowA, rowB) => {
const cellA = rowA.cells[columnIndex];
const cellB = rowB.cells[columnIndex];
if (!cellA || !cellB) return 0;
const valueA = getSortableCellValue(cellA);
const valueB = getSortableCellValue(cellB);
// Try to parse as numbers (handle commas, percentages, units)
const numA = parseNumericValue(valueA);
const numB = parseNumericValue(valueB);
let comparison;
if (numA !== null && numB !== null) {
// Numeric comparison
comparison = numA - numB;
} else {
// String comparison (case-insensitive)
comparison = valueA.toLowerCase().localeCompare(valueB.toLowerCase());
}
return newDir === "asc" ? comparison : -comparison;
});
// Re-append sorted rows
rows.forEach((row) => {
tbody.appendChild(row);
});
}
function getSortableCellValue(cell) {
return (cell.getAttribute("data-sort-value") || cell.textContent || "").trim();
}
function parseNumericValue(value) {
if (
value === "" ||
value === "n/a" ||
value === "N/A" ||
value === "-" ||
value === "pending" ||
value === "Pending"
) {
return null;
}
// Remove common formatting characters, preserving negative sign
// Handle negative numbers with commas like "-1,234"
const cleaned = value
.replace(/,/g, "") // Remove thousands separators
.replace(/\s+/g, " ") // Normalize whitespace
.trim();
// Handle percentages/multipliers (e.g., "0.95x", "121.48x", "-0.5x")
const multiplierMatch = cleaned.match(/^(-?[\d.]+)x$/i);
if (multiplierMatch) {
return parseFloat(multiplierMatch[1]);
}
// Handle time values (e.g., "0.030 ms", "2.13 s", "1.12 s")
const timeMatch = cleaned.match(/^(-?[\d.]+)\s*(ms|s)$/i);
if (timeMatch) {
const timeValue = parseFloat(timeMatch[1]);
const unit = timeMatch[2].toLowerCase();
// Convert to milliseconds for consistent comparison
return unit === "s" ? timeValue * 1000 : timeValue;
}
// Handle ops/sec values (e.g., "1,309,300,000", "63.5M", "868.3K", "-5K")
const suffixMatch = cleaned.match(/^(-?[\d.]+)\s*([KMB])?$/i);
if (suffixMatch) {
let numValue = parseFloat(suffixMatch[1]);
const suffix = (suffixMatch[2] || "").toUpperCase();
if (suffix === "K") numValue *= 1000;
else if (suffix === "M") numValue *= 1000000;
else if (suffix === "B") numValue *= 1000000000;
if (!isNaN(numValue)) return numValue;
}
// Handle parenthetical time values (e.g., "3 (0.254s)")
const parenMatch = cleaned.match(/^(-?\d+)\s*\([\d.]+s\)$/);
if (parenMatch) {
return parseInt(parenMatch[1], 10);
}
// Try direct numeric parse
const directNum = parseFloat(cleaned);
if (!isNaN(directNum)) {
return directNum;
}
return null;
}
});
</script>
<!-- Theme toggle button (injected into body via JS) -->
<script>
document.addEventListener("DOMContentLoaded", () => {
// Create theme toggle button
const toggle = document.createElement("button");
toggle.className = "theme-toggle";
toggle.setAttribute("aria-label", "Toggle theme");
toggle.setAttribute("title", "Toggle light/dark theme");
toggle.innerHTML = `
<svg class="sun-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zM2 13h2c.55 0 1-.45 1-1s-.45-1-1-1H2c-.55 0-1 .45-1 1s.45 1 1 1zm18 0h2c.55 0 1-.45 1-1s-.45-1-1-1h-2c-.55 0-1 .45-1 1s.45 1 1 1zM11 2v2c0 .55.45 1 1 1s1-.45 1-1V2c0-.55-.45-1-1-1s-1 .45-1 1zm0 18v2c0 .55.45 1 1 1s1-.45 1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1zM5.99 4.58a.996.996 0 0 0-1.41 0 .996.996 0 0 0 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0s.39-1.03 0-1.41L5.99 4.58zm12.37 12.37a.996.996 0 0 0-1.41 0 .996.996 0 0 0 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0a.996.996 0 0 0 0-1.41l-1.06-1.06zm1.06-10.96a.996.996 0 0 0 0-1.41.996.996 0 0 0-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06zM7.05 18.36a.996.996 0 0 0 0-1.41.996.996 0 0 0-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06z"/>
</svg>
<svg class="moon-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M12 3a9 9 0 1 0 9 9c0-.46-.04-.92-.1-1.36a5.389 5.389 0 0 1-4.4 2.26 5.403 5.403 0 0 1-3.14-9.8c-.44-.06-.9-.1-1.36-.1z"/>
</svg>
<span>Theme</span>
`;
// Add click handler
toggle.addEventListener("click", () => {
const currentTheme = document.documentElement.getAttribute("data-theme");
const newTheme = currentTheme === "dark" ? "light" : "dark";
document.documentElement.setAttribute("data-theme", newTheme);
localStorage.setItem("unity-helpers-theme", newTheme);
// Update mermaid theme if diagrams exist
// Check for window.updateMermaidTheme (mermaid is module-scoped, not global)
if (typeof window.updateMermaidTheme === "function" && document.querySelector(".mermaid")) {
window.updateMermaidTheme(newTheme);
}
});
// Insert toggle after header if one exists, otherwise at start of body
const header = document.querySelector("header");
if (header && header.nextSibling) {
header.parentNode.insertBefore(toggle, header.nextSibling);
} else {
document.body.insertBefore(toggle, document.body.firstChild);
}
});
</script>