/**/ import type { ColGroupDef, GridApi, GridOptions, GridReadyEvent, ICellRendererParams, RowSelectionOptions, ValueSetterParams, } from "ag-grid-enterprise"; import type { Component } from "vue"; import { computed, shallowRef, watch } from "vue"; import { autoSizeRowNumberColumn, makeRowNumberColDef, PlAgOverlayLoading, type PlAgOverlayLoadingParams, } from "../components/PlAgDataTable"; import { PlAgOverlayNoRows } from "../components/PlAgDataTable"; import { createAgGridColDef, type ColDefExtended } from "./createAgGridColDef"; import { whenever } from "@vueuse/core"; import { PlAgCellFile } from "../components/PlAgCellFile"; import { PlAgChartStackedBarCell } from "../components/PlAgChartStackedBarCell"; import { PlAgChartHistogramCell } from "../components/PlAgChartHistogramCell"; import type { ImportFileHandle } from "@platforma-sdk/model"; import type { ImportProgress } from "@platforma-sdk/model"; import { PlAgCellStatusTag } from "../components/PlAgCellStatusTag"; import { AgGridTheme } from "./AgGridTheme"; interface GridOptionsExtended extends Omit< GridOptions, "columnDefs" | "loadingOverlayComponentParams" > { /** * Array of Column / Column Group definitions. */ columnDefs?: (ColDefExtended | ColGroupDef)[] | null; /** * Show row numbers column */ rowNumbersColumn?: boolean; /** * Loading overlay text */ loadingText?: string; /** * Not ready overlay (Data is not computed). Takes priority over "loading" */ notReady?: boolean; /** * "Data is not computed" by default */ notReadyText?: string; /** * Override standard 'Empty' text for the "no rows" overlay */ noRowsText?: string; /** * @deprecated Use loading, notReady, loadingText instead */ loadingOverlayComponentParams?: never; } // @TODO (super simple builder for now) class Builder { #options: GridOptionsExtended = {}; public options(options: GridOptionsExtended) { this.#options = Object.assign({}, this.#options, options); return this; } private get columnDefs() { return this.#options.columnDefs ?? []; } /** * Set default column definition * @param def - column definition * @returns this */ public setDefaultColDef(def: ColDefExtended) { this.#options.defaultColDef = def; return this; } /** * Show loading overlay * @param loading * @returns this */ public setLoading(loading?: boolean) { this.#options.loading = loading; return this; } /** * Set loading overlay custom text (default is "Loading") * @param loadingText * @returns this */ public setLoadingText(loadingText?: string) { this.#options.loadingText = loadingText; return this; } /** * Show "not ready overlay * @param notReady * @returns this */ public setNotReady(notReady?: boolean) { this.#options.notReady = notReady; return this; } /** * Set "not ready" text * @param notReadyText * @returns this */ public setNotReadyText(notReadyText?: string) { this.#options.notReadyText = notReadyText; return this; } /** * Set "no rows" text when there are no rows (default is "Empty") * @param noRowsText * @returns this */ public setNoRowsText(noRowsText?: string) { this.#options.noRowsText = noRowsText; return this; } /** * Set row selection options * @param rowSelection * @returns this */ public setRowSelection(rowSelection?: RowSelectionOptions) { this.#options.rowSelection = rowSelection; return this; } /** * Set row data * @param rowData * @returns this */ public setRowData(rowData?: TData[]) { this.#options.rowData = rowData; return this; } /** * Set components * @param components * @returns this */ public setComponents(components?: Record) { this.#options.components = components; return this; } /** * Set an option * @param key - option key * @param value - option value * @returns this */ public setOption>( key: K, value: GridOptionsExtended[K], ) { this.#options[key] = value; return this; } /** * Add an extended column definition * @param def - column definition * @returns this */ public column(def: ColDefExtended) { this.#options.columnDefs = [...this.columnDefs, def]; return this; } /** * Show row numbers column * @param show - show or hide row numbers column * @returns this */ public columnRowNumbers(show: boolean = true) { this.#options.rowNumbersColumn = show; return this; } /** * Add a file input column * @param def - column definition * @param cb - callback to set params for the file input cell renderer * @returns this */ public columnFileInput( def: ColDefExtended & { /** * Allowed file extensions (like ['fastq.gz']) */ extensions?: string[]; /** * The resolveProgress function is an optional input parameter for the component * that allows tracking the file upload progress in real-time. * By passing resolveProgress, you can ensure that the component * displays accurate progress values for each file as they upload. * How to use it in AgGrid * cellRendererParams: { * resolveProgress: (cellData) => { * const progresses = app.progresses; * if (!cellData.value.importFileHandle) return undefined; * else return progresses[cellData.value.importFileHandle]; * } * } */ resolveImportProgress?: ( cellData: ICellRendererParams, ) => ImportProgress | undefined; /** * The resolveFileHandle function is an optional input parameter for the component * that allows tracking the file upload progress in real-time. * By passing resolveFileHandle, you can ensure that the component * displays accurate progress values for each file as they upload. * How to use it in AgGrid * cellRendererParams: { * resolveFileHandle: (cellData) => { * return cellData.value.importFileHandle; * } * } */ resolveImportFileHandle?: ( cellData: ICellRendererParams, ) => ImportFileHandle | undefined; setImportFileHandle?: (d: ValueSetterParams) => void; }, ) { return this.column( Object.assign( { cellRenderer: "PlAgCellFile", headerComponentParams: { type: "File" }, cellStyle: { padding: 0 }, valueSetter: (d: ValueSetterParams) => { def.setImportFileHandle?.(d); return true; }, cellRendererSelector: (cellData: ICellRendererParams) => { return { component: "PlAgCellFile", params: { extensions: def.extensions, value: def.resolveImportFileHandle?.(cellData), resolveProgress: () => { return def.resolveImportProgress?.(cellData); }, }, }; }, }, def, ), ); } public build() { return this.#options; } } // Simple helper to use like column in grid options literal type ColumnFunc = ( def: ColDefExtended, ) => ColDefExtended; /** * Returns a set of Ag Grid options along with a reference to the Ag Grid API. * (This is a fast prototype) * * @example * ```ts * const { gridOptions, gridApi } = useAgGridOptions(() => ({ * // custom grid options here * })); * * // Usage in a template (v-bind is required!) * * ``` */ export function useAgGridOptions( factory: (context: { builder: Builder; column: ColumnFunc; }) => Builder | GridOptionsExtended, ) { const gridApi = shallowRef(); const extOptions = computed(() => { const def: GridOptionsExtended = { theme: AgGridTheme, loadingOverlayComponent: PlAgOverlayLoading, noRowsOverlayComponent: PlAgOverlayNoRows, onGridReady: (e: GridReadyEvent) => { gridApi.value = e.api; autoSizeRowNumberColumn(e.api); // @TODO }, }; const column = (def: ColDefExtended) => { return def; }; const result = factory({ builder: new Builder(), column }); const options = Object.assign({}, def, result instanceof Builder ? result.build() : result); if (options.rowNumbersColumn) { options.columnDefs = [makeRowNumberColDef(), ...(options.columnDefs ?? [])]; } if (options.noRowsText) { options.noRowsOverlayComponentParams = { text: options.noRowsText, }; } if ("loadingOverlayComponentParams" in options) { console.warn( "useAgGridOptions: remove loadingOverlayComponentParams from options, use loading, notReady, loadingText instead", ); } options.loading = options.notReady || options.loading; options.columnDefs = options.columnDefs?.map((it) => createAgGridColDef(it)); // Register all special components options.components = Object.assign({}, options.components ?? {}, { PlAgCellFile, PlAgChartStackedBarCell, PlAgChartHistogramCell, PlAgCellStatusTag, }); return options; }); const gridOptions = computed(() => { const options = extOptions.value; return { ...options, loadingOverlayComponentParams: { variant: options.notReady ? "not-ready" : "loading", notReadyText: options.notReadyText, loadingText: options.loadingText, } satisfies PlAgOverlayLoadingParams, }; }); whenever( () => extOptions.value.rowNumbersColumn, () => { if (gridApi.value) { autoSizeRowNumberColumn(gridApi.value); } }, ); watch( [() => extOptions.value.notReady, () => extOptions.value.loading], ([notReady, loading]) => { const loadingOverlayComponentParams = { variant: notReady ? "not-ready" : "loading", // we probably don't need to update the parameters below notReadyText: extOptions.value.notReadyText, loadingText: extOptions.value.loadingText, } satisfies PlAgOverlayLoadingParams; // Hack to apply loadingOverlayComponentParams gridApi.value?.updateGridOptions({ loading: !loading, loadingOverlayComponentParams, }); gridApi.value?.updateGridOptions({ loading, loadingOverlayComponentParams, }); }, { deep: true, immediate: true }, ); return { gridOptions, gridApi }; }