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 = `
-
-
- `
-
- 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 = `