fix: remaining hot migrations - handsontable/angular-wrapper
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 2m3s
Build / Build-and-test-development (pull_request) Failing after 1m34s

This commit is contained in:
M
2025-08-06 14:06:07 +02:00
parent b1db4ea590
commit b419cd5078
24 changed files with 637 additions and 261 deletions

View File

@@ -20,7 +20,7 @@
"@clr/angular": "file:libraries/clr-angular-17.9.0.tgz",
"@clr/icons": "^13.0.2",
"@clr/ui": "file:libraries/clr-ui-17.9.0.tgz",
"@handsontable/angular": "^16.0.1",
"@handsontable/angular-wrapper": "16.0.1",
"@sasjs/adapter": "^4.12.2",
"@sasjs/utils": "^3.4.0",
"@sheet/crypto": "file:libraries/sheet-crypto.tgz",
@@ -4714,23 +4714,23 @@
"integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==",
"license": "MIT"
},
"node_modules/@handsontable/angular": {
"node_modules/@handsontable/angular-wrapper": {
"version": "16.0.1",
"resolved": "https://registry.npmjs.org/@handsontable/angular/-/angular-16.0.1.tgz",
"integrity": "sha512-AOXPntQOwga8vI7KblvlBUx/sPc60PhXDGTqAdZhji1KPUvCrd7hyjR2R1YlffETe0zcCLDptmfbUEGXjaQtEw==",
"resolved": "https://registry.npmjs.org/@handsontable/angular-wrapper/-/angular-wrapper-16.0.1.tgz",
"integrity": "sha512-1yK5ES5l6+uG3KjXvfd9L0RupfPC8Rq5AR0D8tYBAG+Fyhr7oVHKbBONNSS/nzZHifgr/YLrnAvutQ+EZb0FdA==",
"license": "SEE LICENSE IN LICENSE.txt",
"optionalDependencies": {
"tslib": "^2.3.0"
},
"peerDependencies": {
"@angular/animations": ">=12.0.0",
"@angular/common": ">=12.0.0",
"@angular/compiler": ">=12.0.0",
"@angular/core": ">=12.0.0",
"@angular/forms": ">=12.0.0",
"@angular/platform-browser": ">=12.0.0",
"@angular/platform-browser-dynamic": ">=12.0.0",
"@angular/router": ">=12.0.0",
"@angular/animations": ">=16.0.0",
"@angular/common": ">=16.0.0",
"@angular/compiler": ">=16.0.0",
"@angular/core": ">=16.0.0",
"@angular/forms": ">=16.0.0",
"@angular/platform-browser": ">=16.0.0",
"@angular/platform-browser-dynamic": ">=16.0.0",
"@angular/router": ">=16.0.0",
"handsontable": "^16.0.0"
}
},

View File

@@ -48,7 +48,7 @@
"@clr/angular": "file:libraries/clr-angular-17.9.0.tgz",
"@clr/icons": "^13.0.2",
"@clr/ui": "file:libraries/clr-ui-17.9.0.tgz",
"@handsontable/angular": "^16.0.1",
"@handsontable/angular-wrapper": "16.0.1",
"@sasjs/adapter": "^4.12.2",
"@sasjs/utils": "^3.4.0",
"@sheet/crypto": "file:libraries/sheet-crypto.tgz",

View File

@@ -408,12 +408,11 @@
<div class="hot-wrapper clr-flex-1">
<hot-table
#hotInstance
hotId="hotInstance"
id="hotTable"
class="edit-hot"
className="htDark"
[class.hidden]="hotTable.hidden"
[licenseKey]="hotTable.licenseKey"
[data]="hotTable.data"
[settings]="hotTableSettings"
>
</hot-table>
</div>

View File

@@ -17,7 +17,7 @@ import { SasStoreService } from '../services/sas-store.service'
type AOA = any[][]
import { HotTableRegisterer } from '@handsontable/angular'
import { HotTableComponent } from '@handsontable/angular-wrapper'
import { UploadFile } from '@sasjs/adapter'
import { isSpecialMissing } from '@sasjs/utils/input/validators'
import CellRange from 'handsontable/3rdparty/walkontable/src/cell/range'
@@ -77,8 +77,8 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
uploadStaterCompList: QueryList<UploadStaterComponent> = new QueryList()
@ViewChildren('queryFilter')
queryFilterCompList: QueryList<QueryComponent> = new QueryList()
@ViewChildren('hotInstance')
hotInstanceCompList: QueryList<Handsontable> = new QueryList()
@ViewChild(HotTableComponent, { static: false })
hotTableComponent!: HotTableComponent
@ViewChildren('fileUploadInput')
fileUploadInputCompList: QueryList<ElementRef> = new QueryList()
@@ -120,13 +120,26 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
public hotInstance!: Handsontable
public dcValidator: DcValidator | undefined
public hotTableSettings: Handsontable.GridSettings = {}
private updateHotTableSettings(): void {
this.hotTableSettings = {
colHeaders: this.hotTable.colHeaders,
columns: this.hotTable.columns,
height: this.hotTable.height,
licenseKey: this.hotTable.licenseKey,
readOnly: this.hotTable.readOnly,
copyPaste: this.hotTable.copyPaste,
contextMenu: true
}
}
public hotTable: HotTableInterface = {
data: [],
colHeaders: [],
hidden: true,
columns: [],
height: 'calc(100vh - 160px)',
minSpareRows: 1,
licenseKey: undefined,
readOnly: true,
copyPaste: {
@@ -163,10 +176,30 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
}
},
row_above: {
name: 'Insert Row above'
name: 'Insert Row above',
callback: (
key: string,
selection: any[],
clickEvent: MouseEvent
) => {
const firstSelection = selection[0]
const targetRow = firstSelection.start.row
this.insertRowAtPosition(targetRow)
}
},
row_below: {
name: 'Insert Row below'
name: 'Insert Row below',
callback: (
key: string,
selection: any[],
clickEvent: MouseEvent
) => {
const firstSelection = selection[0]
const targetRow = firstSelection.start.row + 1
this.insertRowAtPosition(targetRow)
}
},
remove_row: {
name: 'Ignore row'
@@ -364,15 +397,12 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
private route: ActivatedRoute,
private sasService: SasService,
private cdf: ChangeDetectorRef,
private hotRegisterer: HotTableRegisterer,
private spreadsheetService: SpreadsheetService
) {
const lang = languages[window.navigator.language]
if (lang)
numbro.default.registerLanguage(languages[window.navigator.language])
this.hotRegisterer = new HotTableRegisterer()
this.parseRestrictions()
this.setRestrictions()
}
@@ -931,6 +961,9 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
this.cellValidationSource = []
// Clear custom validation styling
this.clearDuplicateValidation()
const hot = this.hotInstance
const columnSorting = hot.getPlugin('multiColumnSorting')
const columnSortConfig = columnSorting.getSortConfig()
@@ -991,22 +1024,54 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
setTimeout(() => {
const hot = this.hotInstance
const dsInsertIndex = this.dataSource.length
hot.alter('insert_row_below', dsInsertIndex, 1)
// Create a new empty row object with proper structure
const newRow = this.createEmptyRow()
// Add the new row to the data source
this.dataSource.push(newRow)
// Update the hot table with the new data
hot.updateSettings({ data: this.dataSource }, false)
// Select the newly added row
hot.selectCell(this.dataSource.length - 1, 0)
hot.render()
if (this.dataSource[dsInsertIndex]) {
this.dataSource[dsInsertIndex]['noLinkOption'] = true
}
this.addingNewRow = false
this.reSetCellValidationValues()
})
}
/**
* Creates a new empty row object with proper structure
*/
private createEmptyRow(): any {
const newRow: any = {}
this.headerColumns.forEach((col: string) => {
newRow[col] = ''
})
newRow['noLinkOption'] = true
return newRow
}
/**
* Inserts a new row at the specified position and updates the table
*/
private insertRowAtPosition(targetRow: number): void {
const newRow = this.createEmptyRow()
// Insert the new row at the target position
this.dataSource.splice(targetRow, 0, newRow)
// Update the hot table
const hot = this.hotInstance
hot.updateSettings({ data: this.dataSource }, false)
hot.render()
this.reSetCellValidationValues()
}
public cancelSubmit() {
this.dataSource = this.helperService.deepClone(this.dataSourceBeforeSubmit)
this.dataSourceBeforeSubmit = []
@@ -1086,51 +1151,96 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
}
}
public validatePrimaryKeys() {
private clearDuplicateValidation() {
const hot = this.hotInstance
const myTable = hot.getData()
this.pkFields = []
for (let index = 0; index < myTable.length; index++) {
let pkRow = ''
for (let ind = 1; ind < this.readOnlyFields + 1; ind++) {
pkRow = pkRow + '|' + myTable[index][ind]
}
this.pkFields.push(pkRow)
}
const results = []
const rows = this.dataSource.length
for (let j = 0; j < this.pkFields.length; j++) {
for (let i = 0; i < this.pkFields.length; i++) {
if (this.pkFields[j] === this.pkFields[i] && i !== j) {
results.push(i)
// Clear previous duplicate validation styling
for (const rowIndex of this.duplicatePkIndexes) {
for (let col = 1; col <= this.readOnlyFields; col++) {
hot.removeCellMeta(rowIndex, col, 'valid')
hot.removeCellMeta(rowIndex, col, 'dupKey')
// Remove our custom class from cell metadata
const cellMeta = hot.getCellMeta(rowIndex, col)
if (cellMeta.className) {
let cleanedClassName: string
if (Array.isArray(cellMeta.className)) {
cleanedClassName = cellMeta.className
.filter((c) => c !== 'dc-invalid-cell')
.join(' ')
} else {
cleanedClassName = cellMeta.className
.replace('dc-invalid-cell', '')
.trim()
}
hot.setCellMeta(rowIndex, col, 'className', cleanedClassName)
}
}
}
if (this.pkFields.length > rows) {
for (let n = rows; n < this.pkFields.length; n++) {
for (let p = rows; p < this.pkFields.length; p++) {
if (n < p && this.pkFields[n] === this.pkFields[p]) {
results.push(p)
this.duplicatePkIndexes = []
hot.render()
}
public validatePrimaryKeys() {
const hot = this.hotInstance
// Clear previous validation before applying new ones
this.clearDuplicateValidation()
// Get data from the data source instead of hot.getData() to ensure consistency
const myTable = this.dataSource
this.pkFields = []
for (let index = 0; index < myTable.length; index++) {
let pkRow = ''
for (let ind = 1; ind < this.readOnlyFields + 1; ind++) {
const colName = this.headerColumns[ind]
const value = myTable[index][colName] || ''
pkRow = pkRow + '|' + value
}
this.pkFields.push(pkRow)
}
const results: any = []
const rows = this.dataSource.length
// Only check for duplicates if we have data
if (this.pkFields.length > 0) {
for (let j = 0; j < this.pkFields.length; j++) {
for (let i = 0; i < this.pkFields.length; i++) {
if (
this.pkFields[j] === this.pkFields[i] &&
i !== j &&
this.pkFields[j] !== '|'
) {
results.push(i)
}
}
}
}
let cellMeta
// Clear any existing validation marks for all cells
for (let row = 0; row < myTable.length; row++) {
for (let col = 0; col < this.headerColumns.length; col++) {
const cellMeta = hot.getCellMeta(row, col)
if (cellMeta) {
cellMeta.valid = true
cellMeta.dupKey = false
}
}
}
// Mark duplicate cells as invalid
for (let k = 0; k < results.length; k++) {
for (let index = 1; index < this.readOnlyFields + 1; index++) {
cellMeta = hot.getCellMeta(results[k], index)
cellMeta.valid = false
cellMeta.dupKey = true
hot.render()
hot.setCellMeta(results[k], index, 'valid', false)
hot.setCellMeta(results[k], index, 'dupKey', true)
hot.setCellMeta(results[k], index, 'className', 'dc-invalid-cell')
}
}
this.duplicatePkIndexes = [...new Set(results.sort())]
hot.render()
}
/**
@@ -1425,10 +1535,26 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
this.dataSourceBeforeSubmit = this.helperService.deepClone(this.dataSource)
// Clean up the data source by removing noLinkOption property
for (let i = 0; i < this.dataSource.length; i++) {
delete this.dataSource[i].noLinkOption
}
// Remove any completely empty rows from the end
while (this.dataSource.length > 0) {
const lastRow = this.dataSource[this.dataSource.length - 1]
const isEmpty = Object.keys(lastRow).every((key) => {
if (key === '_____DELETE__THIS__RECORD_____') return true
return !lastRow[key] || lastRow[key] === ''
})
if (isEmpty) {
this.dataSource.pop()
} else {
break
}
}
hot.updateSettings(
{
data: this.dataSource,
@@ -1446,17 +1572,6 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
EditorComponent.cnt = 0
EditorComponent.nonPkCnt = 0
// this.saveLoading = true;
/**
* Below code should be analized, not sure what is the purpose of exceedCells
*/
const myTableData = hot.getData()
// If the last row is empty, remove it before validation
if (myTableData.length > 1 && hot.isEmptyRow(myTableData.length - 1)) {
hot.alter('remove_row', myTableData.length - 1)
}
this.validatePrimaryKeys()
@@ -1486,15 +1601,6 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
if (txt) txt.focus()
}, 200)
})
// let cnt = 0;
// hot.addHook("afterValidate", () => {
// this.updateSoftSelectColumns(true);
// cnt++;
// if (cnt === long) {
// this.validationDone = 1;
// }
// });
}
public async saveTable(data: any) {
@@ -1648,11 +1754,20 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
}
public checkInvalid() {
const hotElement = (this.hotInstanceCompList.first.container as any)
.nativeElement
const invalidCells = hotElement.querySelectorAll('.htInvalid')
// Use Angular wrapper to access Handsontable element instead of DOM queries
if (!this.hotTableComponent || !this.hotTableComponent.hotInstance)
return false
return invalidCells.length > 0
const hotElement = this.hotTableComponent.hotInstance.rootElement
if (!hotElement) return false
// Check for standard Handsontable validation failures
const standardInvalidCells = hotElement.querySelectorAll('.htInvalid')
// Check for our custom duplicate primary key validation failures
const customInvalidCells = hotElement.querySelectorAll('.dc-invalid-cell')
return standardInvalidCells.length > 0 || customInvalidCells.length > 0
}
public goToEditor() {
@@ -2192,6 +2307,7 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
*/
private setCellFilter(filter: boolean) {
const hotSelected = this.hotInstance.getSelected()
if (!hotSelected) return
const selection = hotSelected ? hotSelected[0] : hotSelected
// When we open a dropdown we want filter disabled so value in cell
@@ -2216,9 +2332,13 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
}
async ngOnInit() {
// Initialize hot table settings
this.updateHotTableSettings()
this.licenceService.hot_license_key.subscribe(
(hot_license_key: string | undefined) => {
this.hotTable.licenseKey = hot_license_key
this.updateHotTableSettings() // Update settings when license key changes
}
)
@@ -2276,6 +2396,27 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
setTimeout(() => {
this.fixAriaAccessibility()
}, 1000)
// Set up event listener for hot table element
// Double click to edit
setTimeout(() => {
if (this.hotTableComponent && this.hotTableComponent.hotInstance) {
const hotElement = this.hotTableComponent.hotInstance.rootElement
if (hotElement) {
hotElement.addEventListener('mousedown', (event: MouseEvent) => {
if (!this.uploadPreview) {
this.hotClicked()
}
setTimeout(() => {
const menuDebugItem: any =
document.querySelector('.debug-switch-item') || undefined
if (menuDebugItem) menuDebugItem.click()
}, 100)
})
}
}
}, 100)
}
ngOnDestroy() {
@@ -2440,11 +2581,12 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
}
initSetup(response: EditorsGetDataServiceResponse) {
this.hotInstance = this.hotRegisterer.getInstance('hotInstance')
this.hotInstance = this.hotTableComponent!.hotInstance!
if (this.getdataError) return
if (!response) return
if (!response.data) return
if (!this.hotInstance) return
this.cols = response.data.cols
this.dsmeta = response.data.dsmeta
@@ -2464,7 +2606,7 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
this.dsNote = ''
}
const hot: Handsontable = this.hotInstance
const hot = this.hotInstance
const approvers: Approver[] = response.data.approvers
@@ -2585,6 +2727,11 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
rowHeights: 24,
maxRows: this.licenceState.value.editor_rows_allowed || Infinity,
invalidCellClassName: 'htInvalid',
// Prevent automatic row creation
autoWrapRow: false,
autoWrapCol: false,
// Ensure proper data binding
bindRowsWithHeaders: false,
dropdownMenu: {
items: {
make_read_only: {
@@ -2659,7 +2806,51 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
cellProperties: Handsontable.CellProperties
) => {
const isReadonlyCol = col && this.isReadonlyCol(col)
if (isReadonlyCol) cellProperties.className = 'readonlyCell'
// Check if this cell should be marked as invalid due to duplicate primary key values
// Only applies to primary key columns (col 1 through readOnlyFields)
const isDuplicateCell =
this.duplicatePkIndexes.includes(row) &&
col >= 1 &&
col <= this.readOnlyFields
// Handle existing CSS classes - Handsontable can provide className as string or array
const existingClasses = cellProperties.className || ''
let classes: string[]
if (Array.isArray(existingClasses)) {
// If already an array, create a copy
classes = [...existingClasses]
} else {
// If string, split by spaces and filter out empty strings
classes = existingClasses
.split(' ')
.filter((c: string) => c.length > 0)
}
// Add readonlyCell class for readonly columns to maintain original styling
if (isReadonlyCol && !classes.includes('readonlyCell')) {
classes.push('readonlyCell')
}
// Apply custom validation styling for duplicate primary key cells
// Note: Uses 'dc-invalid-cell' instead of Handsontable's 'htInvalid' class
// because Handsontable's internal validation system was removing 'htInvalid'
// causing flickering. Our custom class persists reliably.
if (isDuplicateCell) {
if (!classes.includes('dc-invalid-cell')) {
classes.push('dc-invalid-cell')
}
// Mark cell as invalid to prevent form submission
cellProperties.valid = false
// Custom flag to identify this as a duplicate key cell for cleanup
cellProperties.dupKey = true
}
// Apply the combined CSS classes back to the cell
if (classes.length > 0) {
cellProperties.className = classes.join(' ')
}
}
},
false
@@ -2678,22 +2869,6 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
this.columnHeader[0] = 'Delete?'
this.readOnlyFields = response.data.sasparams[0].PKCNT
const hotInstaceEl = document.getElementById('hotInstance')
if (hotInstaceEl) {
hotInstaceEl.addEventListener('mousedown', (event) => {
if (!this.uploadPreview) {
this.hotClicked()
}
setTimeout(() => {
const menuDebugItem: any =
document.querySelector('.debug-switch-item') || undefined
if (menuDebugItem) menuDebugItem.click()
}, 100)
})
}
hot.addHook(
'afterSelection',
(
@@ -2796,6 +2971,21 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
}
})
// Add hook to prevent unwanted row creation
hot.addHook(
'beforeCreateRow',
(index: number, amount: number, source?: any) => {
// Only allow row creation through the Add Row button or context menu
if (
!this.addingNewRow &&
source !== 'ContextMenu.insert_row_above' &&
source !== 'ContextMenu.insert_row_below'
) {
return false
}
}
)
hot.addHook('beforePaste', (data: any, cords: any) => {
const startCol = cords[0].startCol

View File

@@ -2,7 +2,7 @@ import { CommonModule } from '@angular/common'
import { NgModule } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { ClarityModule } from '@clr/angular'
import { HotTableModule } from '@handsontable/angular'
import { HotTableModule } from '@handsontable/angular-wrapper'
import { registerAllModules } from 'handsontable/registry'
import { AppSharedModule } from '../app-shared.module'
import { DirectivesModule } from '../directives/directives.module'
@@ -28,7 +28,7 @@ registerAllModules()
FormsModule,
EditorRoutingModule,
ClarityModule,
HotTableModule.forRoot(),
HotTableModule,
AppSharedModule,
DirectivesModule,
SharedModule,

View File

@@ -166,13 +166,9 @@
>
<hot-table
hotId="hotInstanceUserDataset"
id="hotTableUserDataset"
class="mt-15"
[afterGetColHeader]="afterGetColHeader"
[settings]="hotUserDatasets"
[licenseKey]="hotTableLicenseKey"
stretchH="all"
[settings]="hotUserDatasetsSettings"
>
</hot-table>
@@ -360,17 +356,9 @@
</div>
<hot-table
hotId="hotInstance"
id="hotTable"
class="mt-15"
[afterGetColHeader]="afterGetColHeader"
[className]="['htDark', 'htCustomHidden']"
[licenseKey]="hotTableLicenseKey"
[multiColumnSorting]="true"
[viewportRowRenderingOffset]="50"
[manualColumnResize]="true"
[filters]="true"
stretchH="all"
[settings]="hotMainTableSettings"
>
</hot-table>
</ng-container>

View File

@@ -22,7 +22,7 @@ import { HotTableInterface } from '../models/HotTable.interface'
import { Col } from '../shared/dc-validator/models/col.model'
import { SpreadsheetService } from '../services/spreadsheet.service'
import Handsontable from 'handsontable'
import { HotTableRegisterer } from '@handsontable/angular'
import { HotTableComponent } from '@handsontable/angular-wrapper'
import { EditorsStageDataSASResponse } from '../models/sas/editors-stagedata.model'
import { CellChange, ChangeSource } from 'handsontable/common'
import { baseAfterGetColHeader } from '../shared/utils/hot.utils'
@@ -89,7 +89,10 @@ export class MultiDatasetComponent implements OnInit {
public hotInstance!: Handsontable
public hotInstanceUserDataset!: Handsontable
private hotRegisterer: HotTableRegisterer
@ViewChild('hotInstanceMain', { static: false })
hotTableMainComponent!: HotTableComponent
@ViewChild('hotInstanceUserDataset', { static: false })
hotTableUserDatasetComponent!: HotTableComponent
public showSubmitReasonModal: boolean = false
public submitReasonMessage: string = ''
@@ -136,7 +139,29 @@ export class MultiDatasetComponent implements OnInit {
}
},
manualRowMove: true,
columnSorting: true
columnSorting: true,
afterGetColHeader: baseAfterGetColHeader,
stretchH: 'all'
}
get hotMainTableSettings(): Handsontable.GridSettings {
return {
className: ['htDark', 'htCustomHidden'],
licenseKey: this.hotTableLicenseKey,
multiColumnSorting: true,
viewportRowRenderingOffset: 50,
manualColumnResize: true,
filters: true,
stretchH: 'all',
afterGetColHeader: baseAfterGetColHeader
}
}
get hotUserDatasetsSettings(): Handsontable.GridSettings {
return {
...this.hotUserDatasets,
licenseKey: this.hotTableLicenseKey
}
}
public afterGetColHeader = baseAfterGetColHeader
@@ -149,9 +174,7 @@ export class MultiDatasetComponent implements OnInit {
private spreadsheetService: SpreadsheetService,
private sasService: SasService,
private cdr: ChangeDetectorRef
) {
this.hotRegisterer = new HotTableRegisterer()
}
) {}
ngOnInit() {
this.licenceService.hot_license_key.subscribe(
@@ -392,7 +415,7 @@ export class MultiDatasetComponent implements OnInit {
initHot() {
setTimeout(() => {
this.hotInstance = this.hotRegisterer.getInstance('hotInstance')
this.hotInstance = this.hotTableMainComponent!.hotInstance!
// Set height of parsed data to full height of the page content area
const contentAreaHeight = this.contentAreaRef.nativeElement.clientHeight
@@ -413,9 +436,8 @@ export class MultiDatasetComponent implements OnInit {
initUserInputHot() {
setTimeout(() => {
this.hotInstanceUserDataset = this.hotRegisterer.getInstance(
'hotInstanceUserDataset'
)
this.hotInstanceUserDataset =
this.hotTableUserDatasetComponent!.hotInstance!
this.hotInstanceUserDataset.addHook(
'beforeChange',

View File

@@ -2,7 +2,7 @@ import { CommonModule } from '@angular/common'
import { NgModule } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { ClarityModule } from '@clr/angular'
import { HotTableModule } from '@handsontable/angular'
import { HotTableModule } from '@handsontable/angular-wrapper'
import { registerAllModules } from 'handsontable/registry'
import { AppSharedModule } from '../app-shared.module'
import { DirectivesModule } from '../directives/directives.module'

View File

@@ -2,7 +2,7 @@ import { CommonModule } from '@angular/common'
import { NgModule } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { ClarityModule } from '@clr/angular'
import { HotTableModule } from '@handsontable/angular'
import { HotTableModule } from '@handsontable/angular-wrapper'
import { DirectivesModule } from '../directives/directives.module'
import { SharedModule } from '../shared/shared.module'
import { ApproveDetailsComponent } from './approve-details/approve-details.component'
@@ -23,7 +23,7 @@ import { HistoryComponent } from './history/history.component'
FormsModule,
ReviewRoutingModule,
ClarityModule,
HotTableModule.forRoot(),
HotTableModule,
DirectivesModule,
SharedModule
]

View File

@@ -365,13 +365,18 @@ export class SasService {
}
},
(err: any) => {
if (err.error.includes('Unauthorized')) {
const errorMessage =
typeof err.error === 'string'
? err.error
: JSON.stringify(err.error || err)
if (errorMessage.includes('Unauthorized')) {
this.shouldLogin.next(true)
this.shouldLogin.subscribe((res: boolean) => {
if (res === false) location.reload()
})
} else if (err.error.includes(`Folder doesn't exist.`)) {
} else if (errorMessage.includes(`Folder doesn't exist.`)) {
console.warn(
'SASjs SAS services are not present on the current appLoc.'
)
@@ -419,7 +424,11 @@ export class SasService {
}
},
(err: any) => {
if (err.error.includes(`Folder doesn't exist.`)) {
const errorMessage =
typeof err.error === 'string'
? err.error
: JSON.stringify(err.error || err)
if (errorMessage.includes(`Folder doesn't exist.`)) {
reject()
}
}

View File

@@ -386,27 +386,9 @@
</div>
<hot-table
*ngIf="viewboxTableIndex > -1"
[hotId]="'hotInstance_viewbox_' + viewbox.id"
id="hotTable"
className="htDark"
[readOnly]="true"
[modifyColWidth]="maxWidthCheker"
[copyPaste]="viewboxTables[viewboxTableIndex].hotTable.copyPaste"
[contextMenu]="viewboxTables[viewboxTableIndex].hotTable.contextMenu"
[multiColumnSorting]="true"
[viewportRowRenderingOffset]="50"
[data]="viewboxTables[viewboxTableIndex].hotTable.data"
[colHeaders]="viewboxTables[viewboxTableIndex].hotTable.colHeaders"
[columns]="viewboxTables[viewboxTableIndex].hotTable.columns"
[filters]="true"
[dropdownMenu]="viewboxTables[viewboxTableIndex].hotTable.dropdownMenu"
[height]="calculateTableHeight(viewbox)"
stretchH="all"
[cells]="viewboxTables[viewboxTableIndex].hotTable.cells"
[maxRows]="viewboxTables[viewboxTableIndex].hotTable.maxRows"
[manualColumnResize]="true"
[licenseKey]="viewboxTables[viewboxTableIndex].hotTable.licenseKey"
*ngIf="viewboxTableIndex > -1 && viewboxHotSettings.get(viewbox.id)"
[settings]="viewboxHotSettings.get(viewbox.id) || {}"
[id]="'hotTable_' + viewbox.id"
></hot-table>
</div>
</div>

View File

@@ -21,9 +21,9 @@ import {
ViewEncapsulation
} from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { HotTableRegisterer } from '@handsontable/angular'
import { SASjsConfig } from '@sasjs/adapter'
import Handsontable from 'handsontable'
import { HotTableComponent } from '@handsontable/angular-wrapper'
import { cloneDeep } from 'lodash-es'
import { Subscription } from 'rxjs'
import { FilterQuery, FilterGroup } from 'src/app/models/FilterQuery'
@@ -54,6 +54,8 @@ export class ViewboxesComponent implements OnInit, AfterViewInit, OnDestroy {
@ViewChildren('resizeBox') resizeBoxQuery!: QueryList<ElementRef> //make query list, handle multiple
@ViewChildren('dragHandleCorner')
dragHandleCornerQuery!: QueryList<ElementRef>
@ViewChildren(HotTableComponent)
hotTableComponents!: QueryList<HotTableComponent>
private _viewboxModal: boolean = false
get viewboxModal(): boolean {
@@ -119,8 +121,9 @@ export class ViewboxesComponent implements OnInit, AfterViewInit, OnDestroy {
licenseKey: undefined,
dropdownMenu: undefined
}
public viewboxHotSettings: Map<number, Handsontable.GridSettings> = new Map()
public viewboxTables: ViewboxTable[] = []
private hotTableRegisterer: HotTableRegisterer
public filteringViewbox: Viewbox | undefined
@@ -150,9 +153,7 @@ export class ViewboxesComponent implements OnInit, AfterViewInit, OnDestroy {
private router: Router,
private activatedRoute: ActivatedRoute,
private cdf: ChangeDetectorRef
) {
this.hotTableRegisterer = new HotTableRegisterer()
}
) {}
ngOnInit(): void {
// Load libraries
@@ -207,7 +208,17 @@ export class ViewboxesComponent implements OnInit, AfterViewInit, OnDestroy {
}
ngAfterViewInit(): void {
//set handles for box resize
// Set handles for box resize and ensure HOT components are properly initialized
setTimeout(() => {
this.setAllHandleTransform()
// Force refresh of any existing HOT instances after view init
this.viewboxes.forEach((viewbox) => {
if (this.getViewboxTableIndex(viewbox) > -1) {
this.refreshTableAfterResize(viewbox)
}
})
}, 1000)
}
// Maximum number of open viewboxes reached
@@ -304,6 +315,9 @@ export class ViewboxesComponent implements OnInit, AfterViewInit, OnDestroy {
if (viewboxTable) {
viewboxTable.hotTable.data = res.viewdata
// Update settings with new data
this.createViewboxTableSettings(viewbox)
resolve(null)
} else {
resolve(null)
@@ -413,6 +427,9 @@ export class ViewboxesComponent implements OnInit, AfterViewInit, OnDestroy {
viewbox.query = this.helperService.deepClone(res.query)
viewbox.filterText = res.sasparams[0].FILTER_TEXT
// Create settings for this viewbox
this.createViewboxTableSettings(viewbox)
setTimeout(() => {
this.updateHotColumns(
viewboxTable!.hotTable.colHeadersHidden || [],
@@ -421,30 +438,34 @@ export class ViewboxesComponent implements OnInit, AfterViewInit, OnDestroy {
// HOT Settings are bound in HTML but some settings due to timing issues
// requires to be updated after the HOT is instanced
// after the update `render` method is called
const hotInstance = this.getViewboxHotInstance(viewbox.id)
// Use a longer timeout to ensure the HOT component is fully initialized
setTimeout(() => {
const hotInstance = this.getViewboxHotInstance(viewbox.id)
hotInstance?.updateSettings({
manualColumnMove: viewboxTable!.hotTable.manualColumnMove,
afterGetColHeader: (col: number, th: any) => {
const column = hotInstance?.colToProp(col) as string
if (hotInstance) {
hotInstance.updateSettings({
manualColumnMove: viewboxTable!.hotTable.manualColumnMove,
afterGetColHeader: (col: number, th: any) => {
const column = hotInstance?.colToProp(col) as string
// header columns styling - primary keys
const isPKCol =
column &&
viewboxTable!.hotTable.headerPks.indexOf(column) > -1
// header columns styling - primary keys
const isPKCol =
column &&
viewboxTable!.hotTable.headerPks.indexOf(column) > -1
if (isPKCol) th.classList.add('primaryKeyHeaderStyle')
// Dark mode
th.classList.add(globals.handsontable.darkTableHeaderClass)
if (isPKCol) th.classList.add('primaryKeyHeaderStyle')
// Dark mode
th.classList.add(globals.handsontable.darkTableHeaderClass)
}
})
hotInstance.render()
}
})
hotInstance?.render()
if (this.selectedViewbox) {
this.resetSelectedViewbox(viewbox)
}
})
if (this.selectedViewbox) {
this.resetSelectedViewbox(viewbox)
}
}, 500)
}, 100)
resolve()
})
@@ -490,6 +511,68 @@ export class ViewboxesComponent implements OnInit, AfterViewInit, OnDestroy {
return index
}
/**
* Create and store Handsontable settings for a specific viewbox
* @param viewbox
*/
private createViewboxTableSettings(viewbox: Viewbox): void {
const viewboxTableIndex = this.getViewboxTableIndex(viewbox)
if (viewboxTableIndex === -1) {
this.viewboxHotSettings.set(viewbox.id, {})
return
}
const viewboxTable = this.viewboxTables[viewboxTableIndex]
const calculatedHeight = this.calculateTableHeight(viewbox)
// HOT v16 settings - data will be loaded manually after initialization
const settings: Handsontable.GridSettings = {
colHeaders: viewboxTable.hotTable.colHeaders,
columns: viewboxTable.hotTable.columns,
height: calculatedHeight,
readOnly: true,
modifyColWidth: this.maxWidthCheker,
copyPaste: viewboxTable.hotTable.copyPaste,
contextMenu: viewboxTable.hotTable.contextMenu,
multiColumnSorting: true,
viewportRowRenderingOffset: 50,
filters: true,
dropdownMenu: viewboxTable.hotTable.dropdownMenu,
stretchH: 'all',
cells: viewboxTable.hotTable.cells,
maxRows: viewboxTable.hotTable.maxRows || Infinity,
manualColumnResize: true,
rowHeaders: true,
licenseKey: viewboxTable.hotTable.licenseKey
}
// Force a new object reference to trigger change detection
this.viewboxHotSettings.set(viewbox.id, { ...settings })
// Force change detection to ensure the template updates
setTimeout(() => {
this.cdf.detectChanges()
// Try to get the HOT instance and force a render
setTimeout(() => {
const hotInstance = this.getViewboxHotInstance(viewbox.id)
if (hotInstance) {
// Load data manually - this is required for HOT v16 Angular wrapper
hotInstance.loadData(viewboxTable.hotTable.data)
hotInstance.render()
}
}, 500)
})
}
/**
* Get stored Handsontable settings for a specific viewbox
* @param viewbox
*/
getViewboxTableSettings(viewbox: Viewbox): Handsontable.GridSettings {
return this.viewboxHotSettings.get(viewbox.id) || {}
}
/**
* Viewbox resize
* @param dragHandle
@@ -514,8 +597,9 @@ export class ViewboxesComponent implements OnInit, AfterViewInit, OnDestroy {
this.viewboxChanged()
this.eventService.dispatchEvent('resize')
// Refresh all viewbox tables after resize
// Refresh all viewbox tables after resize and update their settings
this.viewboxes.forEach((viewbox) => {
// Settings will include updated heights when accessed
this.refreshTableAfterResize(viewbox)
})
})
@@ -680,6 +764,7 @@ export class ViewboxesComponent implements OnInit, AfterViewInit, OnDestroy {
// Refresh all tables after snap to grid
this.viewboxes.forEach((viewbox) => {
// Settings will include correct height when accessed
this.refreshTableAfterResize(viewbox)
})
})
@@ -726,6 +811,7 @@ export class ViewboxesComponent implements OnInit, AfterViewInit, OnDestroy {
// Refresh table after restoring
setTimeout(() => {
// Settings will include correct height when accessed
this.refreshTableAfterResize(viewbox)
}, 100)
}
@@ -741,6 +827,7 @@ export class ViewboxesComponent implements OnInit, AfterViewInit, OnDestroy {
// Refresh table after expanding
setTimeout(() => {
// Settings will include correct height when accessed
this.refreshTableAfterResize(viewbox)
}, 100)
}
@@ -759,6 +846,9 @@ export class ViewboxesComponent implements OnInit, AfterViewInit, OnDestroy {
if (index > -1) this.viewboxes.splice(index, 1)
if (viewtableIndex > -1) this.viewboxTables.splice(viewtableIndex, 1)
// Clean up settings for this viewbox
this.viewboxHotSettings.delete(viewbox.id)
if (this.selectedViewbox?.id === viewbox.id) {
this.unsetSelectedViewbox()
}
@@ -1056,6 +1146,9 @@ export class ViewboxesComponent implements OnInit, AfterViewInit, OnDestroy {
}
viewboxTable.hotTable.data = res.viewdata
// Update settings with new data
this.createViewboxTableSettings(viewbox)
})
.catch((err: any) => {
this.loggerService.error(err)
@@ -1084,6 +1177,8 @@ export class ViewboxesComponent implements OnInit, AfterViewInit, OnDestroy {
this.updateHiddenColumnsHot(hiddenColProps, viewboxId)
this.setColumnOrder(viewboxId)
// Settings will be regenerated when accessed
}
/**
@@ -1179,8 +1274,6 @@ export class ViewboxesComponent implements OnInit, AfterViewInit, OnDestroy {
}
/**
* WORKAROUND: This is a workaround to calculate the height of the table since `100%`
* makes hot not load
* Calculate available height for Handsontable
* @param viewbox The viewbox to calculate height for
* @returns Available height in pixels
@@ -1188,9 +1281,13 @@ export class ViewboxesComponent implements OnInit, AfterViewInit, OnDestroy {
calculateTableHeight(viewbox: Viewbox): number {
// Calculate the exact height of the content div
const dragHandleHeight = 20
const searchFormHeight = 38
// Return the exact remaining height for the table
return viewbox.height - dragHandleHeight - searchFormHeight
const searchFormHeight = 36
const padding = 2
// Return the exact remaining height for the table with minimum height
const calculatedHeight =
viewbox.height - dragHandleHeight - searchFormHeight - padding
return calculatedHeight
}
/**
@@ -1202,7 +1299,30 @@ export class ViewboxesComponent implements OnInit, AfterViewInit, OnDestroy {
if (hotInstance) {
// Force the table to recalculate its dimensions
setTimeout(() => {
hotInstance.refreshDimensions()
try {
// Update height setting and refresh
hotInstance.updateSettings({
height: this.calculateTableHeight(viewbox)
})
hotInstance.refreshDimensions()
hotInstance.render()
} catch (error) {
// If refresh fails, try again later
setTimeout(() => {
try {
hotInstance.updateSettings({
height: this.calculateTableHeight(viewbox)
})
hotInstance.refreshDimensions()
} catch (e) {
console.warn(
'Failed to refresh HOT dimensions for viewbox',
viewbox.id,
e
)
}
}, 500)
}
}, 100)
}
}
@@ -1213,13 +1333,27 @@ export class ViewboxesComponent implements OnInit, AfterViewInit, OnDestroy {
* @returns HOT Instance from the given Viewbox
*/
private getViewboxHotInstance(viewboxId?: number): Handsontable | undefined {
if (!viewboxId) return
if (!viewboxId || !this.hotTableComponents) return
const hotInstance = this.hotTableRegisterer.getInstance(
`hotInstance_viewbox_${viewboxId}`
)
// Find the component corresponding to this viewbox
// Since we only show one table per viewbox and they're rendered in order,
// we can match by the viewbox's position in the array
const viewboxIndex = this.viewboxes.findIndex((vb) => vb.id === viewboxId)
if (viewboxIndex === -1) return
return hotInstance
// Get the HOT component at this index
const hotComponents = this.hotTableComponents.toArray()
let hotComponentIndex = 0
// Count how many viewboxes before this one have loaded tables
for (let i = 0; i < viewboxIndex; i++) {
if (this.getViewboxTableIndex(this.viewboxes[i]) > -1) {
hotComponentIndex++
}
}
const hotTableComponent = hotComponents[hotComponentIndex]
return hotTableComponent?.hotInstance || undefined
}
/**

View File

@@ -4,7 +4,11 @@ import { ClarityModule } from '@clr/angular'
import { FormsModule } from '@angular/forms'
import { ViewboxesComponent } from './viewboxes.component'
import { QueryModule } from 'src/app/query/query.module'
import { HotTableModule } from '@handsontable/angular'
import { HotTableModule } from '@handsontable/angular-wrapper'
import { registerAllModules } from 'handsontable/registry'
// register Handsontable's modules
registerAllModules()
import { DragDropModule } from '@angular/cdk/drag-drop'
import { AutocompleteModule } from '../autocomplete/autocomplete.module'
import { DcTreeModule } from '../dc-tree/dc-tree.module'

View File

@@ -125,19 +125,9 @@
</div>
<div class="card-block">
<hot-table
hotId="hotInstance"
id="hotTable"
className="htDark"
[data]="hotTable.data"
[colHeaders]="hotTable.colHeaders"
[columns]="hotTable.columns"
[maxRows]="hotTable.maxRows"
[height]="hotTable.height"
[licenseKey]="hotTable.licenseKey"
[afterGetColHeader]="hotTable.afterGetColHeader"
stretchH="all"
[cells]="hotTable.cells"
[settings]="hotTable.settings"
[settings]="hotTableSettings"
aria-label="Staged data table"
>
<!--[licenseKey]=null-->

View File

@@ -11,6 +11,7 @@ import { ActivatedRoute } from '@angular/router'
import { SasService } from '../services/sas.service'
import { EventService } from '../services/event.service'
import { HotTableInterface } from '../models/HotTable.interface'
import Handsontable from 'handsontable'
import { LicenceService } from '../services/licence.service'
import { globals } from '../_globals'
import { EditorsRestoreServiceResponse } from '../models/sas/editors-restore.model'
@@ -61,6 +62,22 @@ export class StageComponent implements OnInit, AfterViewInit {
}
}
get hotTableSettings(): Handsontable.GridSettings {
return {
...this.hotTable.settings,
colHeaders: this.hotTable.colHeaders,
columns: this.hotTable.columns,
maxRows: this.hotTable.maxRows,
height: this.hotTable.height,
licenseKey: this.hotTable.licenseKey,
afterGetColHeader: this.hotTable.afterGetColHeader,
afterInit: this.hotTable.afterInit,
stretchH: 'all',
cells: this.hotTable.cells,
className: 'htDark'
}
}
constructor(
private licenceService: LicenceService,
private sasStoreService: SasStoreService,

View File

@@ -1,7 +1,7 @@
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { StageComponent } from './stage.component'
import { HotTableModule } from '@handsontable/angular'
import { HotTableModule } from '@handsontable/angular-wrapper'
import { ClarityModule } from '@clr/angular'
import { RouterModule, Routes } from '@angular/router'

View File

@@ -621,33 +621,16 @@
</div>
<div *ngIf="!noData && !noDataReqErr && table" class="clr-flex-1">
<hot-table
#hotInstance
hotId="hotInstance"
id="hotTable"
className="htDark"
[multiColumnSorting]="true"
[viewportRowRenderingOffset]="50"
[data]="hotTable.data"
[colHeaders]="hotTable.colHeaders"
[columns]="hotTable.columns"
[copyPaste]="hotTable.copyPaste"
[contextMenu]="hotTable.contextMenu"
[filters]="true"
[dropdownMenu]="hotTable.dropdownMenu"
[height]="hotTable.height"
stretchH="all"
[modifyColWidth]="maxWidthCheker"
[cells]="hotTable.cells"
[maxRows]="hotTable.maxRows"
[manualColumnResize]="true"
[afterGetColHeader]="hotTable.afterGetColHeader"
[rowHeaders]="hotTable.rowHeaders"
[rowHeaderWidth]="hotTable.rowHeaderWidth"
[rowHeights]="hotTable.rowHeights"
[licenseKey]="hotTable.licenseKey"
>
</hot-table>
<div class="hot-wrapper clr-flex-1">
<hot-table
#hotInstance
id="hotTable"
class="view-hot"
[data]="hotTable.data"
[settings]="hotTableSettings"
>
</hot-table>
</div>
</div>
<div>

View File

@@ -18,7 +18,7 @@ import { globals } from '../_globals'
import { EventService } from '../services/event.service'
import { HelperService } from '../services/helper.service'
import { HotTableRegisterer } from '@handsontable/angular'
import { HotTableComponent } from '@handsontable/angular-wrapper'
import { SasService } from '../services/sas.service'
import { SASjsConfig } from '@sasjs/adapter'
import { QueryComponent } from '../query/query.component'
@@ -102,7 +102,35 @@ export class ViewerComponent
public sasjsConfig: SASjsConfig = new SASjsConfig()
public searchLoading: boolean = false
public searchNumeric: boolean = false
private hotTableRegisterer: HotTableRegisterer
@ViewChild(HotTableComponent, { static: false })
hotTableComponent!: HotTableComponent
public hotTableSettings: Handsontable.GridSettings = {}
private updateHotTableSettings(): void {
this.hotTableSettings = {
multiColumnSorting: true,
viewportRowRenderingOffset: 30,
colHeaders: this.hotTable.colHeaders,
columns: this.hotTable.columns,
copyPaste: this.hotTable.copyPaste,
contextMenu: this.hotTable.contextMenu,
filters: true,
dropdownMenu: this.hotTable.dropdownMenu,
height: this.hotTable.height,
stretchH: 'all',
modifyColWidth: this.maxWidthCheker,
cells: this.hotTable.cells,
maxRows: this.hotTable.maxRows,
manualColumnResize: true,
afterGetColHeader: this.hotTable.afterGetColHeader,
rowHeaders: this.hotTable.rowHeaders,
rowHeaderWidth: this.hotTable.rowHeaderWidth,
rowHeights: this.hotTable.rowHeights,
licenseKey: this.hotTable.licenseKey,
className: 'htDark'
}
}
public numberOfRows: number | null = null
public headerPks: string[] = []
public $dataFormats: $DataFormats | null = null
@@ -129,6 +157,13 @@ export class ViewerComponent
return ' '
},
afterGetColHeader: (col: number, th: any, headerLevel: number) => {
const column = this.hotInstance?.colToProp(col) as string
// header columns styling - primary keys
const isPKCol = column && this.headerPks.indexOf(column) > -1
if (isPKCol) th.classList.add('primaryKeyHeaderStyle')
// Dark mode
th.classList.add(globals.handsontable.darkTableHeaderClass)
},
@@ -203,7 +238,6 @@ export class ViewerComponent
private location: Location,
private cdf: ChangeDetectorRef
) {
this.hotTableRegisterer = new HotTableRegisterer()
this.sasjsConfig = this.sasService.getSasjsConfig()
}
@@ -223,6 +257,7 @@ export class ViewerComponent
this.licenceService.hot_license_key.subscribe(
(hot_license_key: string | undefined) => {
this.hotTable.licenseKey = hot_license_key
this.updateHotTableSettings() // Update settings when license key changes
}
)
}
@@ -857,6 +892,9 @@ export class ViewerComponent
return { readOnly: true }
}
// Update hot table settings after data is loaded
this.updateHotTableSettings()
this.tableFlag = false
let ds = []
ds = libDataset.split('.')
@@ -1081,7 +1119,7 @@ export class ViewerComponent
private setupHot() {
setTimeout(() => {
if (!this.loadingTableView && this.libDataset) {
this.hotInstance = this.hotTableRegisterer.getInstance('hotInstance')
this.hotInstance = this.hotTableComponent?.hotInstance
if (this.hotInstance) {
this.hotInstance.updateSettings({

View File

@@ -2,7 +2,7 @@ import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { ViewerComponent } from './viewer.component'
import { ViewRouteComponent } from '../routes/view-route/view-route.component'
import { HotTableModule } from '@handsontable/angular'
import { HotTableModule } from '@handsontable/angular-wrapper'
import { ViewerRoutingModule } from './viewer-routing.module'
import { ClarityModule } from '@clr/angular'
import { FormsModule } from '@angular/forms'
@@ -36,7 +36,7 @@ import { MetadataComponent } from '../metadata/metadata.component'
ClipboardModule,
FormsModule,
ClarityModule,
HotTableModule.forRoot(),
HotTableModule,
AppSharedModule,
SharedModule,
PipesModule,

View File

@@ -125,30 +125,9 @@
<div class="clr-flex-1">
<hot-table
hotId="hotInstance"
id="hot-table"
className="htDark"
[multiColumnSorting]="true"
[viewportRowRenderingOffset]="50"
[data]="selectedTab === TabsEnum.Rules ? xlmapRules : xlData"
[colHeaders]="
selectedTab === TabsEnum.Rules ? xlmapRulesHeaders : xlUploadHeader
"
[columns]="
selectedTab === TabsEnum.Rules ? xlmapRulesColumns : xlUploadColumns
"
[filters]="true"
[height]="'100%'"
stretchH="all"
[afterGetColHeader]="afterGetColHeader"
[modifyColWidth]="maxWidthChecker"
[cells]="getCellConfiguration"
[maxRows]="hotTableMaxRows"
[manualColumnResize]="true"
[rowHeaders]="rowHeaders"
[rowHeaderWidth]="15"
[rowHeights]="20"
[licenseKey]="hotTableLicenseKey"
[settings]="hotTableSettings"
>
</hot-table>
</div>

View File

@@ -23,6 +23,7 @@ import {
} from '../services'
import { getCellAddress, getFinishingCell } from './utils/xl.utils'
import { blobToFile, byteArrayToBinaryString } from './utils/file.utils'
import Handsontable from 'handsontable'
import { UploadFileResponse } from '../models/UploadFile'
interface XLMapRule {
@@ -136,6 +137,34 @@ export class XLMapComponent implements AfterContentInit, AfterViewInit, OnInit {
public hotTableMaxRows =
this.licenceState.value.viewer_rows_allowed || Infinity
get hotTableSettings(): Handsontable.GridSettings {
return {
multiColumnSorting: true,
viewportRowRenderingOffset: 50,
colHeaders:
this.selectedTab === this.TabsEnum.Rules
? this.xlmapRulesHeaders
: this.xlUploadHeader,
columns:
this.selectedTab === this.TabsEnum.Rules
? this.xlmapRulesColumns
: this.xlUploadColumns,
filters: true,
height: '100%',
stretchH: 'all',
afterGetColHeader: this.afterGetColHeader,
modifyColWidth: this.maxWidthChecker,
cells: this.getCellConfiguration,
maxRows: this.hotTableMaxRows,
manualColumnResize: true,
rowHeaders: this.rowHeaders,
rowHeaderWidth: 15,
rowHeights: 20,
licenseKey: this.hotTableLicenseKey,
className: 'htDark'
}
}
constructor(
private eventService: EventService,
private licenceService: LicenceService,

View File

@@ -2,7 +2,7 @@ import { CommonModule } from '@angular/common'
import { NgModule } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { ClarityModule } from '@clr/angular'
import { HotTableModule } from '@handsontable/angular'
import { HotTableModule } from '@handsontable/angular-wrapper'
import { registerAllModules } from 'handsontable/registry'
import { AppSharedModule } from '../app-shared.module'
import { DirectivesModule } from '../directives/directives.module'

View File

@@ -4781,6 +4781,12 @@ body[cds-theme="dark"] {
color: #ffffff !important;
}
.handsontable td.dc-invalid-cell {
background: #e62700ad !important;
border: 1px solid red !important;
color: #ffffff !important;
}
.handsontable .numericListbox {
text-align: right;
}

View File

@@ -13,6 +13,12 @@ if (fs.existsSync(sessionStoragePath)){
} catch (err) {}
}
let controlTableText = ''
if (_WEBIN_FILENAME1.includes('SASControlTable')) controlTableText = _WEBIN_FILEREF1.toString()
let webouts = {
MPE_X_TEST: `{"SYSDATE" : "26SEP22"
,"SYSTIME" : "08:48"