Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 27 additions & 5 deletions templates/js/annotationOverlay.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
/**
* 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) {
Expand All @@ -12,6 +18,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");
Expand Down Expand Up @@ -66,8 +77,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.
* Uses normalized geometry from the annotation payload to map the label and
* pointer coordinates onto the displayed pixel size of the container.
* @param {HTMLElement} container - The element to draw annotations into.
* @param {Object} annotationsJson - Text annotation payload from the API.
* @param {Array<Object>} annotationsJson.annotations - Array of text annotation objects.
* @param {Object} annotationsJson.normalized_geometry - Normalized geometry for the slide crop.
Comment thread
leandrumartin marked this conversation as resolved.
* @returns {void}
*/
export function drawAnnotations(container, annotationsJson) {
if (!container || !annotationsJson) return;
Expand Down Expand Up @@ -95,7 +112,7 @@ export function drawAnnotations(container, annotationsJson) {
};

// Get the list of annotations.
const list = annotationsJson.annotations || annotationsJson.text_annotations || [];
const list = annotationsJson.annotations || [];

list.forEach((a) => {
if (!a || !a.text_box) return;
Expand Down Expand Up @@ -130,7 +147,7 @@ export function drawAnnotations(container, annotationsJson) {

if (validateEvent.detail.isValid) {
el.classList.add("valid-link");

el.addEventListener("click", () => {
const clickEvent = new CustomEvent("annotationSelected", {
detail: { text: a.text_content, annotationData: a },
Expand Down Expand Up @@ -166,7 +183,12 @@ export function drawAnnotations(container, annotationsJson) {
stage.__lastJson = annotationsJson;
}

/** 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}
*/
export function attachAutoscale(container) {
const stage = ensureStage(container);
if (stage.__resizeObs) return; // already attached
Expand Down
6 changes: 6 additions & 0 deletions templates/js/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,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<Object>} 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}`;

Expand Down
2 changes: 2 additions & 0 deletions templates/js/coloredRegionsOverlay.js
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,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}
Comment thread
leandrumartin marked this conversation as resolved.
*/
export function clearColoredRegions(container) {
if (!container) return;
Expand All @@ -501,6 +502,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");
Expand Down
8 changes: 8 additions & 0 deletions templates/js/description.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { fetchDescription } from "./api.js";

/**
* Fetches the description HTML for the given bone/subbone ID and
* places it 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<void>}
*/
export async function loadDescription(id) {
const container = document.getElementById("description-Container");
container.innerHTML = "";
Expand Down
31 changes: 28 additions & 3 deletions templates/js/dropdowns.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,27 @@ document.addEventListener("DOMContentLoaded", () => {
showPlaceholder();
});

/**
* 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 (document.getElementById("bone-image-container"));
}

/** 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`.
* @returns {Promise<void>}
*/
async function loadBoneImages(boneId, options = {}) {
const stage = getImageStage();

if (stage) {
clearAnnotations(stage);
stage.classList.remove("with-annotations");
clearAnnotations(stage);
stage.classList.remove("with-annotations");
}

if (!boneId) {
Expand All @@ -43,6 +53,12 @@ async function loadBoneImages(boneId, options = {}) {
}
}

/**
* Populates the boneset `<select>` element with options from the provided array.
* Disables the dropdown if no bonesets are available.
* @param {Array<{id: string, name: string}>} bonesets - Array of boneset objects.
* @returns {void}
*/
export function populateBonesetDropdown(bonesets) {
const bonesetSelect = document.getElementById("boneset-select");
if (!bonesetSelect) {
Expand All @@ -64,6 +80,15 @@ export function populateBonesetDropdown(bonesets) {
bonesetSelect.disabled = false;
}

/**
* Wires up change event listeners on the boneset, bone, and subbone `<select>` elements.
* Each listener loads images, descriptions, and annotations appropriate to the selection.
* @param {Object} combinedData - The full application data set.
* @param {Array<Object>} combinedData.bonesets - Array of boneset objects.
* @param {Array<Object>} combinedData.bones - Array of bone objects.
* @param {Array<Object>} combinedData.subbones - Array of subbone objects.
* @returns {void}
*/
export function setupDropdownListeners(combinedData) {
const bonesetSelect = document.getElementById("boneset-select");
const boneSelect = document.getElementById("bone-select");
Expand Down
8 changes: 8 additions & 0 deletions templates/js/imageCaptions.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
// 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.<string, {image1: string|null, image2: string|null}>}
*/
export const imageCaptions = {
// Bony Pelvis (main boneset)
"bony_pelvis": {
Expand Down
57 changes: 50 additions & 7 deletions templates/js/imageDisplay.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,42 @@ import { fetchAnnotations } from "./api.js";

let currentBoneId = null;

/**
* Returns the `#bone-image-container` DOM element.
* @returns {HTMLElement|null} The image container element, or null if not found.
*/
function getImageContainer() {
return (
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 };
}
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) {
existingCaptions.remove();
}
}

/** Empty-state / clearing */
/**
* Renders the empty-state placeholder message inside the image container
* and clears all text annotations, colored regions, and captions.
* @returns {void}
*/
export function showPlaceholder() {
const c = getImageContainer();
if (!c) return;
Expand All @@ -48,6 +61,10 @@ export function showPlaceholder() {
if (imagesContent) imagesContent.classList.remove("has-images");
}

/**
* Clears all images, text annotations, colored regions, and captions from the image container.
* @returns {void}
*/
export function clearImages() {
const c = getImageContainer();
if (c) {
Expand All @@ -65,8 +82,15 @@ 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.boneId] - Bone ID used for colored region overlays.
* @returns {void}
*/
export function displayBoneImages(images, options = {}) {
const container = getImageContainer();
Expand Down Expand Up @@ -109,7 +133,12 @@ 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.
* @returns {void}
Comment thread
leandrumartin marked this conversation as resolved.
*/
function displaySingleImage(image, container) {
const captions = getCaptionsForBone(currentBoneId);

Expand Down Expand Up @@ -176,6 +205,14 @@ function displaySingleImage(image, container) {
}
}

/**
* 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.
* @returns {void}
*/
function displayTwoImages(images, container) {
const captions = getCaptionsForBone(currentBoneId);

Expand Down Expand Up @@ -264,7 +301,13 @@ function displayTwoImages(images, container) {
}
}

/** 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";
Expand Down
13 changes: 7 additions & 6 deletions templates/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,6 @@ import quizManager from "./quiz.js";

let combinedData = { bonesets: [], bones: [], subbones: [] };

/**
* Handles bone selection from dropdown
* @param {string} boneId - The ID of the selected bone
*/
// handleBoneSelection is defined inside DOMContentLoaded after DOM elements are known

document.addEventListener("DOMContentLoaded", async () => {
initializeSearch();
await initializeSidebar();
Expand Down Expand Up @@ -90,6 +84,13 @@ document.addEventListener("DOMContentLoaded", async () => {
clearViewer();
});

/**
* Populates the subbone `<select>` 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 = "";
Expand Down
Loading
Loading