|
145 | 145 | <!-- eslint-disable vue/attribute-hyphenation --> |
146 | 146 | <component |
147 | 147 | :is="currentHandler?.tagname" |
148 | | - v-if="currentFile" |
| 148 | + v-if="currentFile && elementReady(currentHandler)" |
149 | 149 | :file="currentFile" |
150 | 150 | :files="[]" |
151 | 151 | :is-sidebar-shown="isSidebarShown" |
|
156 | 156 | @errored="onError" /> |
157 | 157 | <component |
158 | 158 | :is="comparisonHandler?.tagname" |
159 | | - v-if="comparisonFile" |
| 159 | + v-if="comparisonFile && elementReady(comparisonHandler)" |
160 | 160 | :file="comparisonFile" |
161 | 161 | :files="[]" |
162 | 162 | :is-sidebar-shown="isSidebarShown" |
|
170 | 170 | <!-- Single file view --> |
171 | 171 | <component |
172 | 172 | :is="currentHandler?.tagname" |
173 | | - v-else-if="currentFile" |
| 173 | + v-else-if="currentFile && elementReady(currentHandler)" |
174 | 174 | v-show="!loading && !errorString" |
175 | 175 | :key="`${currentFile.fileid}-${reloadKey}`" |
176 | 176 | ref="handlerElement" |
@@ -245,6 +245,7 @@ import { getHandlerForFile } from '../helpers/handlerHelper.ts' |
245 | 245 | import { fetchFolderContent } from '../services/dav.ts' |
246 | 246 | import { logger } from '../services/logger.ts' |
247 | 247 | import { canDownload } from '../utils/canDownload.ts' |
| 248 | +import { initHandlerElement } from '../utils/customElements.ts' |
248 | 249 | import { restoreTitle, setViewerTitle } from '../utils/documentTitle.ts' |
249 | 250 | import { emittedValue, toError } from '../utils/handlerEvents.ts' |
250 | 251 | import { t } from '../utils/l10n.ts' |
@@ -353,6 +354,41 @@ const canOpenSidebar = computed(() => currentOptions.value.enableSidebar !== fal |
353 | 354 | // Comparison context (compare API) |
354 | 355 | const comparisonFile = ref<IFile>() |
355 | 356 | const comparisonHandler = ref<IHandler>() |
| 357 | +
|
| 358 | +// Tags whose element a handler's onInit() has defined. The registry itself |
| 359 | +// is not reactive, so this is what tells the template it may render them. |
| 360 | +const loadedTags = ref(new Set<string>()) |
| 361 | +
|
| 362 | +/** |
| 363 | + * Whether a handler's element can be rendered yet. |
| 364 | + * |
| 365 | + * Rendered before its tag is defined, the element gets its bindings as |
| 366 | + * attributes and loses them when it upgrades, so one that comes from |
| 367 | + * `onInit()` waits for it. |
| 368 | + * |
| 369 | + * @param handler - The handler about to be rendered |
| 370 | + */ |
| 371 | +function elementReady(handler?: IHandler): boolean { |
| 372 | + return handler !== undefined |
| 373 | + && (handler.onInit === undefined || loadedTags.value.has(handler.tagname) || window.customElements.get(handler.tagname) !== undefined) |
| 374 | +} |
| 375 | +
|
| 376 | +watch([currentHandler, comparisonHandler], (handlers) => { |
| 377 | + for (const handler of handlers) { |
| 378 | + if (handler === undefined || elementReady(handler)) { |
| 379 | + continue |
| 380 | + } |
| 381 | + initHandlerElement(handler).then(() => { |
| 382 | + loadedTags.value = new Set([...loadedTags.value, handler.tagname]) |
| 383 | + }, (error: unknown) => { |
| 384 | + logger.error('Could not initialize a handler', { handler: handler.id, error }) |
| 385 | + onError(error) |
| 386 | + }) |
| 387 | + } |
| 388 | +// Synchronous: closing and reopening with the same handler in one tick is a |
| 389 | +// change a deferred watcher would not see, and the retry after a failed |
| 390 | +// load would never happen |
| 391 | +}, { immediate: true, flush: 'sync' }) |
356 | 392 | const isComparing = computed(() => !!comparisonFile.value) |
357 | 393 |
|
358 | 394 | // Files actions rendered in the viewer menu (download, delete, …), linked to |
|
0 commit comments