Updated DOM

added some DOM fixes for collapse and expand events on view page and fallback display for resources.
This commit is contained in:
Joshua Hare 2025-04-11 09:29:06 +10:00
parent 1e43e0c4bb
commit 95f80fbe91
3 changed files with 156 additions and 93 deletions

53
app.py
View File

@ -289,7 +289,7 @@ def unload_ig():
flash("Invalid package ID.", "error")
return redirect(url_for('view_igs'))
processed_ig = ProcessedIg.query.get(ig_id)
processed_ig = db.session.get(ProcessedIg, ig_id)
if processed_ig:
try:
db.session.delete(processed_ig)
@ -318,16 +318,54 @@ def get_structure_definition():
resource_identifier = request.args.get('resource_type')
if not all([package_name, package_version, resource_identifier]):
return jsonify({"error": "Missing query parameters"}), 400
# First, try to get the structure definition from the specified package
tgz_path = os.path.join(app.config['FHIR_PACKAGES_DIR'], services._construct_tgz_filename(package_name, package_version))
if not os.path.exists(tgz_path):
return jsonify({"error": f"Package file not found: {tgz_path}"}), 404
sd_data = None
fallback_used = False
if os.path.exists(tgz_path):
sd_data, _ = services.find_and_extract_sd(tgz_path, resource_identifier)
# If not found, fall back to the core FHIR package (hl7.fhir.r4.core#4.0.1)
if sd_data is None:
return jsonify({"error": f"SD for '{resource_identifier}' not found."}), 404
logger.debug(f"Structure definition for '{resource_identifier}' not found in {package_name}#{package_version}, attempting fallback to hl7.fhir.r4.core#4.0.1")
core_package_name = "hl7.fhir.r4.core"
core_package_version = "4.0.1"
# Ensure the core package is downloaded
core_tgz_path = os.path.join(app.config['FHIR_PACKAGES_DIR'], services._construct_tgz_filename(core_package_name, core_package_version))
if not os.path.exists(core_tgz_path):
logger.debug(f"Core package {core_package_name}#{core_package_version} not found, attempting to download")
try:
result = services.import_package_and_dependencies(core_package_name, core_package_version)
if result['errors'] and not result['downloaded']:
logger.error(f"Failed to download core package: {result['errors'][0]}")
return jsonify({"error": f"SD for '{resource_identifier}' not found in {package_name}#{package_version}, and failed to download core package: {result['errors'][0]}"}), 404
except Exception as e:
logger.error(f"Error downloading core package: {str(e)}")
return jsonify({"error": f"SD for '{resource_identifier}' not found in {package_name}#{package_version}, and error downloading core package: {str(e)}"}), 500
# Try to extract the structure definition from the core package
if os.path.exists(core_tgz_path):
sd_data, _ = services.find_and_extract_sd(core_tgz_path, resource_identifier)
if sd_data is None:
return jsonify({"error": f"SD for '{resource_identifier}' not found in {package_name}#{package_version} or in core package {core_package_name}#{core_package_version}."}), 404
fallback_used = True
else:
return jsonify({"error": f"SD for '{resource_identifier}' not found in {package_name}#{package_version}, and core package {core_package_name}#{core_package_version} could not be located."}), 404
elements = sd_data.get('snapshot', {}).get('element', []) or sd_data.get('differential', {}).get('element', [])
processed_ig = ProcessedIg.query.filter_by(package_name=package_name, version=package_version).first()
must_support_paths = processed_ig.must_support_elements.get(resource_identifier, []) if processed_ig else []
return jsonify({"elements": elements, "must_support_paths": must_support_paths})
response = {
"elements": elements,
"must_support_paths": must_support_paths,
"fallback_used": fallback_used,
"source_package": f"{core_package_name}#{core_package_version}" if fallback_used else f"{package_name}#{package_version}"
}
return jsonify(response)
@app.route('/get-example')
def get_example_content():
@ -579,10 +617,5 @@ with app.app_context():
db.create_all()
logger.debug("Database initialization complete")
# Add route to serve favicon
@app.route('/favicon.ico')
def favicon():
return send_from_directory(os.path.join(app.root_path, 'static'), 'favicon.ico', mimetype='image/vnd.microsoft.icon')
if __name__ == '__main__':
app.run(debug=True)

View File

@ -101,7 +101,7 @@ def find_and_extract_sd(tgz_path, resource_identifier): # Public version
if fileobj: fileobj.close() # Ensure resource cleanup
if sd_data is None:
logger.warning(f"SD matching '{resource_identifier}' not found within archive {os.path.basename(tgz_path)}")
logger.info(f"SD matching '{resource_identifier}' not found within archive {os.path.basename(tgz_path)} - caller may attempt fallback")
except tarfile.TarError as e:
logger.error(f"TarError reading {tgz_path} in find_and_extract_sd: {e}")
raise tarfile.TarError(f"Error reading package archive: {e}") from e
@ -182,7 +182,6 @@ def extract_dependencies(tgz_path):
error_message = f"Unexpected error extracting deps: {e}"; logger.error(error_message, exc_info=True); dependencies = None
return dependencies, error_message
# --- Recursive Import Orchestrator ---
def import_package_and_dependencies(initial_name, initial_version):
"""Orchestrates recursive download and dependency extraction."""
@ -247,7 +246,6 @@ def import_package_and_dependencies(initial_name, initial_version):
logger.info(f"Import finished. Processed: {proc_count}, Downloaded/Verified: {dl_count}, Errors: {err_count}")
return results
# --- Package File Content Processor (V6.2 - Fixed MS path handling) ---
def process_package_file(tgz_path):
""" Extracts types, profile status, MS elements, and examples from a downloaded .tgz package (Single Pass). """

View File

@ -1,9 +1,7 @@
{% extends "base.html" %}
{% block content %}
<div class="px-4 py-5 my-5 text
center">
<div class="px-4 py-5 my-5 text-center">
<img class="d-block mx-auto mb-4" src="{{ url_for('static', filename='FHIRFLARE.png') }}" alt="FHIRFLARE Ig Toolkit" width="192" height="192">
<h1 class="display-5 fw-bold text-body-emphasis">{{ title }}</h1>
<div class="col-lg-6 mx-auto">
@ -101,6 +99,7 @@ center">
<div id="structure-loading" class="text-center py-3" style="display: none;">
<div class="spinner-border text-primary" role="status"><span class="visually-hidden">Loading...</span></div>
</div>
<div id="structure-fallback-message" class="alert alert-info" style="display: none;"></div>
<div id="structure-content"></div>
</div>
</div>
@ -148,17 +147,8 @@ center">
{% else %}
<div class="alert alert-warning" role="alert">Processed IG details not available.</div>
{% endif %}
<!-- Debugging: Display raw data for verification ------------------------------------------------------------------------------
<div class="mt-4">
<details>
<summary class="text-muted small">Debug: Raw Data (Click to Expand)</summary>
<pre class="p-2 border bg-light">Resource Types Info: {{ processed_ig.resource_types_info | tojson | safe }}</pre>
<pre class="p-2 border bg-light">Examples: {{ processed_ig.examples | tojson | safe }}</pre>
</details>
</div>
</div>
-------------------------------------------------------------------------------------------------------------DEBUG end -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>const examplesData = {{ examples_by_type | tojson | safe }};</script>
<script>
@ -168,6 +158,7 @@ document.addEventListener('DOMContentLoaded', function() {
const structureDisplay = document.getElementById('structure-content');
const structureTitle = document.getElementById('structure-title');
const structureLoading = document.getElementById('structure-loading');
const structureFallbackMessage = document.getElementById('structure-fallback-message');
const exampleDisplayWrapper = document.getElementById('example-display-wrapper');
const exampleSelectorWrapper = document.getElementById('example-selector-wrapper');
const exampleSelect = document.getElementById('example-select');
@ -188,10 +179,18 @@ document.addEventListener('DOMContentLoaded', function() {
// Debugging: Log initial data
console.log("Examples Data:", examplesData);
document.body.addEventListener('click', function(event) {
// Debug all clicks to identify interference
document.addEventListener('click', function(event) {
console.log("Click event on:", event.target, "Class:", event.target.className);
}, true);
// Handle clicks on resource-type-link within resource-type-list containers
document.querySelectorAll('.resource-type-list').forEach(container => {
container.addEventListener('click', function(event) {
const link = event.target.closest('.resource-type-link');
if (!link) return;
event.preventDefault();
console.log("Resource type link clicked:", link.dataset.resourceType);
const pkgName = link.dataset.packageName;
const pkgVersion = link.dataset.packageVersion;
@ -211,6 +210,7 @@ document.addEventListener('DOMContentLoaded', function() {
rawStructureContent.textContent = '';
structureLoading.style.display = 'block';
rawStructureLoading.style.display = 'block';
structureFallbackMessage.style.display = 'none';
structureDisplayWrapper.style.display = 'block';
rawStructureWrapper.style.display = 'block';
exampleDisplayWrapper.style.display = 'none';
@ -221,14 +221,23 @@ document.addEventListener('DOMContentLoaded', function() {
return response.json().then(data => ({ ok: response.ok, status: response.status, data }));
})
.then(result => {
if (!result.ok) throw new Error(result.data.error || `HTTP error ${result.status}`);
if (!result.ok) {
throw new Error(result.data.error || `HTTP error ${result.status}`);
}
console.log("Structure data received:", result.data);
if (result.data.fallback_used) {
structureFallbackMessage.style.display = 'block';
structureFallbackMessage.textContent = `Structure definition not found in this IG; using base FHIR definition from ${result.data.source_package}.`;
} else {
structureFallbackMessage.style.display = 'none';
}
renderStructureTree(result.data.elements, result.data.must_support_paths || []);
rawStructureContent.textContent = JSON.stringify(result.data, null, 2);
populateExampleSelector(resourceType);
})
.catch(error => {
console.error("Error fetching structure:", error);
structureFallbackMessage.style.display = 'none';
structureDisplay.innerHTML = `<div class="alert alert-danger">Error: ${error.message}</div>`;
rawStructureContent.textContent = `Error: ${error.message}`;
})
@ -237,6 +246,7 @@ document.addEventListener('DOMContentLoaded', function() {
rawStructureLoading.style.display = 'none';
});
});
});
function populateExampleSelector(resourceOrProfileIdentifier) {
console.log("Populating examples for:", resourceOrProfileIdentifier);
@ -355,6 +365,7 @@ document.addEventListener('DOMContentLoaded', function() {
const mustSupportDisplay = isMustSupport ? '<i class="bi bi-check-circle-fill text-warning"></i>' : '';
const hasChildren = Object.keys(node.children).length > 0;
const collapseId = `collapse-${path.replace(/[\.\:]/g, '-')}`;
console.log(`Rendering node: ${path}, Collapse ID: ${collapseId}`); // Debug collapse IDs
const padding = level * 20;
const pathStyle = `padding-left: ${padding}px; white-space: nowrap;`;
let typeString = 'N/A';
@ -378,7 +389,7 @@ document.addEventListener('DOMContentLoaded', function() {
childrenHtml += `</ul>`;
}
let itemHtml = `<li class="${liClass}">`;
itemHtml += `<div class="row gx-2 align-items-center ${hasChildren ? 'collapse-toggle' : ''}" ${hasChildren ? `data-bs-toggle="collapse" href="#${collapseId}" role="button" aria-expanded="false" aria-controls="${collapseId}"` : ''}>`;
itemHtml += `<div class="row gx-2 align-items-center ${hasChildren ? 'collapse-toggle' : ''}" ${hasChildren ? `data-bs-toggle="collapse" data-bs-target="#${collapseId}" role="button" aria-expanded="false" aria-controls="${collapseId}"` : ''}>`;
itemHtml += `<div class="col-lg-4 col-md-3 text-truncate" style="${pathStyle}"><span style="display: inline-block; width: 1.2em; text-align: center;">`;
if (hasChildren) {
itemHtml += `<i class="bi bi-chevron-right small toggle-icon"></i>`;
@ -423,13 +434,34 @@ document.addEventListener('DOMContentLoaded', function() {
tooltipTriggerList.forEach(function (tooltipTriggerEl) {
new bootstrap.Tooltip(tooltipTriggerEl);
});
structureDisplay.querySelectorAll('.collapse').forEach(collapseEl => {
collapseEl.addEventListener('show.bs.collapse', event => {
event.target.previousElementSibling.querySelector('.toggle-icon')?.classList.replace('bi-chevron-right', 'bi-chevron-down');
});
collapseEl.addEventListener('hide.bs.collapse', event => {
event.target.previousElementSibling.querySelector('.toggle-icon')?.classList.replace('bi-chevron-down', 'bi-chevron-right');
// Initialize collapse toggles and chevron icons *after* rendering
const collapseToggles = structureDisplay.querySelectorAll('.collapse-toggle');
collapseToggles.forEach(toggleEl => {
const collapseId = toggleEl.getAttribute('data-bs-target');
const collapseEl = document.querySelector(collapseId);
const toggleIcon = toggleEl.querySelector('.toggle-icon');
if (collapseEl && toggleIcon) {
// Initially hide the collapse element
collapseEl.style.display = 'none';
// Handle click on the toggle element
toggleEl.addEventListener('click', (event) => {
event.preventDefault();
event.stopPropagation(); // Stop propagation to prevent conflicts
// Toggle visibility with a slight delay
const isExpanding = collapseEl.style.display === 'none';
toggleIcon.classList.replace(isExpanding ? 'bi-chevron-right' : 'bi-chevron-down', isExpanding ? 'bi-chevron-down' : 'bi-chevron-right');
// Use setTimeout for a slight delay before showing/hiding
setTimeout(() => {
collapseEl.style.display = isExpanding ? 'block' : 'none'; // Or 'flex', 'grid', etc.
}, 5); // A very small delay (milliseconds)
});
}
});
}
} catch (e) {