UNPKG

@geogirafe/lib-geoportal

Version:

GeoGirafe is a flexible application to build online geoportals.

290 lines (289 loc) 11.6 kB
import FeatureToGridDataById from '../../../tools/featuretogriddatabyid.js'; import { TabulatorFull as Tabulator } from 'tabulator-tables'; import { getUid } from 'ol/util.js'; import ColumnAliasHelper from '../../../tools/utils/aliases.js'; import { linkify } from '../../../tools/utils/utils.js'; import { noop } from '../../../tools/utils/async.js'; import tippy from 'tippy.js'; import { sanitize } from '../../../tools/utils/sanitize.js'; const geometryColumns = new Set(['geom', 'the_geom', 'geometry']); export default class SelectionTabulatorManager { featureToGridData; idTab = {}; tabHeaders = []; table = null; element = ''; data = {}; context; columnAliasHelper; minimalColumnWidth = 64; columnPositionsBeforeFreezing = {}; popupLastImageSrc; popupLastInstance; get tableColumns() { return this.table?.getColumns(false) ?? []; } constructor(context) { this.context = context; this.columnAliasHelper = new ColumnAliasHelper(context.stateManager); this.featureToGridData = new FeatureToGridDataById(this.context, { keepGeomProperty: true }); } /** * Set the HTML element to be used by the grid. */ setElement(htmlElement) { this.element = htmlElement; } /** * Replace the data of the current grid with the data of the specified tab. */ replaceData(id) { const columns = this.columnsToGridColumns(id, this.data[id].columns); this.table?.setColumns(columns); this.table?.replaceData(this.data[id].notOlProperties); } /** * Activates a tab matching the given id. */ activateTab(id) { for (const tabHeader of this.tabHeaders) { tabHeader.active = false; } const visibleTabHeader = this.tabHeaders?.find((tabHeader) => tabHeader.id === id); if (!visibleTabHeader) { return; } visibleTabHeader.active = true; this.context.stateManager.state.selection.selectedFeaturesType = id; } /** * Creates (replace) the Tabulator grid based on the provided tab id and related data and features. */ displayGrid(id) { if (!this.element) { return; } this.table = new Tabulator(this.element, { data: this.data[id].notOlProperties, columns: this.columnsToGridColumns(id, this.data[id].columns), selectableRows: true, layout: 'fitDataStretch', movableColumns: true, locale: true, maxHeight: '750', resizableRows: true, headerSortElement: function (_, dir) { switch (dir) { case 'asc': return '<img alt="sort-up-icon" src="icons/sort-up.svg" class="sort-icon" />'; case 'desc': return '<img alt="sort-down-icon" src="icons/sort-down.svg" class="sort-icon" />'; default: return '<img alt="sort-icon" src="icons/sort.svg" class="sort-icon default" />'; } }, popupContainer: this.element }); this.table?.on('rowSelectionChanged', (selection) => { // True if at least one row is selected. this.context.stateManager.state.selection.gridSelected = selection.length > 0; // Create a Map of features by their UIDs const featureMap = new Map(); for (const feature of this.data[id].features) { featureMap.set(getUid(feature.getGeometry()), feature); } // Highlight selected features on the map. this.context.stateManager.state.selection.highlightedFeatures = selection .map((row) => featureMap.get(getUid(row.geom ?? row.the_geom ?? row.geometry))) .filter((feature) => feature !== undefined); if (selection.length == 1) { this.context.stateManager.state.selection.getDirectionsFeature = this.context.stateManager.state.selection.highlightedFeatures[0]; } else { this.context.stateManager.state.selection.getDirectionsFeature = undefined; } }); this.table?.on('dataProcessed', () => this.redraw()); } /** * Transforms the given features into grid data and sets tab headers. * The grid data are completely replaced. */ featuresToGridData(features) { this.idTab = {}; const gridDataById = this.featureToGridData.toGridDataById(features); // Create tabs and collect data. for (const id of Object.keys(gridDataById)) { this.gridDataToGridTab(id, gridDataById[id]); } // Create tabs headers this.tabHeaders = Object.keys(this.idTab).map((key) => { return { id: key, text: this.context.i18nManager.getTranslation(key), active: false }; }); this.data = this.filterEmptyColumnsOnData(gridDataById); } /** * Converts grid data to grid tab format and adds it to the idTab object. * @private */ gridDataToGridTab(id, gridData) { if (id in this.idTab) { // Already in ? Don't add a new one. return; } this.idTab[id] = { columns: gridData.columns.map((column) => this.createGridColumn(id, column)), data: gridData.data, features: gridData.features }; } columnsToGridColumns(idTable, columns) { const columnDefinition = []; for (const column of columns) { if (geometryColumns.has(column)) { continue; } const columnAlias = this.columnAliasHelper.getColumnAlias(idTable, column); columnDefinition.push({ title: this.context.i18nManager.getTranslation(columnAlias), field: column, formatter: 'html', headerTooltip: true, minWidth: this.minimalColumnWidth, headerContextMenu: [ { label: (column) => this.context.i18nManager.getTranslation(column.getDefinition().frozen ? 'unfreeze-column' : 'freeze-column'), action: (_e, column) => { const columnDefinition = column.getDefinition(); const freezeOrNot = !columnDefinition.frozen; const [toColumn, after] = this.getLookupColumnForFreezing(); if (freezeOrNot) { this.columnPositionsBeforeFreezing[column.getField()] = this.tableColumns.findIndex((colComp) => colComp.getField() === column.getField()); this.table?.moveColumn(column, toColumn, after); } else { const colPos = this.columnPositionsBeforeFreezing[column.getField()]; if (colPos) { const previousColumn = this.tableColumns.at(colPos); if (previousColumn) { this.table?.moveColumn(column, previousColumn, true); } } } column .updateDefinition({ ...columnDefinition, frozen: !columnDefinition.frozen }) .then(noop); } } ], contextMenu: [ { label: this.context.i18nManager.getTranslation('copy-cell-content'), action: (_e, cell) => { navigator.clipboard?.writeText(cell.getElement().textContent); } } ], cellMouseEnter: (_e, cell) => { const imageElement = cell.getElement().querySelector('img'); if (imageElement && this.popupLastImageSrc !== imageElement.src) { if (this.popupLastInstance) { this.popupLastInstance.destroy(); } this.popupLastImageSrc = imageElement.src; this.popupLastInstance = tippy(imageElement, { arrow: true, theme: 'image-popup', content: (_ref) => { const popupImageElement = document.createElement('img'); popupImageElement.src = imageElement.src; return popupImageElement; } }); this.popupLastInstance.show(); } } }); } return columnDefinition; } getLookupColumnForFreezing() { const firstFrozenColumnDefinition = this.tableColumns.find((colComp) => colComp.getDefinition().frozen); const firstColumnDefinition = this.tableColumns[0]; return firstFrozenColumnDefinition ? [firstFrozenColumnDefinition, true] : [firstColumnDefinition, false]; } filterEmptyColumnsOnData(data) { for (const [_, entry] of Object.entries(data)) { const columns = entry.columns; const notOlProperties = entry.notOlProperties; for (const [_, row] of Object.entries(notOlProperties)) { for (const [key, value] of Object.entries(row)) { if (!columns.includes(key)) { delete row[key]; } else if (typeof value === 'string') { row[key] = sanitize(linkify(value), this.context.configManager.Config); } } } } return data; } /** * @returns A newly created grid column object. * @private */ createGridColumn(idTable, idColumn) { const columnAlias = this.columnAliasHelper.getColumnAlias(idTable, idColumn); return { id: idColumn, name: this.context.i18nManager.getTranslation(columnAlias) }; } blockRedraw() { this.table?.blockRedraw(); } restoreRedraw() { this.table?.restoreRedraw(); } redraw(force) { this.table?.redraw(force); } selectAll() { this.table?.selectRow(); } selectNone() { this.table?.deselectRow(); } invertSelection() { for (const row of this.table.getRows()) { row.toggleSelect(); } } getSelectedRows() { return this.table?.getRows('selected') ?? []; } getSelectedData() { return this.table?.getSelectedData() ?? []; } getColumnFields() { return this.table?.getColumns(false).map((columnComponent) => columnComponent.getField()) ?? []; } getTabHeaders() { return this.tabHeaders; } clearTabHeaders() { this.tabHeaders = []; } getTabIds() { return Object.keys(this.idTab); } }