diff --git a/components/column-selector/index.js b/components/column-selector/index.js index 29dd4595..6f9524b5 100644 --- a/components/column-selector/index.js +++ b/components/column-selector/index.js @@ -34,6 +34,14 @@ export default class ColumnSelector extends HTMLElement { connectedCallback() { TPEN.attachAuthentication(this) this._unsubProject = onProjectReady(this, this.authgate) + + // Listen for column updates to refresh selector + this.cleanup.onEvent(eventDispatcher, "tpen-columns-updated", (event) => { + // Only refresh if the update is for the current page + if (event.detail?.pageId === TPEN.screen.pageInQuery) { + this.refreshFromProject() + } + }) } /** @@ -55,7 +63,7 @@ export default class ColumnSelector extends HTMLElement { } async findColumnsData() { - const pageId = new URLSearchParams(location.search).get("pageID") + const pageId = TPEN.screen.pageInQuery const page = TPEN.activeProject?.layers?.flatMap(layer => layer.pages || []).find(p => p.id.split('/').pop() === pageId) this.columns = page?.columns || [] @@ -80,6 +88,37 @@ export default class ColumnSelector extends HTMLElement { this.render() } + refreshFromProject() { + const pageId = TPEN.screen.pageInQuery + const page = TPEN.activeProject?.layers?.flatMap(layer => layer.pages || []).find(p => p.id.split('/').pop() === pageId) + this.columns = page?.columns || [] + + if (this.columns.length < 1) { + this.remove() + return + } + + this.columns = this.columns.map((col, i) => { + const isAuto = col.label.startsWith("Column ") && + /^[a-f\d]{24}$/i.test(col.label.slice(7)) + return { ...col, label: isAuto ? `Unnamed ${i + 1}` : col.label } + }) + + if (!this.#page) { + this.findColumnsData() + return + } + + const { orderedItems, columnsInPage, allColumnLines } = orderPageItemsByColumns( + { columns: this.columns, items: page?.items }, + this.#page + ) + this.columns = columnsInPage + this.allLinesInColumns = allColumnLines + this.#page.items = orderedItems + this.render() + } + getLabel(data) { if (typeof data.label === "string") { return data.label diff --git a/components/create-column/index.js b/components/create-column/index.js deleted file mode 100644 index 90460ddf..00000000 --- a/components/create-column/index.js +++ /dev/null @@ -1,805 +0,0 @@ -import TPEN from "../../api/TPEN.js" -import CheckPermissions from "../check-permissions/checkPermissions.js" -import { onProjectReady } from "../../utilities/projectReady.js" -import { CleanupRegistry } from "../../utilities/CleanupRegistry.js" -import vault from '../../js/vault.js' - -/** - * TpenCreateColumn - Interface for creating and managing columns on annotation pages. - * Requires LINE SELECTOR all access. - * @element tpen-create-column - */ -class TpenCreateColumn extends HTMLElement { - /** @type {CleanupRegistry} Registry for cleanup handlers */ - cleanup = new CleanupRegistry() - /** @type {CleanupRegistry} Registry for render-specific handlers (annotation boxes) */ - renderCleanup = new CleanupRegistry() - /** @type {CleanupRegistry} Registry for dynamic workspace listeners */ - workspaceCleanup = new CleanupRegistry() - /** @type {Function|null} Unsubscribe function for project ready listener */ - _unsubProject = null - - constructor() { - super() - this.attachShadow({ mode: 'open' }) - - this.projectID = null - this.pageID = null - this.annotationPageID = null - this.selectedBoxes = [] - this.lastClickedIndex = null - this.totalIds = [] - this.existingColumns = [] - this.originalColumnLabels = [] - - this.shadowRoot.innerHTML = ` - -
-
- -
- - -
-
- - -
- - -
-
-
-

Workspace

-

Use this area to merge or extend existing columns.

-
-
- ` - - this.container = this.shadowRoot.querySelector("#container") - this.createBtn = this.shadowRoot.querySelector("#createColumnBtn") - this.clearBtn = this.shadowRoot.querySelector("#clearSelectionBtn") - this.mergeColumnsCheckbox = this.shadowRoot.querySelector("#mergeColumnsCheckbox") - this.extendColumnCheckbox = this.shadowRoot.querySelector("#extendColumnCheckbox") - this.mergeColumnsLabel = this.shadowRoot.querySelector("#mergeColumnsLabel") - this.extendColumnLabel = this.shadowRoot.querySelector("#extendColumnLabel") - this.columnTitleInput = this.shadowRoot.querySelector("#columnTitle") - } - - connectedCallback() { - TPEN.attachAuthentication(this) - localStorage.removeItem('annotationsState') - const params = new URLSearchParams(window.location.search) - this.pageID = `${TPEN.RERUMURL}/id/${params.get("pageID")}` - this.projectID = params.get("projectID") - this.annotationPageID = this.pageID.split("/").pop() - this._unsubProject = onProjectReady(this, this.authgate) - } - - /** - * Authorization gate - checks permissions before loading page. - * Shows permission message if user lacks LINE SELECTOR all access. - */ - authgate() { - if (!CheckPermissions.checkAllAccess("LINE", "SELECTOR")) { - this.container.innerHTML = `
You do not have permission to manage columns.
` - return - } - this.addEventListeners() - this.loadPage(this.pageID) - } - - /** - * Registers event listeners using CleanupRegistry for proper cleanup. - */ - addEventListeners() { - // Guard: only register toolbar listeners once (authgate can be called multiple times) - if (this._toolbarListenersRegistered) return - this._toolbarListenersRegistered = true - - this.cleanup.onElement(this.createBtn, "click", () => this.createColumn()) - this.cleanup.onElement(this.clearBtn, "click", () => this.clearAllSelections()) - this.cleanup.onElement(this.mergeColumnsCheckbox, "change", () => this.handleModeChange()) - this.cleanup.onElement(this.extendColumnCheckbox, "change", () => this.handleModeChange()) - } - - disconnectedCallback() { - try { this._unsubProject?.() } catch {} - this.workspaceCleanup.run() - this.renderCleanup.run() - this.cleanup.run() - } - - async columnLabelCheck() { - this.originalColumnLabels = this.existingColumns.map(col => col.label) - this.existingColumns = this.existingColumns.map((col, index) => { - const { label } = col - const isAutoLabel = label.startsWith("Column ") && /^[a-f\d]{24}$/i.test(label.slice(7)) - return { - ...col, - label: isAutoLabel ? `Unnamed ${index + 1}` : label - } - }) - } - - async loadPage(pageID = null) { - try { - this.showLoading() - let { imgUrl, annotations, imgWidth, imgHeight } = await this.fetchPageViewerData(pageID) - await this.renderImage(imgUrl) - const page = TPEN.activeProject.layers.flatMap(layer => layer.pages || []).find(p => p.id.split('/').pop() === this.annotationPageID) - this.existingColumns = page?.columns?.map(column => ({ label: column.label, lines: column.lines })) || [] - const assignedAnnotationIds = [] - await this.columnLabelCheck() - this.existingColumns.forEach(column => { - column.lines.forEach(annoId => assignedAnnotationIds.push({ - lineId: annoId, columnLabel: column.label - })) - }) - this.totalIds = annotations.filter(anno => !assignedAnnotationIds.find(a => a.lineId === anno.lineId)).map(a => a.lineId) - localStorage.setItem('annotationsState', JSON.stringify({ - remainingIDs: this.totalIds, - selectedIDs: [] - })) - - this.renderAnnotations(annotations, imgWidth, imgHeight, assignedAnnotationIds) - this.restoreAnnotationState() - } catch (e) { - this.showError(e.message) - } - } - - handleModeChange() { - if (this.extendColumnCheckbox.checked || this.mergeColumnsCheckbox.checked) { - this.createBtn.classList.add("disable-button") - this.columnTitleInput.disabled = true - const workspaceToolbar = this.shadowRoot.querySelectorAll('.toolbar')[1] - workspaceToolbar.style.justifyContent = 'space-between' - } else { - this.createBtn.classList.remove("disable-button") - this.columnTitleInput.disabled = false - const workspaceToolbar = this.shadowRoot.querySelectorAll('.toolbar')[1] - workspaceToolbar.innerHTML = ` -

Workspace

-

Use this area to merge existing columns or extend them.

- ` - workspaceToolbar.style.justifyContent = 'flex-start' - } - - if (this.extendColumnCheckbox.checked) { - this.mergeColumnsCheckbox.classList.add("disable-other") - this.mergeColumnsLabel.classList.add("disable-other") - } else { - this.mergeColumnsCheckbox.classList.remove("disable-other") - this.mergeColumnsLabel.classList.remove("disable-other") - } - - if (this.mergeColumnsCheckbox.checked) { - this.extendColumnCheckbox.classList.add("disable-other") - this.extendColumnLabel.classList.add("disable-other") - } else { - this.extendColumnCheckbox.classList.remove("disable-other") - this.extendColumnLabel.classList.remove("disable-other") - } - - if (this.extendColumnCheckbox.checked) { - this.extendColumn() - } - - if (this.mergeColumnsCheckbox.checked) { - this.mergeColumns() - } - } - - mergeColumns() { - // Clear previous workspace listeners - this.workspaceCleanup.run() - const columnLabelsToMerge = [] - const columnLabels = this.existingColumns.map(col => col.label).filter(label => label) - const workspaceToolbar = this.shadowRoot.querySelectorAll('.toolbar')[1] - workspaceToolbar.innerHTML = `

Workspace - Merge Columns Mode

` - const workspaceMessage = document.createElement('div') - workspaceMessage.style.marginTop = '1em' - workspaceMessage.style.color = 'red' - workspaceMessage.style.fontWeight = '600' - workspaceMessage.style.fontSize = '1em' - if (columnLabels.length === 0) { - workspaceMessage.textContent = 'No columns available to merge.' - const workspaceToolbar = this.shadowRoot.querySelectorAll('.toolbar')[1] - workspaceToolbar.style.justifyContent = 'flex-start' - workspaceToolbar.appendChild(workspaceMessage) - return - } - - const input = document.createElement('input') - input.type = 'text' - input.placeholder = 'Enter new column label' - input.classList.add('merge-column-input') - workspaceToolbar.appendChild(input) - - columnLabels.forEach(label => { - const btn = document.createElement('button') - btn.classList.add('merge-label-btn') - btn.textContent = label - btn.style.margin = '5px' - btn.style.padding = '8px 12px' - btn.style.cursor = 'pointer' - this.workspaceCleanup.onElement(btn, 'click', () => { - if(!btn.dataset.selected) { - btn.dataset.selected = 'true' - btn.style.backgroundColor = 'rgb(255, 255, 255)' - btn.style.color = 'var(--primary-color)' - columnLabelsToMerge.push(columnLabels.indexOf(label) !== -1 ? columnLabels.indexOf(label) : '') - } else { - delete btn.dataset.selected - btn.style.backgroundColor = 'var(--primary-color)' - btn.style.color = 'rgb(255, 255, 255)' - const index = columnLabelsToMerge.indexOf(columnLabels.indexOf(label)) - if (index > -1) { - columnLabelsToMerge.splice(index, 1) - } - } - }) - workspaceToolbar.appendChild(btn) - }) - - const mergeColumnBtn = document.createElement('button') - mergeColumnBtn.id = 'mergeColumnBtn' - mergeColumnBtn.textContent = 'Merge Columns' - mergeColumnBtn.style.marginTop = '1em' - workspaceToolbar.appendChild(mergeColumnBtn) - - this.workspaceCleanup.onElement(mergeColumnBtn, 'click', async () => { - const newLabel = input.value.trim() - if (!newLabel) { - return TPEN.eventDispatcher.dispatch("tpen-toast", { - status: "error", message: 'Please enter a new column label.' - }) - } - - const duplicate = this.existingColumns.some(col => { - const existingLabel = (col.label ?? "").toString().trim() - return existingLabel === newLabel - }) - if (duplicate) { - return TPEN.eventDispatcher.dispatch("tpen-toast", { - status: "error", message: 'Column label already exists. Please choose a different label.' - }) - } - - try { - const res = await fetch(`${TPEN.servicesURL}/project/${this.projectID}/page/${this.annotationPageID}/column`, { - method: "PUT", - headers: { - "Authorization": `Bearer ${TPEN.getAuthorization()}`, - "Content-Type": "application/json" - }, - body: JSON.stringify({ - newLabel, - columnLabelsToMerge: columnLabelsToMerge.map(index => this.originalColumnLabels[index]), - }) - }) - if (!res.ok) throw new Error(`Failed to merge columns: ${res.status}`) - TPEN.eventDispatcher.dispatch("tpen-toast", { - status: "success", message: 'Columns merged successfully.' - }) - window.location.reload() - } catch (error) { - TPEN.eventDispatcher.dispatch("tpen-toast", { - status: "error", message: error.message - }) - } - }) - } - - extendColumn() { - // Clear previous workspace listeners - this.workspaceCleanup.run() - let columnToExtend = '' - const columnLabels = this.existingColumns.map(col => col.label).filter(label => label) - const workspaceToolbar = this.shadowRoot.querySelectorAll('.toolbar')[1] - workspaceToolbar.innerHTML = `

Workspace - Extend Column Mode

` - const workspaceMessage = document.createElement('div') - workspaceMessage.style.marginTop = '1em' - workspaceMessage.style.color = 'red' - workspaceMessage.style.fontWeight = '600' - workspaceMessage.style.fontSize = '1em' - if (columnLabels.length === 0) { - workspaceMessage.textContent = 'No columns available to extend.' - const workspaceToolbar = this.shadowRoot.querySelectorAll('.toolbar')[1] - workspaceToolbar.style.justifyContent = 'flex-start' - workspaceToolbar.appendChild(workspaceMessage) - return - } - - columnLabels.forEach(label => { - const btn = document.createElement('button') - btn.classList.add('extend-label-btn') - btn.textContent = label - btn.style.margin = '5px' - btn.style.padding = '8px 12px' - btn.style.cursor = 'pointer' - this.workspaceCleanup.onElement(btn, 'click', () => { - if (btn.dataset.selected) { - columnToExtend = '' - delete btn.dataset.selected - btn.style.backgroundColor = 'var(--primary-color)' - btn.style.color = 'rgb(255, 255, 255)' - } - else { - columnToExtend = columnLabels.indexOf(label) !== -1 ? columnLabels.indexOf(label) : '' - Array.from(workspaceToolbar.querySelectorAll('.extend-label-btn')).forEach(otherBtn => { - if (otherBtn !== btn) { - delete otherBtn.dataset.selected - otherBtn.style.backgroundColor = 'var(--primary-color)' - otherBtn.style.color = 'rgb(255, 255, 255)' - } - }) - btn.dataset.selected = 'true' - btn.style.backgroundColor = 'rgb(255, 255, 255)' - btn.style.color = 'var(--primary-color)' - } - }) - workspaceToolbar.appendChild(btn) - }) - - const extendColumnBtn = document.createElement('button') - extendColumnBtn.id = 'extendColumnBtn' - extendColumnBtn.textContent = 'Extend Column' - extendColumnBtn.style.marginTop = '1em' - workspaceToolbar.appendChild(extendColumnBtn) - - this.workspaceCleanup.onElement(extendColumnBtn, 'click', async () => { - if (!this.originalColumnLabels[columnToExtend]) { - return TPEN.eventDispatcher.dispatch("tpen-toast", { - status: "error", message: 'Please select a column to extend.' - }) - } - - if (this.selectedBoxes.length === 0) { - return TPEN.eventDispatcher.dispatch("tpen-toast", { - status: "error", message: 'Please select annotations to add to the column.' - }) - } - - try { - const annotationIdsToAdd = this.selectedBoxes.map(box => box.dataset.lineId) - const res = await fetch(`${TPEN.servicesURL}/project/${this.projectID}/page/${this.annotationPageID}/column`, { - method: "PATCH", - headers: { - "Authorization": `Bearer ${TPEN.getAuthorization()}`, - "Content-Type": "application/json" - }, - body: JSON.stringify({ - columnLabel: this.originalColumnLabels[columnToExtend], - annotationIdsToAdd - }) - }) - if (!res.ok) throw new Error(`Failed to extend column: ${res.status}`) - TPEN.eventDispatcher.dispatch("tpen-toast", { - status: "success", message: 'Column extended successfully.' - }) - window.location.reload() - } catch (error) { - TPEN.eventDispatcher.dispatch("tpen-toast", { - status: "error", message: error.message - }) - } - }) - } - - parseXYWH(target) { - const xywh = target.replace("xywh=pixel:", "").split(",").map(Number) - return { x: xywh[0], y: xywh[1], w: xywh[2], h: xywh[3] } - } - - async fetchPageViewerData(pageID = null) { - if (!pageID) throw new Error("No page ID provided") - const annotationPageData = await vault.getWithFallback(pageID, 'annotationpage', TPEN.activeProject?.manifest, true) - if (!annotationPageData) throw new Error("Failed to load annotation page") - const canvasData = await vault.getWithFallback( - annotationPageData.target, 'canvas', TPEN.activeProject?.manifest - ) - if (!canvasData) throw new Error("Failed to load canvas data") - return await this.processDirectCanvasData(canvasData, annotationPageData) - } - - async processDirectCanvasData(canvasData, annotationPageData = { items: [] }) { - const canvasInfo = await this.extractImageInfo(canvasData) - const annotations = await this.extractAnnotations(annotationPageData) - return { ...canvasInfo, annotations } - } - - async extractAnnotations(annotationPageData) { - if (!annotationPageData?.items) return [] - const results = await Promise.all(annotationPageData.items.map(async anno => { - try { - let data = anno - if (!data?.target) { - data = await vault.get(anno.id ?? anno, 'annotation', true) - } - if (!data) return null - return { target: data?.target?.selector?.value ?? data?.target, lineId: data?.id } - } catch { return null } - })) - return results.filter(r => r) - } - - async extractImageInfo(canvasData) { - const imgUrl = canvasData?.items?.[0]?.items?.[0]?.body?.id ?? canvasData?.images?.[0]?.resource?.["@id"] ?? canvasData?.images?.[0]?.resource?.id - const imgWidth = canvasData?.width - const imgHeight = canvasData?.height - if (!imgUrl || !imgWidth || !imgHeight) throw new Error("Missing image data") - return { imgUrl, imgWidth, imgHeight } - } - - showError(message) { - this.container.innerHTML = `
Error: ${message}
` - } - - showLoading(message = "Loading...") { - this.container.innerHTML = `
${message}
` - } - - async renderImage(imgUrl) { - return new Promise((resolve, reject) => { - const img = document.createElement("img") - img.id = "canvasImage" - img.src = imgUrl - img.onload = () => resolve(img) - img.onerror = () => reject("Failed to load image") - this.container.innerHTML = "" - this.container.appendChild(img) - }) - } - - restoreAnnotationState() { - const saved = localStorage.getItem('annotationsState') - if (!saved) return - const { selectedIDs = [] } = JSON.parse(saved) - const boxes = Array.from(this.shadowRoot.querySelectorAll('.overlayBox')) - this.selectedBoxes = boxes.filter(b => selectedIDs.includes(b.dataset.lineId)) - this.selectedBoxes.forEach((b, idx) => { - b.classList.add('clicked') - b.textContent = idx + 1 - }) - boxes.forEach(b => { - if (b.classList.contains('disabled')) return - if (!selectedIDs.includes(b.dataset.lineId)) b.textContent = '' - }) - if (this.selectedBoxes.length) { - const lastId = selectedIDs[selectedIDs.length - 1] - this.lastClickedIndex = boxes.findIndex(b => b.dataset.lineId === lastId) - } - } - - renderAnnotations(annotations, imgWidth, imgHeight, filteredAnnotations = []) { - // Clear previous annotation box listeners - this.renderCleanup.run() - - this.annotationData = annotations - const createdBoxes = [] - - annotations.forEach((anno, i) => { - if (!anno.target) return - - const { x, y, w, h } = this.parseXYWH(anno.target) - - const box = document.createElement("div") - box.className = "overlayBox" - box.dataset.index = i - box.dataset.lineId = anno.lineId - - box.style.left = `${(x / imgWidth) * 100}%` - box.style.top = `${(y / imgHeight) * 100}%` - box.style.width = `${(w / imgWidth) * 100}%` - box.style.height = `${(h / imgHeight) * 100}%` - - this.renderCleanup.onElement(box, "click", (event) => this.selectAnnotation(box, event)) - - createdBoxes.push(box) - }) - - createdBoxes.sort((a, b) => - parseFloat(a.style.top) - parseFloat(b.style.top) - ) - - createdBoxes.forEach(box => { - this.container.appendChild(box) - - const filtered = filteredAnnotations.find(a => a.lineId === box.dataset.lineId) - if (filtered) { - box.textContent = filtered.columnLabel - box.classList.add("clicked", "disabled") - } - }) - } - - selectAnnotation(box, event) { - if (box.classList.contains('disabled')) return - const boxes = Array.from(this.shadowRoot.querySelectorAll('.overlayBox')) - const clickedIndex = boxes.indexOf(box) - - if (event.shiftKey && this.lastClickedIndex !== null) { - const [start, end] = [this.lastClickedIndex, clickedIndex].sort((a, b) => a - b) - for (let i = start; i <= end; i++) { - const b = boxes[i] - if (!b.classList.contains('disabled') && !this.selectedBoxes.includes(b)) this.selectedBoxes.push(b) - if (!b.classList.contains('disabled')) b.classList.add('clicked') - } - } else if (event.metaKey || event.ctrlKey) { - if (box.classList.contains('clicked')) { - box.classList.remove('clicked') - this.selectedBoxes = this.selectedBoxes.filter(b => b !== box) - } else { - box.classList.add('clicked') - this.selectedBoxes.push(box) - } - this.lastClickedIndex = clickedIndex - } else { - boxes.forEach(b => { - if (!b.classList.contains('disabled')) b.classList.remove('clicked') - }) - this.selectedBoxes = [box] - box.classList.add('clicked') - this.lastClickedIndex = clickedIndex - } - - this.selectedBoxes.forEach((b, idx) => b.textContent = idx + 1) - boxes.forEach(b => { - if (!this.selectedBoxes.includes(b) && !b.classList.contains('disabled')) b.textContent = '' - }) - - const selectedIDs = this.selectedBoxes.map(b => b.dataset.lineId) - const remainingIDs = this.totalIds.filter(id => !this.selectedBoxes.some(b => b.dataset.lineId === id)) - try { - localStorage.setItem('annotationsState', JSON.stringify({ remainingIDs, selectedIDs })) - } catch (e) { - // localStorage may be unavailable (e.g., private mode, quota exceeded) - console.warn('Could not save annotationsState to localStorage:', e) - } - } - - async createColumn() { - if (!this.selectedBoxes?.length) - return TPEN.eventDispatcher.dispatch("tpen-toast", { - status: "error", message: 'Please select annotations first.' - }) - - const columnLabel = this.shadowRoot.getElementById("columnTitle").value.trim() - if (!columnLabel) - return TPEN.eventDispatcher.dispatch("tpen-toast", { - status: "error", message: 'Please enter a column title.' - }) - - const duplicate = this.existingColumns.some(col => { - const existingLabel = (col.label ?? "").toString().trim() - return existingLabel === columnLabel - }) - if (duplicate) { - return TPEN.eventDispatcher.dispatch("tpen-toast", { - status: "error", message: 'Column label already exists. Please choose a different label.' - }) - } - - const selectedIDs = this.selectedBoxes.map(b => b.dataset.lineId) - try { - const res = await fetch(`${TPEN.servicesURL}/project/${this.projectID}/page/${this.annotationPageID}/column`, { - method: "POST", - headers: { - "Authorization": `Bearer ${TPEN.getAuthorization()}`, - "Content-Type": "application/json" - }, - body: JSON.stringify({ - label: columnLabel, - annotations: selectedIDs - }) - }) - - if (!res.ok) - throw new Error(`Server error: ${res.status}`) - TPEN.eventDispatcher.dispatch("tpen-toast", { - status: "success", message: 'Column created successfully.' - }) - window.location.reload() - } catch (err) { - TPEN.eventDispatcher.dispatch("tpen-toast", { - status: "error", message: 'Failed to create column.' - }) - } - } - - async clearAllSelections() { - try { - const res = await fetch(`${TPEN.servicesURL}/project/${this.projectID}/page/${this.annotationPageID}/clear-columns`, { - method: 'DELETE', - headers: { - "Authorization": `Bearer ${TPEN.getAuthorization()}`, - 'Content-Type': 'application/json' - } - }) - if (!res.ok) throw new Error(`Server error: ${res.status}`) - TPEN.eventDispatcher.dispatch("tpen-toast", { - status: "info", message: 'All columns cleared successfully.' - }) - window.location.reload() - } catch (err) { - TPEN.eventDispatcher.dispatch("tpen-toast", { - status: "error", message: 'Failed to clear columns: ' + err.message - }) - } - } -} - -customElements.define("tpen-create-column", TpenCreateColumn) diff --git a/interfaces/manage-columns/index.js b/interfaces/manage-columns/index.js index 8deba03e..5aaccb0b 100644 --- a/interfaces/manage-columns/index.js +++ b/interfaces/manage-columns/index.js @@ -30,6 +30,8 @@ class TpenManageColumns extends HTMLElement { this.totalIds = [] this.existingColumns = [] this.originalColumnLabels = [] + this.cachedImageInfo = null + this.cachedAnnotations = [] this.shadowRoot.innerHTML = `