/** * Pathway Viewer Alpine.js Component * * Provides reactive status management and polling for pathway predictions. * Handles status updates, change detection, and update notices. */ document.addEventListener('alpine:init', () => { /** * Pathway Viewer Component * * Usage: *
* ... *
*/ Alpine.data('pathwayViewer', (config) => ({ status: config.status, modified: config.modified, modifiedDate: null, statusUrl: config.statusUrl, emptyDueToThreshold: config.emptyDueToThreshold === "True", showUpdateNotice: false, showEmptyDueToThresholdNotice: false, emptyDueToThresholdMessage: 'The Pathway is empty due to the selected threshold. Please try a different threshold.', updateMessage: '', pollInterval: null, get statusTooltip() { const tooltips = { 'completed': 'Pathway prediction completed.', 'failed': 'Pathway prediction failed.', 'running': 'Pathway prediction running.' }; return tooltips[this.status] || ''; }, init() { this.modifiedDate = this.parseDate(this.modified); if (this.status === 'running') { this.startPolling(); } if (this.emptyDueToThreshold) { this.showEmptyDueToThresholdNotice = true; } }, startPolling() { if (this.pollInterval) { return; } this.pollInterval = setInterval(() => this.checkStatus(), 5000); }, async checkStatus() { try { const response = await fetch(this.statusUrl); const data = await response.json(); if (data.emptyDueToThreshold) { this.emptyDueToThreshold = true; this.showEmptyDueToThresholdNotice = true; } const nextModifiedDate = this.parseDate(data.modified); const modifiedChanged = this.hasNewerTimestamp(nextModifiedDate, this.modifiedDate); const statusChanged = data.status !== this.status; if ((modifiedChanged || statusChanged) && !this.emptyDueToThreshold) { this.showUpdateNotice = true; this.updateMessage = this.getUpdateMessage(data.status, modifiedChanged, statusChanged); } this.modified = data.modified; this.modifiedDate = nextModifiedDate; this.status = data.status; if (data.status !== 'running' && this.pollInterval) { clearInterval(this.pollInterval); this.pollInterval = null; } } catch (err) { console.error('Polling error:', err); } }, getUpdateMessage(status, modifiedChanged, statusChanged) { // Prefer explicit status change messaging, otherwise fall back to modified change copy if (statusChanged) { if (status === 'completed') { return 'Prediction completed. Reload the page to see the updated Pathway.'; } if (status === 'failed') { return 'Prediction failed. Reload the page to see the latest status.'; } } let msg = 'Prediction '; if (status === 'running') { msg += 'is still running. But the Pathway was updated.'; } else if (status === 'completed') { msg += 'is completed. Reload the page to see the updated Pathway.'; } else if (status === 'failed') { msg += 'failed. Reload the page to see the current shape.'; } return msg; }, parseDate(dateString) { // Normalize "YYYY-MM-DD HH:mm:ss" into an ISO-compatible string to avoid locale issues if (!dateString) return null; return new Date(dateString.replace(' ', 'T')); }, hasNewerTimestamp(nextDate, currentDate) { if (!nextDate) return false; if (!currentDate) return true; return nextDate.getTime() > currentDate.getTime(); }, reloadPage() { location.reload(); } })); });