diff --git a/templates/js/annotationOverlay.js b/templates/js/annotationOverlay.js index 7fd8f219..13cffad7 100644 --- a/templates/js/annotationOverlay.js +++ b/templates/js/annotationOverlay.js @@ -3,6 +3,12 @@ const PPT_EMU = { W: 9_144_000, H: 6_858_000 }; // PowerPoint slide size in EMUs +/** + * Ensures the annotation stage (overlay container) exists inside the given container, + * creating it if necessary. The stage holds both the SVG line layer and the label div. + * @param {HTMLElement} container - The parent element to attach the stage to. + * @returns {HTMLElement} The existing or newly created annotation stage element. + */ function ensureStage(container) { let stage = container.querySelector(".annotation-stage"); if (!stage) { @@ -17,6 +23,11 @@ function ensureStage(container) { return stage; } +/** + * Removes the annotation stage and all its contents from the given container. + * @param {HTMLElement} container - The container whose annotation stage should be removed. + * @returns {void} + */ export function clearAnnotations(container) { if (!container) return; const stage = container.querySelector(".annotation-stage"); @@ -67,8 +78,14 @@ function normalizedPointToPx(pt, box, norm) { // <--- RENAMED to reflect change } /** - * Draw labels + lines from a JSON object: - * { annotations: [...], normalized_geometry: { normX, normY, normW, normH } } + * Draws text annotation labels and pointer lines onto the given container. + * Reads normalized geometry and slide dimensions from the JSON to map PowerPoint + * EMU coordinates onto the displayed pixel size of the container. + * @param {HTMLElement} container - The element to draw annotations into. + * @param {Object} annotationsJson - Annotation data from the API, containing: + * `annotations` {Array}, `normalized_geometry` {Object}, `slide_width` {number}, + * and `slide_height` {number}. + * @returns {void} */ export function drawAnnotations(container, annotationsJson) { if (!container || !annotationsJson) return; @@ -143,7 +160,13 @@ export function drawAnnotations(container, annotationsJson) { stage.__lastJson = annotationsJson; } -/** Load JSON from a URL and draw it. Returns a promise. */ +/** + * Fetches annotation JSON from a URL, draws it onto the container, + * and attaches a ResizeObserver so annotations redraw when the container resizes. + * @param {HTMLElement} container - The element to draw annotations into. + * @param {string} jsonUrl - URL of the annotation JSON file. + * @returns {Promise} + */ export async function loadAndDrawAnnotations(container, jsonUrl) { if (!container || !jsonUrl) return; const res = await fetch(jsonUrl); @@ -155,7 +178,12 @@ export async function loadAndDrawAnnotations(container, jsonUrl) { attachAutoscale(container); // keep aligned on resize } -/** Re-draw on container resize using the last JSON used. */ +/** + * Attaches a ResizeObserver to the container that redraws annotations whenever + * the container's dimensions change. Does nothing if an observer is already attached. + * @param {HTMLElement} container - The container to watch for size changes. + * @returns {void} + */ function attachAutoscale(container) { const stage = ensureStage(container); if (stage.__resizeObs) return; // already attached diff --git a/templates/js/api.js b/templates/js/api.js index ee79f035..dd994784 100644 --- a/templates/js/api.js +++ b/templates/js/api.js @@ -10,6 +10,12 @@ const API_CONFIG = { } }; +/** + * Fetches the combined boneset/bone/subbone data from the backend API. + * This is the primary data source used to populate all dropdowns on page load. + * @returns {Promise} Combined data object containing `bonesets`, `bones`, and `subbones` arrays. + * @throws {Error} If the network request fails or the server returns a non-OK response. + */ export async function fetchCombinedData() { const API_URL = `${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.COMBINED_DATA}`; @@ -25,6 +31,11 @@ export async function fetchCombinedData() { } } +/** + * Fetches mock bone data from a local JSON file. Used for development and testing + * without a running backend server. + * @returns {Promise} The mock bone data object, or null if the fetch fails. + */ export async function fetchMockBoneData() { try { const response = await fetch(API_CONFIG.ENDPOINTS.MOCK_BONE_DATA); diff --git a/templates/js/coloredRegionsOverlay.js b/templates/js/coloredRegionsOverlay.js index 5919818d..041f85ea 100644 --- a/templates/js/coloredRegionsOverlay.js +++ b/templates/js/coloredRegionsOverlay.js @@ -517,6 +517,7 @@ export async function displayColoredRegions(imageElement, boneId, imageIndex = 0 /** * Clear all colored region overlays from a container * @param {HTMLElement} container - The container element + * @returns {void} */ export function clearColoredRegions(container) { if (!container) return; @@ -527,6 +528,7 @@ export function clearColoredRegions(container) { /** * Clear all colored region overlays in the entire image container + * @returns {void} */ export function clearAllColoredRegions() { const container = document.getElementById("bone-image-container"); diff --git a/templates/js/description.js b/templates/js/description.js index 57b1c617..08e1640a 100644 --- a/templates/js/description.js +++ b/templates/js/description.js @@ -1,6 +1,14 @@ // js/description.js const GITHUB_RAW_URL = "https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/data/descriptions/"; +/** + * Fetches the description JSON for the given bone/subbone ID from GitHub and + * renders it as a list of bullet points inside the `#description-Container` element. + * Shows an error message in the container if the fetch fails. + * @param {string} id - The bone or subbone ID (e.g. `"ilium"`, `"iliac_crest"`), + * used to construct the filename `{id}_description.json`. + * @returns {Promise} + */ export async function loadDescription(id) { const container = document.getElementById("description-Container"); container.innerHTML = ""; diff --git a/templates/js/dropdowns.js b/templates/js/dropdowns.js index f7581e14..61e2af8e 100644 --- a/templates/js/dropdowns.js +++ b/templates/js/dropdowns.js @@ -12,10 +12,22 @@ document.addEventListener("DOMContentLoaded", () => { // ---- Map/lookup you can extend later ----------------- let _boneById = {}; // filled in setupDropdownListeners +/** + * Returns the `#bone-image-container` element, which serves as the host for + * displayed bone images and their annotation overlays. + * @returns {HTMLElement|null} The image container element, or null if not found. + */ function getImageStage() { return /** @type {HTMLElement|null} */ (document.getElementById("bone-image-container")); } +/** + * Clears any existing annotation overlay from the image stage. + * Annotation loading is now handled directly in the dropdown change listeners + * via the `opts.annotationsUrl` option passed to `loadBoneImages`. + * @param {string} boneId - The bone ID (unused; retained for future use). + * @returns {Promise} + */ // Function maybeLoadAnnotations: Logic removed. Annotation URL construction is now in the listeners. async function maybeLoadAnnotations(boneId) { const stage = getImageStage(); @@ -32,7 +44,12 @@ async function maybeLoadAnnotations(boneId) { // Backend API base (runs on 8000) const API_BASE = "http://127.0.0.1:8000"; -/** Helper: fetch images for a bone/sub-bone and render them */ +/** Helper: fetch images for a bone/sub-bone and render them + * @param {string} boneId - The bone or subbone ID to load images for. + * @param {Object} [options={}] - Options forwarded to `displayBoneImages`, e.g. + * `{ annotationsUrl: string, boneId: string, isBonesetSelection: boolean }`. + * @returns {Promise} + */ async function loadBoneImages(boneId, options = {}) { const stage = getImageStage(); if (!boneId) { @@ -57,6 +74,12 @@ async function loadBoneImages(boneId, options = {}) { } } +/** + * Populates the boneset `` elements. + * Each listener loads images, descriptions, and annotations appropriate to the selection. + * @param {Object} combinedData - The full application data set containing: + * `bonesets` {Array}, `bones` {Array}, and `subbones` {Array}. + * @returns {void} + */ export function setupDropdownListeners(combinedData) { const bonesetSelect = document.getElementById("boneset-select"); const boneSelect = document.getElementById("bone-select"); diff --git a/templates/js/imageCaptions.js b/templates/js/imageCaptions.js index 28e61325..604c6fb2 100644 --- a/templates/js/imageCaptions.js +++ b/templates/js/imageCaptions.js @@ -1,6 +1,14 @@ // js/imageCaptions.js // Image captions for bone images, extracted from PowerPoint slides +/** + * A lookup map of image captions keyed by bone/subbone ID. + * Each entry provides caption strings for up to two images (`image1`, `image2`). + * Captions describe the anatomical view shown in the corresponding image + * (e.g. lateral aspect, medial aspect). + * + * @type {Object.} + */ export const imageCaptions = { // Bony Pelvis (main boneset) "bony_pelvis": { diff --git a/templates/js/imageDisplay.js b/templates/js/imageDisplay.js index 040ccf5e..fc373295 100644 --- a/templates/js/imageDisplay.js +++ b/templates/js/imageDisplay.js @@ -9,13 +9,20 @@ import { imageCaptions } from "./imageCaptions.js"; let currentBoneId = null; let currentIsBonesetSelection = false; // Track if this is a boneset selection +/** + * Returns the `#bone-image-container` DOM element. + * @returns {HTMLElement|null} The image container element, or null if not found. + */ function getImageContainer() { return /** @type {HTMLElement|null} */ ( document.getElementById("bone-image-container") ); } -/** Helper function to get captions for a boneId */ +/** Helper function to get captions for a boneId + * @param {string|null} boneId - The bone or subbone ID. + * @returns {{image1: string|null, image2: string|null}} Caption strings for the two images, or nulls if not found. + */ function getCaptionsForBone(boneId) { if (!boneId || !imageCaptions[boneId]) { return { image1: null, image2: null }; @@ -23,7 +30,9 @@ function getCaptionsForBone(boneId) { return imageCaptions[boneId]; } -/** Helper to clear existing caption container */ +/** Removes the `#caption-container` element from the DOM if it exists. + * @returns {void} + */ function clearCaptionContainer() { const existingCaptions = document.getElementById("caption-container"); if (existingCaptions) { @@ -31,7 +40,11 @@ function clearCaptionContainer() { } } -/** ---- Empty-state / clearing ------------------------------------------- */ +/** + * Renders the empty-state placeholder message inside the image container + * and clears all annotations, colored regions, and captions. + * @returns {void} + */ export function showPlaceholder() { const c = getImageContainer(); if (!c) return; @@ -53,6 +66,10 @@ export function showPlaceholder() { if (imagesContent) imagesContent.classList.remove("has-images"); } +/** + * Clears all images, annotations, colored regions, and captions from the image container. + * @returns {void} + */ export function clearImages() { const c = getImageContainer(); if (c) { @@ -72,8 +89,16 @@ export function clearImages() { if (imagesContent) imagesContent.classList.remove("has-images"); } -/** ---- Public entry: render images array -------------------------------- - * Optionally pass { annotationsUrl: 'templates/data/annotations/xyz.json', boneId: 'bone_name' } +/** + * Renders one or more bone images into the image container, applying the appropriate + * layout (single, two-up, or grid) based on the number of images provided. + * Also loads colored region overlays and text annotation overlays if applicable. + * @param {Array<{url?: string, src?: string, alt?: string, filename?: string}>} images - Array of image objects to display. + * @param {Object} [options={}] - Optional display configuration: + * @param {string} [options.annotationsUrl] - API URL for text annotation JSON. + * @param {string} [options.boneId] - Bone ID used for colored region overlays. + * @param {boolean} [options.isBonesetSelection] - True when displaying the full boneset view. + * @returns {void} */ export function displayBoneImages(images, options = {}) { const container = getImageContainer(); @@ -114,6 +139,13 @@ export function displayBoneImages(images, options = {}) { } //** ---- Single image ------------------------------------------------------ */ +/** + * Renders a single bone image with its colored region overlay and text annotations. + * @param {{url?: string, src?: string, alt?: string, filename?: string}} image - The image object to display. + * @param {HTMLElement} container - The image container element. + * @param {Object} [options={}] - Options forwarded from `displayBoneImages`. + * @returns {void} + */ function displaySingleImage(image, container, options = {}) { // Get captions for this bone const captions = getCaptionsForBone(currentBoneId); @@ -197,6 +229,15 @@ const TWO_IMAGE_ROTATION = { right: { rot_deg: 0, flipH: false }, }; +/** + * Applies a CSS rotation (and optional horizontal flip) to an image element + * based on the PowerPoint rotation template for the given view. + * @param {HTMLImageElement} imgEl - The image element to transform. + * @param {Object} [options={}] - Rotation parameters. + * @param {number} [options.rot_deg=0] - Rotation angle in degrees. + * @param {boolean} [options.flipH=false] - Whether to flip the image horizontally. + * @returns {void} + */ function applyRotation(imgEl, { rot_deg = 0, flipH = false } = {}) { const parts = []; if (flipH) parts.push("scaleX(-1)"); @@ -206,6 +247,14 @@ function applyRotation(imgEl, { rot_deg = 0, flipH = false } = {}) { imgEl.style.willChange = "transform"; } +/** + * Renders two bone images side by side, each with its own colored region overlay. + * Appends a two-column caption bar beneath the images if captions are available. + * @param {Array<{url?: string, src?: string, alt?: string, filename?: string}>} images - Array of exactly two image objects. + * @param {HTMLElement} container - The image container element. + * @param {Object} [options={}] - Options forwarded from `displayBoneImages`. + * @returns {void} + */ function displayTwoImages(images, container, options = {}) { // Get captions for this bone const captions = getCaptionsForBone(currentBoneId); @@ -299,6 +348,13 @@ function displayTwoImages(images, container, options = {}) { } /** ---- 3+ images grid ---------------------------------------------------- */ +/** + * Renders three or more bone images in a wrapping grid layout. + * Does not load colored regions or annotations (used for supplementary views). + * @param {Array<{url?: string, src?: string, alt?: string, filename?: string}>} images - Array of image objects. + * @param {HTMLElement} container - The image container element. + * @returns {void} + */ function displayMultipleImages(images, container) { const wrapper = document.createElement("div"); wrapper.className = "multiple-image-wrapper"; diff --git a/templates/js/main.js b/templates/js/main.js index 9dfc0a6b..0d0c515d 100644 --- a/templates/js/main.js +++ b/templates/js/main.js @@ -109,6 +109,16 @@ document.addEventListener("DOMContentLoaded", async () => { clearViewer(); }); +/** + * Populates the subbone `` element with options for the given subbone IDs. + * Inserts a placeholder option and disables the dropdown if no subbones are provided. + * @param {HTMLSelectElement} dropdown - The subbone select element to populate. + * @param {string[]} subbones - Array of subbone ID strings. + * @returns {void} + */ function populateSubboneDropdown(dropdown, subbones) { // Leave a placeholder option so the user must explicitly select a subbone dropdown.innerHTML = ""; diff --git a/templates/js/navigation.js b/templates/js/navigation.js index 8d861baf..3ad59fb7 100644 --- a/templates/js/navigation.js +++ b/templates/js/navigation.js @@ -2,6 +2,15 @@ let currentBone = null; let currentSubboneIndex = -1; let subbones = []; +/** + * Initialises the previous/next subbone navigation buttons and the Home button. + * @param {HTMLButtonElement} prevButton - The "previous" navigation button. + * @param {HTMLButtonElement} nextButton - The "next" navigation button. + * @param {HTMLSelectElement} subboneDropdown - The subbone `