1492 lines
42 KiB
TypeScript
1492 lines
42 KiB
TypeScript
import {
|
|
CdkDragDrop,
|
|
CdkDragEnd,
|
|
CdkDragMove,
|
|
moveItemInArray,
|
|
transferArrayItem
|
|
} from '@angular/cdk/drag-drop'
|
|
import {
|
|
AfterViewInit,
|
|
ChangeDetectorRef,
|
|
Component,
|
|
ElementRef,
|
|
EventEmitter,
|
|
Input,
|
|
NgZone,
|
|
OnDestroy,
|
|
OnInit,
|
|
Output,
|
|
QueryList,
|
|
ViewChildren,
|
|
ViewEncapsulation
|
|
} from '@angular/core'
|
|
import { ActivatedRoute, Router } from '@angular/router'
|
|
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'
|
|
import { Libinfo } from 'src/app/models/sas/common/Libinfo'
|
|
import { PublicViewtablesServiceResponse } from 'src/app/models/sas/public-viewtables.model'
|
|
import { EventService } from 'src/app/services/event.service'
|
|
import { HelperService } from 'src/app/services/helper.service'
|
|
import { LicenceService } from 'src/app/services/licence.service'
|
|
import { LoggerService } from 'src/app/services/logger.service'
|
|
import { SasStoreService } from 'src/app/services/sas-store.service'
|
|
import { SasService } from 'src/app/services/sas.service'
|
|
import { AutocompleteComponent } from 'src/app/shared/autocomplete/autocomplete.component'
|
|
import { LibraryClickEmitter } from 'src/app/shared/dc-tree/models/LibraryClickEmitter'
|
|
import { TableClickEmitter } from 'src/app/shared/dc-tree/models/TableClickEmitter'
|
|
import { mergeColsRules } from 'src/app/shared/dc-validator/utils/mergeColsRules'
|
|
import { globals, initFilter } from 'src/app/_globals'
|
|
import { ViewboxHotTable } from './models/viewbox-hot-table.model'
|
|
import { ViewboxTable } from './models/viewbox-table.model'
|
|
import { Viewbox } from './models/viewbox.model'
|
|
|
|
@Component({
|
|
selector: 'app-viewboxes',
|
|
templateUrl: './viewboxes.component.html',
|
|
styleUrls: ['./viewboxes.component.scss'],
|
|
encapsulation: ViewEncapsulation.None
|
|
})
|
|
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 {
|
|
return this._viewboxModal
|
|
}
|
|
@Input() set viewboxModal(value: boolean) {
|
|
// If feature is disabled, prevent modal show up and show demo notice
|
|
if (this.licenceState.value.viewbox === false && !!value) {
|
|
this.eventService.showDemoLimitModal('Viewboxes')
|
|
this.viewboxModalChange.emit(false)
|
|
return
|
|
}
|
|
this._viewboxModal = value
|
|
|
|
if (!!value) this.unsetSelectedViewbox()
|
|
}
|
|
@Output() viewboxModalChange: EventEmitter<boolean> =
|
|
new EventEmitter<boolean>()
|
|
|
|
public libraries!: Array<any>
|
|
public tables: any
|
|
public libinfo: Libinfo[] | null = null
|
|
public librariesLoading: boolean = true
|
|
|
|
public viewboxes: Viewbox[] = []
|
|
|
|
public selectedViewbox: Viewbox | undefined
|
|
public selectedViewboxTable: ViewboxTable | undefined
|
|
|
|
public defaultConfig: Viewbox = {
|
|
id: -1,
|
|
library: '',
|
|
table: '',
|
|
width: 500,
|
|
height: 300,
|
|
x: 0,
|
|
y: 150,
|
|
columns: []
|
|
}
|
|
|
|
public sasjsConfig: SASjsConfig = new SASjsConfig()
|
|
|
|
public hotTableDefault: ViewboxHotTable = {
|
|
data: [],
|
|
headerPks: [],
|
|
$dataformats: {},
|
|
allColHeaders: [],
|
|
colHeadersHidden: [],
|
|
colHeadersVisible: [],
|
|
colHeaders: [],
|
|
contextMenu: ['copy_with_column_headers', 'copy_column_headers_only'],
|
|
copyPaste: {
|
|
copyColumnHeaders: true,
|
|
copyColumnHeadersOnly: true
|
|
},
|
|
columns: [],
|
|
cols: [],
|
|
height: 200, //WORKAROUND: Changed from '100%' to fixed pixel value because otherwize hot does not load
|
|
settings: {},
|
|
hiddenColumns: true,
|
|
manualColumnMove: false,
|
|
afterGetColHeader: undefined,
|
|
licenseKey: undefined,
|
|
dropdownMenu: undefined
|
|
}
|
|
|
|
public viewboxHotSettings: Map<number, Handsontable.GridSettings> = new Map()
|
|
public viewboxTables: ViewboxTable[] = []
|
|
|
|
public filteringViewbox: Viewbox | undefined
|
|
|
|
public filter: boolean = false
|
|
public filterLoading: boolean = false
|
|
public clauses: any
|
|
public nullVariables: boolean = false
|
|
public filterLibds: string | undefined
|
|
public _query: Subscription | undefined
|
|
|
|
public licenceState = this.licenceService.licenceState
|
|
public Infinity = Infinity
|
|
|
|
public maxViewboxes: number =
|
|
this.licenceState.value.viewbox_limit === Infinity
|
|
? 6
|
|
: this.licenceState.value.viewbox_limit || 6
|
|
|
|
constructor(
|
|
private ngZone: NgZone,
|
|
private licenceService: LicenceService,
|
|
private sasService: SasService,
|
|
private eventService: EventService,
|
|
private sasStoreService: SasStoreService,
|
|
private loggerService: LoggerService,
|
|
private helperService: HelperService,
|
|
private router: Router,
|
|
private activatedRoute: ActivatedRoute,
|
|
private cdf: ChangeDetectorRef
|
|
) {}
|
|
|
|
ngOnInit(): void {
|
|
// Load libraries
|
|
this.sasStoreService
|
|
.viewLibs()
|
|
.then((res: any) => {
|
|
this.libraries = res.saslibs
|
|
})
|
|
.catch((err: any) => {
|
|
this.loggerService.error(err)
|
|
})
|
|
.finally(() => {
|
|
this.librariesLoading = false
|
|
})
|
|
|
|
// Listen for filtering data
|
|
this._query = this.sasStoreService.query.subscribe((query: any) => {
|
|
this.clauses = query.obj
|
|
this.filterLibds = query.libds
|
|
})
|
|
|
|
this.sasjsConfig = this.sasService.getSasjsConfig()
|
|
|
|
this.licenceService.hot_license_key.subscribe(
|
|
(hot_license_key: string | undefined) => {
|
|
this.hotTableDefault.licenseKey = hot_license_key
|
|
}
|
|
)
|
|
|
|
const viewboxesQueryParam =
|
|
this.activatedRoute.snapshot.queryParams.viewboxes
|
|
|
|
if (viewboxesQueryParam) {
|
|
if (this.licenceState.value.viewbox === false) {
|
|
setTimeout(() =>
|
|
this.eventService.showDemoLimitModal('Linking Viewboxes')
|
|
)
|
|
this.router.navigate([], {
|
|
relativeTo: this.activatedRoute,
|
|
queryParams: {}
|
|
})
|
|
} else {
|
|
this.viewboxes = this.decodeUrlData(viewboxesQueryParam)
|
|
|
|
setTimeout(() => {
|
|
this.setAllHandleTransform()
|
|
})
|
|
}
|
|
}
|
|
|
|
this.reLoadViewboxtables(this.viewboxes)
|
|
}
|
|
|
|
ngAfterViewInit(): void {
|
|
// 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
|
|
get viewboxLimitReached(): boolean {
|
|
return this.viewboxes.length >= this.maxViewboxes
|
|
}
|
|
|
|
clrModalOpenChange(open: boolean) {
|
|
this.viewboxModalChange.emit(open)
|
|
}
|
|
|
|
libraryOnClick(data: LibraryClickEmitter) {
|
|
if (!data.tablesLoaded)
|
|
this.loadTables(data.library.LIBRARYREF, data.library)
|
|
}
|
|
|
|
/**
|
|
* Adding new Viewbox - Fired when table is selected from the `dc-tree`
|
|
* @param data Selected table data coming from the `dc-tree`
|
|
*/
|
|
async tableOnClick(data: TableClickEmitter) {
|
|
if (this.viewboxLimitReached) return
|
|
|
|
const viewbox = {
|
|
...this.defaultConfig,
|
|
table: data.libTable,
|
|
library: data.library.LIBRARYNAME,
|
|
loadingData: true,
|
|
filter_pk: '0',
|
|
id: this.viewboxes.length + 1,
|
|
x: window.innerWidth - this.defaultConfig.width,
|
|
y: 150
|
|
}
|
|
|
|
this.viewboxes.push(viewbox)
|
|
|
|
setTimeout(() => {
|
|
this.setAllHandleTransform()
|
|
})
|
|
|
|
const libDataset = `${data.library.LIBRARYREF}.${data.libTable}`
|
|
|
|
await this.loadData(libDataset, viewbox)
|
|
|
|
viewbox.loadingData = false
|
|
this.eventService.dispatchEvent('resize') //Force HOT refresh
|
|
this.snapToGrid() //it will call viewboxChanged
|
|
}
|
|
|
|
/**
|
|
* Laods tables to populate `dc-tree`
|
|
* @param lib
|
|
* @param library
|
|
*/
|
|
loadTables(lib: string, library?: any) {
|
|
this.sasStoreService
|
|
.viewTables(lib)
|
|
.then((res: PublicViewtablesServiceResponse) => {
|
|
let tables = res.mptables.map(function (item: any) {
|
|
return item.MEMNAME
|
|
})
|
|
|
|
this.libinfo = res.libinfo || []
|
|
this.tables = tables
|
|
|
|
if (library) {
|
|
library['tables'] = tables
|
|
library['libinfo'] = this.libinfo
|
|
library['loadingTables'] = false
|
|
|
|
if (tables.length > 0) library['expanded'] = true
|
|
}
|
|
})
|
|
.catch((err: any) => {
|
|
this.loggerService.error(err)
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Re-loads data particular Viewbox (preserving the filtering PK)
|
|
* @param libDataset library.table
|
|
* @param viewbox viewbox to reload data for
|
|
* @returns Promise
|
|
*/
|
|
async reloadData(libDataset: string, viewbox: Viewbox) {
|
|
return new Promise((resolve, reject) => {
|
|
let viewboxTable = this.viewboxTables.find(
|
|
(vbt) => vbt.viewboxId === viewbox.id
|
|
)
|
|
|
|
this.sasStoreService
|
|
.viewData(libDataset, parseInt(viewbox.filter_pk || '0'))
|
|
.then((res: any) => {
|
|
if (viewboxTable) {
|
|
viewboxTable.hotTable.data = res.viewdata
|
|
|
|
// Update settings with new data
|
|
this.createViewboxTableSettings(viewbox)
|
|
|
|
resolve(null)
|
|
} else {
|
|
resolve(null)
|
|
}
|
|
})
|
|
.catch(() => {
|
|
reject()
|
|
})
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Initial data load for particular Viewbox
|
|
* If data has been already found for particular Viewbox
|
|
* and filtering is not active it will return stored data
|
|
* instead of sending new request
|
|
*
|
|
* @param libDataset library.table
|
|
* @param viewbox
|
|
* @returns Empty Promise - used for awaiting only
|
|
*/
|
|
async loadData(libDataset: string, viewbox: Viewbox): Promise<void> {
|
|
return new Promise((resolve, reject) => {
|
|
let existingViewboxTable: boolean = false
|
|
|
|
let viewboxTable = this.viewboxTables.find(
|
|
(vbt) => vbt.viewboxId === viewbox.id
|
|
)
|
|
if (viewboxTable && (viewbox.filter_pk === '0' || !viewbox.filter_pk))
|
|
resolve()
|
|
|
|
this.sasStoreService
|
|
.viewData(libDataset, parseInt(viewbox.filter_pk || '0'))
|
|
.then((res: any) => {
|
|
if (!viewboxTable) {
|
|
viewboxTable = {
|
|
viewboxId: viewbox.id,
|
|
viewboxLibDataset: libDataset,
|
|
hotTable: cloneDeep(this.hotTableDefault)
|
|
}
|
|
} else {
|
|
existingViewboxTable = true
|
|
}
|
|
|
|
viewboxTable.hotTable.data = res.viewdata
|
|
viewboxTable.hotTable.$dataformats = res.$viewdata
|
|
viewboxTable.hotTable.cols = res.cols
|
|
|
|
mergeColsRules(viewboxTable.hotTable.cols, [], res.$viewdata)
|
|
|
|
let columns: any[] = []
|
|
let colArr: string[] = []
|
|
|
|
for (let key in res.viewdata[0]) {
|
|
if (key) {
|
|
colArr.push(key)
|
|
}
|
|
}
|
|
|
|
for (let index = 0; index < colArr.length; index++) {
|
|
columns.push({ data: colArr[index] })
|
|
}
|
|
|
|
viewboxTable.hotTable.headerPks = cloneDeep(
|
|
res.sasparams[0].PK_FIELDS.split(' ')
|
|
)
|
|
viewboxTable.hotTable.allColHeaders = colArr.filter(
|
|
(col) => !viewboxTable!.hotTable.headerPks.includes(col)
|
|
)
|
|
viewboxTable.hotTable.colHeadersHidden = cloneDeep(
|
|
viewboxTable.hotTable.allColHeaders
|
|
)
|
|
viewboxTable.hotTable.colHeadersVisible = colArr.filter((col) =>
|
|
viewboxTable!.hotTable.headerPks.includes(col)
|
|
)
|
|
|
|
viewboxTable.hotTable.colHeaders = colArr
|
|
viewboxTable.hotTable.columns = columns
|
|
|
|
if (viewbox.columns && viewbox.columns.length > 0) {
|
|
viewboxTable.hotTable.manualColumnMove = viewbox.columns
|
|
|
|
viewbox.columns?.map((col: number, index: number) => {
|
|
const colProp = colArr[col]
|
|
const hiddenColIndex =
|
|
viewboxTable!.hotTable.colHeadersHidden.indexOf(colProp)
|
|
|
|
if (hiddenColIndex > -1) {
|
|
viewboxTable!.hotTable.colHeadersHidden.splice(
|
|
hiddenColIndex,
|
|
1
|
|
)
|
|
viewboxTable!.hotTable.colHeadersVisible[index] = colProp
|
|
}
|
|
})
|
|
} else {
|
|
viewboxTable.hotTable.colHeadersVisible.push(
|
|
...viewboxTable.hotTable.colHeadersHidden.splice(0, 10)
|
|
)
|
|
}
|
|
|
|
viewboxTable.hotTable.colHeadersVisible =
|
|
viewboxTable.hotTable.colHeadersVisible.filter((x) => x) //remove empty slots
|
|
|
|
if (!existingViewboxTable) this.viewboxTables.push(viewboxTable)
|
|
|
|
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 || [],
|
|
viewbox.id
|
|
)
|
|
|
|
// HOT Settings are bound in HTML but some settings due to timing issues
|
|
// requires to be updated after the HOT is instanced
|
|
// Use a longer timeout to ensure the HOT component is fully initialized
|
|
setTimeout(() => {
|
|
const hotInstance = this.getViewboxHotInstance(viewbox.id)
|
|
|
|
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
|
|
|
|
if (isPKCol) th.classList.add('primaryKeyHeaderStyle')
|
|
// Dark mode
|
|
th.classList.add(globals.handsontable.darkTableHeaderClass)
|
|
}
|
|
})
|
|
hotInstance.render()
|
|
}
|
|
|
|
if (this.selectedViewbox) {
|
|
this.resetSelectedViewbox(viewbox)
|
|
}
|
|
}, 500)
|
|
}, 100)
|
|
|
|
resolve()
|
|
})
|
|
.catch((err: any) => {
|
|
this.loggerService.error(err)
|
|
|
|
reject()
|
|
})
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Reloads SAS data for each viewbox that exists
|
|
* @param viewboxes viewboxes array
|
|
*/
|
|
reLoadViewboxtables(viewboxes: Viewbox[]) {
|
|
viewboxes.map((viewbox: Viewbox) => {
|
|
const libDataset = `${viewbox.library}.${viewbox.table}`
|
|
viewbox.loadingData = true
|
|
|
|
this.loadData(libDataset, viewbox).then(() => {
|
|
viewbox.loadingData = false
|
|
})
|
|
})
|
|
}
|
|
|
|
// HOT cols max width
|
|
maxWidthCheker(width: any, col: any) {
|
|
if (width > 200) return 200
|
|
else return width
|
|
}
|
|
|
|
/**
|
|
* Used to pair `Viewbox` with it's data
|
|
* Which is stored in different array - ViewboxTables
|
|
* @param viewbox
|
|
*/
|
|
getViewboxTableIndex(viewbox: Viewbox): number {
|
|
const index = this.viewboxTables.findIndex(
|
|
(x) => x.viewboxId === viewbox.id
|
|
)
|
|
|
|
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
|
|
* @param target
|
|
*/
|
|
resize(
|
|
dragHandle: HTMLElement,
|
|
target: HTMLElement
|
|
): { width: number; height: number } {
|
|
const dragRect = dragHandle.getBoundingClientRect()
|
|
const targetRect = target.getBoundingClientRect()
|
|
|
|
const width = dragRect.left - targetRect.left + dragRect.width
|
|
const height = dragRect.top - targetRect.top + dragRect.height
|
|
|
|
target.style.width = width + 'px'
|
|
target.style.height = height + 'px'
|
|
|
|
this.setAllHandleTransform()
|
|
|
|
this.helperService.debounceCall(1000, () => {
|
|
this.viewboxChanged()
|
|
this.eventService.dispatchEvent('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)
|
|
})
|
|
})
|
|
|
|
return {
|
|
width,
|
|
height
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calls `resize()` outside of angular zone
|
|
* Running functions via #runOutsideAngular allows you to escape Angular's
|
|
* zone and do work that doesn't trigger Angular change-detection or is subject
|
|
* to Angular's error handling.
|
|
* @param dragHandle
|
|
* @param resizeBox
|
|
* @param viewbox
|
|
* @param $event
|
|
*/
|
|
dragMove(
|
|
dragHandle: HTMLElement,
|
|
resizeBox: any,
|
|
viewbox: Viewbox,
|
|
$event: CdkDragMove<any>
|
|
) {
|
|
this.ngZone.runOutsideAngular(() => {
|
|
const newDimnesion = this.resize(dragHandle, resizeBox)
|
|
|
|
viewbox.width = newDimnesion.width
|
|
viewbox.height = newDimnesion.height
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Sets the 'resize' handle in the correct corner position of the all boxes
|
|
*/
|
|
setAllHandleTransform() {
|
|
this.resizeBoxQuery.forEach((resizeBox: ElementRef) => {
|
|
const rect = resizeBox.nativeElement.getBoundingClientRect()
|
|
const handleId = `handle_${resizeBox.nativeElement.id}`
|
|
|
|
const dragHandleCorner = this.dragHandleCornerQuery.find(
|
|
(el, i) => el.nativeElement.id === handleId
|
|
)
|
|
this.setHandleTransform(dragHandleCorner?.nativeElement, rect, 'both')
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Sets the 'resize' handle in the correct corner position of the box
|
|
*/
|
|
setHandleTransform(
|
|
dragHandle: HTMLElement,
|
|
targetRect: ClientRect | DOMRect,
|
|
position: 'x' | 'y' | 'both'
|
|
) {
|
|
const dragRect = dragHandle.getBoundingClientRect()
|
|
let translateX = targetRect.width - dragRect.width
|
|
let translateY = targetRect.height - dragRect.height
|
|
|
|
//Fine tune
|
|
translateX += 5
|
|
translateY += 5
|
|
|
|
if (position === 'x') {
|
|
dragHandle.style.transform = `translate(${translateX}px, 0)`
|
|
}
|
|
|
|
if (position === 'y') {
|
|
dragHandle.style.transform = `translate(0, ${translateY}px)`
|
|
}
|
|
|
|
if (position === 'both') {
|
|
dragHandle.style.transform = `translate(${translateX}px, ${translateY}px)`
|
|
}
|
|
}
|
|
|
|
/**
|
|
* When clicked on Viewbox, it will focus it
|
|
* Focused Viewbox is always bring to top
|
|
* @param viewbox
|
|
*/
|
|
focusViewbox(viewbox: Viewbox) {
|
|
this.viewboxes.map((vbox) => {
|
|
vbox.focused = false
|
|
})
|
|
|
|
viewbox.focused = true
|
|
}
|
|
|
|
/**
|
|
* On drag end Viewbox position is updated
|
|
* As well as the URL
|
|
* @param event
|
|
* @param viewbox
|
|
*/
|
|
viewboxDragEnded(event: CdkDragEnd, viewbox: Viewbox) {
|
|
let element = event.source.getRootElement()
|
|
let boundingClientRect = element.getBoundingClientRect()
|
|
|
|
viewbox.x = boundingClientRect.left
|
|
viewbox.y = boundingClientRect.top
|
|
|
|
this.viewboxChanged()
|
|
}
|
|
|
|
/**
|
|
* Snap to grid calculates the best grid possible
|
|
* for given screen width and height
|
|
* Configurable options are (in PX):
|
|
* gap - gaps between boxes and left/right sides
|
|
* topOffset - clearance on the top
|
|
* bottomOffset - clearance on the bottom
|
|
*/
|
|
snapToGrid() {
|
|
const windowWidth = window.innerWidth
|
|
const windowHeight = window.innerHeight
|
|
|
|
const gap = 5 //px configurable
|
|
const topOffset = 250 //px configurable
|
|
const bottomOffset = 60 //px configurable
|
|
|
|
const elementsInTopRow = Math.ceil(this.viewboxes.length / 2)
|
|
const elementsInBottomRow = Math.floor(this.viewboxes.length / 2)
|
|
const noOfGapsTop = elementsInTopRow + 1
|
|
const noOfGapsBottom = elementsInBottomRow + 1
|
|
const viewboxWidthTop = (windowWidth - gap * noOfGapsTop) / elementsInTopRow
|
|
const viewboxWidthBottom =
|
|
(windowWidth - gap * noOfGapsBottom) / elementsInBottomRow
|
|
|
|
const viewboxHeight = (windowHeight - topOffset - bottomOffset) / 2
|
|
|
|
let x = 0
|
|
let y = topOffset
|
|
let height = viewboxHeight
|
|
let viewbox_i = 0
|
|
let row_i = 0
|
|
|
|
for (let i = 0; i < this.viewboxes.length; i++) {
|
|
let viewbox = this.viewboxes[i]
|
|
let topRow = !(i > elementsInTopRow - 1)
|
|
const width = topRow ? viewboxWidthTop : viewboxWidthBottom
|
|
if (!topRow && row_i === 0) {
|
|
viewbox_i = 0
|
|
row_i++
|
|
x = 0
|
|
}
|
|
|
|
viewbox.x = gap + x + viewbox_i * (width + gap)
|
|
viewbox.y = y + row_i * (height + gap)
|
|
viewbox.width = width
|
|
viewbox.height = height
|
|
|
|
viewbox_i++
|
|
}
|
|
|
|
this.viewboxChanged()
|
|
|
|
setTimeout(() => {
|
|
this.setAllHandleTransform()
|
|
|
|
// Refresh all tables after snap to grid
|
|
this.viewboxes.forEach((viewbox) => {
|
|
// Settings will include correct height when accessed
|
|
this.refreshTableAfterResize(viewbox)
|
|
})
|
|
})
|
|
}
|
|
|
|
minimizeAll() {
|
|
this.viewboxes.forEach((viewbox: Viewbox) => {
|
|
viewbox.minimized = true
|
|
})
|
|
|
|
this.viewboxChanged()
|
|
}
|
|
|
|
restoreAll() {
|
|
this.viewboxes.forEach((viewbox: Viewbox) => {
|
|
viewbox.minimized = false
|
|
})
|
|
|
|
this.viewboxChanged()
|
|
}
|
|
|
|
/**
|
|
* Resets Viewbox to default position (top right corner)
|
|
* @param viewbox
|
|
*/
|
|
resetPosSize(viewbox: Viewbox) {
|
|
viewbox.x = window.innerWidth - this.defaultConfig.width
|
|
viewbox.y = this.defaultConfig.y
|
|
viewbox.width = this.defaultConfig.width
|
|
viewbox.height = this.defaultConfig.height
|
|
this.viewboxChanged()
|
|
}
|
|
|
|
minimize(viewbox: Viewbox) {
|
|
viewbox.minimized = true
|
|
|
|
this.viewboxChanged()
|
|
}
|
|
|
|
restore(viewbox: Viewbox) {
|
|
viewbox.minimized = false
|
|
|
|
this.viewboxChanged()
|
|
|
|
// Refresh table after restoring
|
|
setTimeout(() => {
|
|
// Settings will include correct height when accessed
|
|
this.refreshTableAfterResize(viewbox)
|
|
}, 100)
|
|
}
|
|
|
|
collapse(viewbox: Viewbox) {
|
|
viewbox.collapsed = true
|
|
this.viewboxChanged()
|
|
}
|
|
|
|
expand(viewbox: Viewbox) {
|
|
viewbox.collapsed = false
|
|
this.viewboxChanged()
|
|
|
|
// Refresh table after expanding
|
|
setTimeout(() => {
|
|
// Settings will include correct height when accessed
|
|
this.refreshTableAfterResize(viewbox)
|
|
}, 100)
|
|
}
|
|
|
|
/**
|
|
* Close Viewbox and remove it's data stored in `paired ViewboxTable array`
|
|
* @param viewbox
|
|
*/
|
|
close(viewbox: Viewbox) {
|
|
const index = this.viewboxes.findIndex((vb) => vb.id === viewbox.id)
|
|
|
|
const viewtableIndex = this.viewboxTables.findIndex(
|
|
(vbt) => vbt.viewboxId === viewbox.id
|
|
)
|
|
|
|
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()
|
|
}
|
|
|
|
globals.viewboxes[viewbox.id] = this.helperService.deepClone(initFilter)
|
|
|
|
this.viewboxChanged()
|
|
}
|
|
|
|
/**
|
|
* Selects Viewbox in the Viewbox Manager for the columns to be configurated
|
|
* @param viewbox
|
|
*/
|
|
selectViewbox(viewbox: Viewbox) {
|
|
if (
|
|
this.selectedViewboxTable === undefined &&
|
|
this.selectedViewbox === undefined
|
|
) {
|
|
this.resetSelectedViewbox(viewbox)
|
|
} else {
|
|
if (viewbox.id === this.selectedViewbox?.id) {
|
|
this.unsetSelectedViewbox()
|
|
} else {
|
|
this.resetSelectedViewbox(viewbox)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Column drop called after column dragged and dropped
|
|
* for Viewbox column to be configurated
|
|
* @param event
|
|
* @returns
|
|
*/
|
|
columnsDrop(event: CdkDragDrop<string[]>) {
|
|
if (!this.selectedViewboxTable?.hotTable.colHeadersHidden) return
|
|
|
|
if (event.previousContainer === event.container) {
|
|
moveItemInArray(
|
|
event.container.data,
|
|
event.previousIndex,
|
|
event.currentIndex
|
|
)
|
|
} else {
|
|
transferArrayItem(
|
|
event.previousContainer.data,
|
|
event.container.data,
|
|
event.previousIndex,
|
|
event.currentIndex
|
|
)
|
|
}
|
|
|
|
if (
|
|
this.selectedViewboxTable.hotTable &&
|
|
typeof this.selectedViewboxTable.hotTable.colHeaders === 'object'
|
|
) {
|
|
const colProp = event.item.data
|
|
const finalIndex = event.currentIndex
|
|
|
|
this.updateColumnOrderHot(
|
|
colProp,
|
|
finalIndex,
|
|
this.selectedViewboxTable.viewboxId
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Viewbox Manager searching for columns to be added
|
|
* @param inputRef Input element
|
|
* @param value
|
|
* @param columns
|
|
*/
|
|
onColsearchChange(
|
|
inputRef: AutocompleteComponent,
|
|
value: string,
|
|
columns: string[]
|
|
) {
|
|
const index = columns.indexOf(value)
|
|
|
|
columns.splice(index, 1)
|
|
inputRef.value = ''
|
|
|
|
if (this.selectedViewboxTable?.hotTable) {
|
|
this.selectedViewboxTable.hotTable.colHeadersVisible.push(value)
|
|
|
|
this.updateHotColumns(
|
|
this.selectedViewboxTable?.hotTable.colHeadersHidden,
|
|
this.selectedViewboxTable.viewboxId
|
|
)
|
|
|
|
this.updateColumnOrderHot(
|
|
value,
|
|
this.selectedViewboxTable.hotTable.colHeadersVisible.length - 1,
|
|
this.selectedViewboxTable.viewboxId
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Viewbox Manager removing the column
|
|
* @param column
|
|
*/
|
|
onColRemove(column: string) {
|
|
if (this.selectedViewboxTable?.hotTable) {
|
|
const index =
|
|
this.selectedViewboxTable.hotTable.colHeadersVisible.indexOf(column)
|
|
this.selectedViewboxTable.hotTable.colHeadersVisible.splice(index, 1)
|
|
this.selectedViewboxTable.hotTable.colHeadersHidden.push(column)
|
|
|
|
this.updateHotColumns(
|
|
this.selectedViewboxTable?.hotTable.colHeadersHidden,
|
|
this.selectedViewboxTable.viewboxId
|
|
)
|
|
|
|
this.updateColumnOrderHot(
|
|
column,
|
|
this.selectedViewboxTable.hotTable.colHeadersVisible.length,
|
|
this.selectedViewboxTable.viewboxId
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Re setting the viewbox from parameter to selected viewbox
|
|
* And sets the correct data for that viewbox
|
|
* @param viewbox
|
|
*/
|
|
resetSelectedViewbox(viewbox: Viewbox) {
|
|
this.selectedViewbox = viewbox
|
|
this.selectedViewboxTable = this.viewboxTables.find(
|
|
(vbt) => vbt.viewboxId === viewbox.id
|
|
)
|
|
}
|
|
|
|
unsetSelectedViewbox() {
|
|
this.selectedViewbox = undefined
|
|
this.selectedViewboxTable = undefined
|
|
}
|
|
|
|
stopPropagation(event: any) {
|
|
event.stopPropagation()
|
|
}
|
|
|
|
/**
|
|
* Opens filter dialog for given Viewbox
|
|
* @param viewbox
|
|
*/
|
|
openFilter(viewbox: Viewbox) {
|
|
this.selectViewbox(viewbox)
|
|
|
|
const viewboxTable = this.viewboxTables[this.getViewboxTableIndex(viewbox)]
|
|
|
|
this.filterLibds = `${viewbox.library}.${viewbox.table}`
|
|
this.filteringViewbox = viewbox
|
|
|
|
this.filter = true
|
|
this.cdf.detectChanges()
|
|
|
|
this.sasStoreService.setQueryVariables(
|
|
this.filterLibds,
|
|
viewboxTable.hotTable.cols
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Opens table edit in new tab
|
|
* @param viewbox
|
|
*/
|
|
openTableEdit(viewbox: Viewbox) {
|
|
const libDataset = viewbox.library + '.' + viewbox.table
|
|
let url = location.href.slice(0, location.href.indexOf('#'))
|
|
url = `${url}#/editor/${libDataset}`
|
|
|
|
window.open(url, '_blank')
|
|
}
|
|
|
|
/**
|
|
* Resets the data in filter dialog
|
|
* It will also reset the filtering in the given Viewbox
|
|
*/
|
|
resetFilter() {
|
|
if (this.filteringViewbox) {
|
|
this.filteringViewbox.filter_pk = '0'
|
|
this.reloadTableData(this.filteringViewbox)
|
|
this.filter = false
|
|
this.viewboxChanged()
|
|
globals.viewboxes[this.filteringViewbox.id] =
|
|
this.helperService.deepClone(initFilter)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sending filtering request for given Viewbox
|
|
*/
|
|
sendClause() {
|
|
this.filterLoading = true
|
|
let nullVariableArr = []
|
|
let emptyVariablesArr = []
|
|
|
|
// to check number of empty clauses
|
|
if (typeof this.clauses === 'undefined') {
|
|
this.nullVariables = true
|
|
this.filterLoading = false
|
|
return
|
|
} else {
|
|
let query = this.clauses.queryObj
|
|
|
|
for (let index = 0; index < query.length; index++) {
|
|
const el = query[index].elements
|
|
nullVariableArr = el.filter(function (item: any) {
|
|
return item.variable === null
|
|
})
|
|
if (nullVariableArr.length) {
|
|
emptyVariablesArr.push(el)
|
|
}
|
|
}
|
|
}
|
|
|
|
if (emptyVariablesArr.length) {
|
|
this.nullVariables = true
|
|
this.filterLoading = false
|
|
return
|
|
} else {
|
|
try {
|
|
if (this.clauses !== undefined && this.filterLibds) {
|
|
const filterQuery: FilterQuery = {
|
|
groupLogic: this.clauses.groupLogic,
|
|
filterGroups: []
|
|
}
|
|
this.clauses.queryObj.forEach((group: any) => {
|
|
const filterGroup: FilterGroup = {
|
|
filterClauses: []
|
|
}
|
|
group.elements.forEach((clause: any) => {
|
|
filterGroup.filterClauses.push(
|
|
this.helperService.deepClone(clause)
|
|
)
|
|
})
|
|
filterGroup.clauseLogic = group.clauseLogic
|
|
filterQuery.filterGroups.push(
|
|
this.helperService.deepClone(filterGroup)
|
|
)
|
|
})
|
|
|
|
const filterQueryClauseTable =
|
|
this.sasStoreService.createFilterQueryTable(filterQuery)
|
|
|
|
this.sasStoreService
|
|
.saveQuery(this.filterLibds, filterQueryClauseTable)
|
|
.then((res: any) => {
|
|
const id = res.result[0].FILTER_RK
|
|
const table = res.result[0].FILTER_TABLE
|
|
|
|
this.filteringViewbox!.filter_pk = id
|
|
this.loadData(this.filterLibds!, this.filteringViewbox!).then(
|
|
() => {
|
|
this.filter = false
|
|
this.filterLoading = false
|
|
}
|
|
)
|
|
|
|
this.viewboxChanged()
|
|
})
|
|
.catch((err: any) => {
|
|
this.filterLoading = false
|
|
})
|
|
}
|
|
} catch (error: any) {
|
|
this.filterLoading = false
|
|
}
|
|
}
|
|
}
|
|
|
|
async searchTable(inputElement: any, viewbox: Viewbox) {
|
|
viewbox.searchLoading = true
|
|
|
|
let searchValue = inputElement.value
|
|
|
|
let libDataset = viewbox.library + '.' + viewbox.table
|
|
let filter_pk = parseInt(viewbox.filter_pk || '0')
|
|
|
|
const viewboxTable = this.viewboxTables.find(
|
|
(vbt) => vbt.viewboxId === viewbox.id
|
|
)
|
|
|
|
if (!viewboxTable) return
|
|
|
|
await this.sasStoreService
|
|
.viewDataSearch(searchValue, viewbox.searchNumeric, libDataset, filter_pk)
|
|
.then((res: any) => {
|
|
if (!res.sasparams && !res.viewData) {
|
|
viewbox.searchLoading = true
|
|
return
|
|
}
|
|
|
|
viewboxTable.hotTable.data = res.viewdata
|
|
|
|
// Update settings with new data
|
|
this.createViewboxTableSettings(viewbox)
|
|
})
|
|
.catch((err: any) => {
|
|
this.loggerService.error(err)
|
|
})
|
|
|
|
viewbox.searchLoading = false
|
|
}
|
|
|
|
async reloadTableData(viewbox: Viewbox) {
|
|
const libDataset = `${viewbox.library}.${viewbox.table}`
|
|
|
|
viewbox.loadingData = true
|
|
|
|
await this.reloadData(libDataset, viewbox)
|
|
|
|
viewbox.loadingData = false
|
|
this.eventService.dispatchEvent('resize') //Force HOT refresh
|
|
}
|
|
|
|
/**
|
|
* Updates the HOT columns Order and Visibility
|
|
* @param hiddenColProps Array of indexes of columns to be hidden
|
|
* @param viewboxId Viewbox ID for which to apply
|
|
*/
|
|
private updateHotColumns(hiddenColProps: string[], viewboxId: number) {
|
|
this.updateHiddenColumnsHot(hiddenColProps, viewboxId)
|
|
|
|
this.setColumnOrder(viewboxId)
|
|
|
|
// Settings will be regenerated when accessed
|
|
}
|
|
|
|
/**
|
|
* HOT Columns ordering
|
|
* @param colProp Column name
|
|
* @param finalIndex Index of where to position it
|
|
* @param viewboxId
|
|
*/
|
|
private updateColumnOrderHot(
|
|
colProp: string,
|
|
finalIndex: number,
|
|
viewboxId: number
|
|
) {
|
|
const hotInstance = this.getViewboxHotInstance(viewboxId)
|
|
|
|
if (hotInstance) {
|
|
const column = hotInstance.propToCol(colProp) as number
|
|
const plugin = hotInstance.getPlugin('manualColumnMove')
|
|
|
|
plugin.moveColumn(column, finalIndex)
|
|
hotInstance.render()
|
|
|
|
this.setColumnOrder(viewboxId)
|
|
}
|
|
}
|
|
|
|
public tableEditExists(viewbox: Viewbox) {
|
|
const editTables = globals.editor.libsAndTables
|
|
const library = viewbox.library
|
|
const table = viewbox.table
|
|
|
|
// If this line is undefined, that means startupservice failed or similar.
|
|
if (!editTables[library]) return false
|
|
|
|
return editTables[library].includes(table)
|
|
}
|
|
|
|
private setColumnOrder(viewboxId: number) {
|
|
const viewbox = this.viewboxes.find((vb) => vb.id === viewboxId)
|
|
|
|
if (viewbox) {
|
|
const columnsOrder = this.createColumnOrder(viewboxId)
|
|
viewbox.columns = columnsOrder.length > 0 ? columnsOrder : viewbox.columns
|
|
}
|
|
|
|
this.viewboxChanged()
|
|
}
|
|
|
|
/**
|
|
* Creating column order in such format to be encoded in URL
|
|
* @param viewboxId
|
|
*/
|
|
private createColumnOrder(viewboxId: number): number[] {
|
|
const hotInstance = this.getViewboxHotInstance(viewboxId)
|
|
if (!hotInstance) return []
|
|
|
|
const hotCols = hotInstance.getColHeader() as string[]
|
|
const sasCols = this.selectedViewboxTable?.hotTable.colHeaders as string[]
|
|
|
|
if (!sasCols) return []
|
|
|
|
const columnsVisibleLength: number =
|
|
this.selectedViewboxTable?.hotTable?.colHeadersVisible.length || 5 //if unexpected happens limit will Be 5 columns
|
|
const columnOrder: number[] = []
|
|
|
|
hotCols.map((hotCol: string, index: number) => {
|
|
if (index < columnsVisibleLength) {
|
|
const indexofSasCol = sasCols.indexOf(hotCol)
|
|
|
|
if (indexofSasCol > -1) columnOrder.push(indexofSasCol)
|
|
}
|
|
})
|
|
|
|
return columnOrder
|
|
}
|
|
|
|
private updateHiddenColumnsHot(colProps: string[], viewboxId: number) {
|
|
const hotInstance = this.getViewboxHotInstance(viewboxId)
|
|
|
|
if (hotInstance) {
|
|
const columns = colProps.map((prop: string) => {
|
|
return hotInstance.propToCol(prop) as number
|
|
})
|
|
|
|
hotInstance.updateSettings({
|
|
hiddenColumns: {
|
|
columns: columns
|
|
}
|
|
})
|
|
|
|
hotInstance.render()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculate available height for Handsontable
|
|
* @param viewbox The viewbox to calculate height for
|
|
* @returns Available height in pixels
|
|
*/
|
|
calculateTableHeight(viewbox: Viewbox): number {
|
|
// Calculate the exact height of the content div
|
|
const dragHandleHeight = 20
|
|
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
|
|
}
|
|
|
|
/**
|
|
* Refresh Handsontable instance after resize
|
|
* @param viewbox The viewbox to refresh
|
|
*/
|
|
refreshTableAfterResize(viewbox: Viewbox): void {
|
|
const hotInstance = this.getViewboxHotInstance(viewbox.id)
|
|
if (hotInstance) {
|
|
// Force the table to recalculate its dimensions
|
|
setTimeout(() => {
|
|
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)
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param viewboxId
|
|
* @returns HOT Instance from the given Viewbox
|
|
*/
|
|
private getViewboxHotInstance(viewboxId?: number): Handsontable | undefined {
|
|
if (!viewboxId || !this.hotTableComponents) return
|
|
|
|
// 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
|
|
|
|
// 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
|
|
}
|
|
|
|
/**
|
|
* Called after any Viewbox change that needs to be stored to URL
|
|
* It does the data encoding and storing to URL
|
|
*/
|
|
private viewboxChanged() {
|
|
let queryParams: any
|
|
const urlData = this.encodeUrlData(this.viewboxes)
|
|
|
|
if (urlData.length > 0) {
|
|
queryParams = {
|
|
viewboxes: urlData
|
|
}
|
|
}
|
|
|
|
this.router.navigate([], {
|
|
relativeTo: this.activatedRoute,
|
|
queryParams
|
|
})
|
|
|
|
this.prepareFilterCache()
|
|
}
|
|
|
|
/**
|
|
* Prepare the init values to `globals` for the filtering
|
|
* that will be used in caching values later
|
|
*/
|
|
private prepareFilterCache() {
|
|
for (let viewbox of this.viewboxes) {
|
|
if (!globals.viewboxes[viewbox.id])
|
|
globals.viewboxes[viewbox.id] = this.helperService.deepClone(initFilter)
|
|
|
|
if (viewbox.query && viewbox.query.length > 0) {
|
|
const viewboxTable = this.viewboxTables.find(
|
|
(vbt) => vbt.viewboxId === viewbox.id
|
|
)
|
|
const globalsPath = `viewboxes.${viewbox.id}`
|
|
|
|
globals.viewboxes[viewbox.id].filter.query = viewbox.query
|
|
globals.viewboxes[viewbox.id].filter.libds =
|
|
viewbox.library + '.' + viewbox.table
|
|
this.sasStoreService.initializeGlobalFilterClause(
|
|
globalsPath,
|
|
viewboxTable?.hotTable.cols
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* It will encode/inject viewboxes data from URL
|
|
* Url data pattern:
|
|
*
|
|
* {id}-{library}-{table}-{width}-{height}-{x}-{y}-{collapsed}-{minimized}-{filter_pk}-{columns}
|
|
* 1-library-table-100-100-10-10-1-1;library2-table2-100-100-10-10-0-0-3-0123456
|
|
*
|
|
* ; <- single viewbox separation symbol
|
|
*/
|
|
private encodeUrlData(viewboxes: Viewbox[]): string {
|
|
let urlData: string = ''
|
|
|
|
viewboxes.map((viewbox: Viewbox, index: number) => {
|
|
urlData += `${viewbox.id}-${viewbox.library}-${viewbox.table}-${
|
|
viewbox.width
|
|
}-${viewbox.height}-${viewbox.x}-${viewbox.y}-${
|
|
viewbox.collapsed ? 1 : 0
|
|
}-${viewbox.minimized ? 1 : 0}-${viewbox.filter_pk || 0}${
|
|
viewbox.columns && viewbox.columns.length > 0
|
|
? '-' + viewbox.columns?.join(',')
|
|
: ''
|
|
}`
|
|
|
|
if (index !== viewboxes.length - 1) urlData += ';'
|
|
})
|
|
|
|
return urlData
|
|
}
|
|
|
|
/**
|
|
* It will decode/parse viewboxes data from URL
|
|
* Url data pattern:
|
|
*
|
|
* {id}-{library}-{table}-{width}-{height}-{x}-{y}-{collapsed}-{minimized}-{filter_pk}-{columns}
|
|
* 1-library-table-100-100-10-10-1-1;library2-table2-100-100-10-10-0-0-3-0123456
|
|
*
|
|
* ; <- single viewbox separation symbol
|
|
*/
|
|
private decodeUrlData(urlData: string): Viewbox[] {
|
|
const urlDataMap = {
|
|
id: 0,
|
|
library: 1,
|
|
table: 2,
|
|
width: 3,
|
|
height: 4,
|
|
x: 5,
|
|
y: 6,
|
|
collapsed: 7,
|
|
minimized: 8,
|
|
filter_pk: 9,
|
|
columns: 10
|
|
}
|
|
|
|
let viewboxes: Viewbox[] = []
|
|
|
|
const separatedViewboxes = urlData.split(';')
|
|
|
|
separatedViewboxes.map((viewboxString: string) => {
|
|
const viewboxDataArr = viewboxString.split('-')
|
|
|
|
viewboxes.push({
|
|
id: parseInt(viewboxDataArr[urlDataMap.id]),
|
|
library: viewboxDataArr[urlDataMap.library],
|
|
table: viewboxDataArr[urlDataMap.table],
|
|
width: parseInt(viewboxDataArr[urlDataMap.width]),
|
|
height: parseInt(viewboxDataArr[urlDataMap.height]),
|
|
x: parseInt(viewboxDataArr[urlDataMap.x]),
|
|
y: parseInt(viewboxDataArr[urlDataMap.y]),
|
|
collapsed: !!parseInt(viewboxDataArr[urlDataMap.collapsed]),
|
|
minimized: !!parseInt(viewboxDataArr[urlDataMap.minimized]),
|
|
columns:
|
|
viewboxDataArr[urlDataMap.columns]
|
|
?.split(',')
|
|
.map((x) => parseInt(x)) || [],
|
|
filter_pk: viewboxDataArr[urlDataMap.filter_pk]
|
|
})
|
|
})
|
|
|
|
return viewboxes
|
|
}
|
|
|
|
ngOnDestroy(): void {
|
|
this._query?.unsubscribe()
|
|
}
|
|
}
|