Spaces:
Build error
Build error
| /** | |
| * Annotation Guide Plugin | |
| * | |
| * Displays variant-specific annotation guidelines in a left-side drawer. | |
| * Fetches markdown documentation from configured URLs and renders it. | |
| */ | |
| /** | |
| * @import { PluginContext } from '../modules/plugin-context.js' | |
| * @import { ApplicationState } from '../state.js' | |
| * @import MarkdownIt from 'markdown-it' | |
| * @import { SlDrawer } from '../ui.js' | |
| * @import { annotationGuideDrawerPart } from '../templates/annotation-guide-drawer.types.js' | |
| */ | |
| import { Plugin } from '../modules/plugin-base.js' | |
| import { registerTemplate, createSingleFromTemplate } from '../modules/ui-system.js' | |
| import { | |
| createMarkdownRenderer, | |
| fetchMarkdown, | |
| renderMarkdown | |
| } from '../modules/markdown-utils.js' | |
| /** | |
| * Annotation guide information from extractor plugins | |
| * @typedef {object} AnnotationGuideInfo | |
| * @property {string} variant_id - The variant identifier | |
| * @property {"html" | "markdown"} type - The content type | |
| * @property {string} url - The URL to fetch the guide from | |
| */ | |
| // Register template | |
| await registerTemplate('annotation-guide-drawer', 'annotation-guide-drawer.html') | |
| class AnnotationGuidePlugin extends Plugin { | |
| /** @param {PluginContext} context */ | |
| constructor(context) { | |
| super(context, { name: 'annotation-guide', deps: ['help', 'extraction', 'dialog', 'logger'] }) | |
| } | |
| get #extraction() { return this.getDependency('extraction') } | |
| get #client() { return this.getDependency('client') } | |
| /** @type {SlDrawer & annotationGuideDrawerPart} */ | |
| #ui = null | |
| /** @type {MarkdownIt | null} */ | |
| #md = null | |
| /** @type {AnnotationGuideInfo[]} */ | |
| #annotationGuides = [] | |
| /** @type {string | null} */ | |
| #currentGuideUrl = null | |
| /** | |
| * @param {ApplicationState} _state | |
| */ | |
| async install(_state) { | |
| await super.install(_state) | |
| this.getDependency('logger').debug(`Installing plugin "annotation-guide"`) | |
| this.#ui = this.createUi(createSingleFromTemplate('annotation-guide-drawer', document.body)) | |
| this.#ui.closeBtn.addEventListener('click', () => this.#ui.hide()) | |
| this.#ui.openInNewWindowBtn.addEventListener('click', () => this.#openInNewWindow()) | |
| this.getDependency('help').registerTopic( | |
| 'Annotation Guide', | |
| 'file-text', | |
| () => this.open() | |
| ) | |
| this.#md = createMarkdownRenderer() | |
| // @ts-ignore | |
| window.appAnnotationGuide = this | |
| } | |
| /** | |
| * Opens the annotation guide drawer | |
| */ | |
| async open() { | |
| this.#ui.show() | |
| if (this.#annotationGuides.length === 0) { | |
| let extractors = this.#extraction.extractorInfo() | |
| if (!extractors) { | |
| extractors = await this.#client.getExtractorList() | |
| } | |
| if (extractors) { | |
| this.#annotationGuides = extractors.flatMap(e => e.annotationGuides || []) | |
| } | |
| } | |
| const variant = this.state?.variant | |
| if (variant) { | |
| await this.load(variant) | |
| } else { | |
| this.#ui.openInNewWindowBtn.hidden = true | |
| this.#ui.content.innerHTML = ` | |
| <div style="padding: 2rem; text-align: center; color: var(--sl-color-neutral-600);"> | |
| <sl-icon name="info-circle" style="font-size: 3rem; margin-bottom: 1rem;"></sl-icon> | |
| <p>No document loaded.</p> | |
| <p style="margin-top: 1rem; font-size: 0.875rem;"> | |
| Load a document to view its annotation guide. | |
| </p> | |
| </div> | |
| ` | |
| } | |
| } | |
| /** | |
| * Loads annotation guide for a specific variant | |
| * @param {string} variant The variant identifier | |
| */ | |
| async load(variant) { | |
| this.#ui.content.innerHTML = "" | |
| const variantGuides = this.#annotationGuides.filter(g => g.variant_id === variant) | |
| const markdownGuide = variantGuides.find(g => g.type === 'markdown') | |
| const htmlGuide = variantGuides.find(g => g.type === 'html') | |
| this.#currentGuideUrl = htmlGuide?.url || null | |
| this.#ui.openInNewWindowBtn.hidden = !htmlGuide | |
| if (!markdownGuide && !htmlGuide) { | |
| this.#ui.content.innerHTML = ` | |
| <div style="padding: 2rem; text-align: center; color: var(--sl-color-neutral-600);"> | |
| <sl-icon name="info-circle" style="font-size: 3rem; margin-bottom: 1rem;"></sl-icon> | |
| <p>No annotation guide is available for variant: <strong>${variant}</strong></p> | |
| <p style="margin-top: 1rem; font-size: 0.875rem;"> | |
| Check back later or contact your administrator for documentation. | |
| </p> | |
| </div> | |
| ` | |
| return | |
| } | |
| if (!markdownGuide && htmlGuide) { | |
| this.#ui.content.innerHTML = ` | |
| <div style="padding: 2rem; text-align: center; color: var(--sl-color-neutral-600);"> | |
| <sl-icon name="box-arrow-up-right" style="font-size: 2rem; margin-bottom: 1rem;"></sl-icon> | |
| <p>The annotation guide for this variant is available as an external page.</p> | |
| <p style="margin-top: 1rem;"> | |
| <a href="${htmlGuide.url}" target="_blank" rel="noopener">Open Annotation Guide</a> | |
| </p> | |
| </div> | |
| ` | |
| return | |
| } | |
| const guideUrl = /** @type {AnnotationGuideInfo} */(markdownGuide).url | |
| const [fetchUrl, anchor] = guideUrl.split('#') | |
| try { | |
| const logger = this.getDependency('logger') | |
| logger.debug(`Loading annotation guide from: ${fetchUrl}`) | |
| const markdown = await fetchMarkdown(fetchUrl, true) | |
| const html = renderMarkdown(/** @type {MarkdownIt} */(this.#md), markdown, { | |
| localLinkHandler: 'appAnnotationGuide.load', | |
| openExternalInNewTab: true | |
| }) | |
| this.#ui.content.innerHTML = html | |
| if (anchor) { | |
| setTimeout(() => { | |
| const targetElement = this.#ui.content.querySelector(`#${anchor}`) | |
| if (targetElement) { | |
| targetElement.scrollIntoView({ behavior: 'smooth', block: 'start' }) | |
| } else { | |
| logger.warn(`Anchor #${anchor} not found in annotation guide`) | |
| } | |
| }, 100) | |
| } | |
| } catch (error) { | |
| const errorMessage = error instanceof Error ? error.message : String(error) | |
| this.getDependency('logger').error(`Failed to load annotation guide: ${errorMessage}`) | |
| this.getDependency('dialog').error(`Failed to load annotation guide: ${errorMessage}`) | |
| this.#ui.content.innerHTML = ` | |
| <div style="padding: 2rem; text-align: center; color: var(--sl-color-danger-600);"> | |
| <sl-icon name="exclamation-octagon" style="font-size: 3rem; margin-bottom: 1rem;"></sl-icon> | |
| <p><strong>Error loading annotation guide</strong></p> | |
| <p style="margin-top: 1rem; font-size: 0.875rem;">${errorMessage}</p> | |
| </div> | |
| ` | |
| } | |
| } | |
| /** | |
| * Closes the annotation guide drawer | |
| */ | |
| close() { | |
| this.#ui.hide() | |
| } | |
| #openInNewWindow() { | |
| if (this.#currentGuideUrl) { | |
| window.open(this.#currentGuideUrl, '_blank') | |
| } | |
| } | |
| } | |
| export default AnnotationGuidePlugin | |
| export const plugin = AnnotationGuidePlugin | |