diff --git a/js/core/gridContextMenu.ts b/js/core/gridContextMenu.ts index 3b0f6a11..844996e4 100644 --- a/js/core/gridContextMenu.ts +++ b/js/core/gridContextMenu.ts @@ -112,6 +112,21 @@ export class FeatherGridContextMenu extends GridContextMenu { this._menu.addItem({ type: 'separator', }); + this._menu.addItem({ + command: FeatherGridContextMenu.CommandID.AlignLeft, + args: args, + }); + this._menu.addItem({ + command: FeatherGridContextMenu.CommandID.AlignCenter, + args: args, + }); + this._menu.addItem({ + command: FeatherGridContextMenu.CommandID.AlignRight, + args: args, + }); + this._menu.addItem({ + type: 'separator', + }); this._menu.addItem({ command: FeatherGridContextMenu.CommandID.OpenFilterByConditionDialog, args: args, @@ -255,6 +270,9 @@ export namespace FeatherGridContextMenu { SaveSelectionAsCsv = 'saveSelectionAsCsv', SaveAllAsCsv = 'saveAllAsCsv', ClearSelection = 'clearSelection', + AlignLeft = 'align:Left', + AlignCenter = 'align:Center', + AlignRight = 'align:Right', } /** diff --git a/js/datagrid.ts b/js/datagrid.ts index 5ab18fec..b8bc56aa 100644 --- a/js/datagrid.ts +++ b/js/datagrid.ts @@ -23,6 +23,7 @@ import { JupyterLuminoPanelWidget, // @ts-ignore needed for ipywidgetx 7.x compatibility JupyterPhosphorPanelWidget, + pack_models, resolvePromisesDict, unpack_models, WidgetModel, @@ -35,9 +36,9 @@ import { ViewBasedJSONModel } from './core/viewbasedjsonmodel'; import { MODULE_NAME, MODULE_VERSION } from './version'; import { CellRendererModel, CellRendererView } from './cellrenderer'; +import { DataSource } from './datasource'; import { FeatherGrid } from './feathergrid'; import { Theme } from './utils'; -import { DataSource } from './datasource'; // Import CSS import '../style/jupyter-widget.css'; @@ -371,7 +372,10 @@ export class DataGridModel extends DOMWidgetModel { static serializers: ISerializers = { ...DOMWidgetModel.serializers, transforms: { deserialize: unpack_models as any }, - renderers: { deserialize: unpack_models as any }, + renderers: { + deserialize: unpack_models as any, + serialize: debug as any, + }, corner_renderer: { deserialize: unpack_models as any }, default_renderer: { deserialize: unpack_models as any }, header_renderer: { deserialize: unpack_models as any }, @@ -396,6 +400,40 @@ export class DataGridModel extends DOMWidgetModel { _view_callbacks: ICallbacks; } +function debug(value: any) { + const upm = debug_pack_models(value); + console.log('first', value); + console.log('upm', upm); + return upm; +} +const IPY_MODEL_ = 'IPY_MODEL_'; + +function debug_pack_models( + value: WidgetModel | Dict | WidgetModel[] | any, + widget?: WidgetModel, +): any | Dict | string | (Dict | string)[] { + if (Array.isArray(value)) { + console.log('array'); + const model_ids: string[] = []; + for (const model of value) { + model_ids.push(pack_models(model, widget)); + } + return model_ids; + } else if (value instanceof WidgetModel) { + console.log('widget model'); + return `${IPY_MODEL_}${value.model_id}`; + } else if (value instanceof Object && typeof value !== 'string') { + console.log('object'); + const packed: { [key: string]: string } = {}; + for (const [key, sub_value] of Object.entries(value)) { + packed[key] = pack_models(sub_value, widget); + } + return packed; + } else { + return value; + } +} + /** * Helper function to convert snake_case strings to camelCase. * Assumes all strings are valid snake_case (all lowercase). @@ -853,14 +891,14 @@ export class DataGridView extends DOMWidgetView { export { BarRendererModel, BarRendererView, - ImageRendererModel, - ImageRendererView, + HtmlRendererModel, + HtmlRendererView, HyperlinkRendererModel, HyperlinkRendererView, + ImageRendererModel, + ImageRendererView, TextRendererModel, TextRendererView, - HtmlRendererModel, - HtmlRendererView, } from './cellrenderer'; export { VegaExprModel, VegaExprView } from './vegaexpr'; diff --git a/js/feathergrid.ts b/js/feathergrid.ts index 71a2b494..78665497 100644 --- a/js/feathergrid.ts +++ b/js/feathergrid.ts @@ -18,9 +18,16 @@ import { ViewBasedJSONModel } from './core/viewbasedjsonmodel'; import { KeyHandler } from './keyhandler'; import { MouseHandler as FeatherGridMouseHandler } from './mousehandler'; import { Theme } from './utils'; +import { MODULE_NAME, MODULE_VERSION } from './version'; import { DataGridModel as BackBoneModel } from './datagrid'; +import { + DOMWidgetModel, + ISerializers, + unpack_models, +} from '@jupyter-widgets/base'; + import '@lumino/default-theme/style/datagrid.css'; import '../style/feathergrid.css'; @@ -107,6 +114,11 @@ const themeVariables: Map = new Map([ ]); export class FeatherGrid extends Widget { + static serializers: ISerializers = { + ...DOMWidgetModel.serializers, + renderers: { deserialize: unpack_models as any }, + }; + constructor(options: DataGrid.IOptions = {}) { super(); this.addClass('ipydatagrid-widget'); @@ -871,6 +883,55 @@ export class FeatherGrid extends Widget { }); } + private async _createTextRendererWidget() { + const model = await this.backboneModel.widget_manager.new_widget({ + model_name: 'TextRendererModel', + model_module: MODULE_NAME, + model_module_version: MODULE_VERSION, + view_name: 'TextRendererView', + view_module: MODULE_NAME, + view_module_version: MODULE_VERSION, + }); + return model; + } + + private async _updateTextAlignment( + columnName: string, + alignment: 'left' | 'center' | 'right', + ) { + const currentRenderers = this.backboneModel.get('renderers'); + const defaultRenderer = this.backboneModel.get('default_renderer'); + const currentRendererForColumn = currentRenderers[columnName]; + + // If there is a renderer for which we can set the alignment, set it + if ( + currentRendererForColumn !== undefined && + 'horizontal_alignment' in currentRendererForColumn.attributes + ) { + currentRendererForColumn.set('horizontal_alignment', alignment); + currentRendererForColumn.save_changes(); + return; + } + + // Assuming it's using the default renderer, we create a new renderer and copy its attributes + // TODO create a renderer of the same type as the default renderer + const model = await this._createTextRendererWidget(); + for (const attr in model.attributes) { + if (attr in defaultRenderer.attributes) { + model.set(attr, defaultRenderer.get(attr)); + } + } + model.set('horizontal_alignment', alignment); + model.save_changes(); + + const updatedRenderers = { ...currentRenderers }; + updatedRenderers[columnName] = model; + + // TODO Find why this is not propagated to Python correctly + this.backboneModel.set('renderers', updatedRenderers); + this.backboneModel.save_changes(); + } + private _createCommandRegistry(): CommandRegistry { const commands = new CommandRegistry(); commands.addCommand(FeatherGridContextMenu.CommandID.SortAscending, { @@ -1034,6 +1095,48 @@ export class FeatherGrid extends Widget { this.grid.selectionModel?.clear(); }, }); + commands.addCommand(FeatherGridContextMenu.CommandID.AlignLeft, { + label: 'Align Left', + mnemonic: -1, + execute: async (args) => { + const commandArgs = args; + const columnName: string = this.dataModel.metadata( + commandArgs.region, + commandArgs.rowIndex, + commandArgs.columnIndex, + )['name']; + + await this._updateTextAlignment(columnName, 'left'); + }, + }); + commands.addCommand(FeatherGridContextMenu.CommandID.AlignCenter, { + label: 'Align Center', + mnemonic: -1, + execute: async (args) => { + const commandArgs = args; + const columnName: string = this.dataModel.metadata( + commandArgs.region, + commandArgs.rowIndex, + commandArgs.columnIndex, + )['name']; + + await this._updateTextAlignment(columnName, 'center'); + }, + }); + commands.addCommand(FeatherGridContextMenu.CommandID.AlignRight, { + label: 'Align Right', + mnemonic: -1, + execute: async (args) => { + const commandArgs = args; + const columnName: string = this.dataModel.metadata( + commandArgs.region, + commandArgs.rowIndex, + commandArgs.columnIndex, + )['name']; + + await this._updateTextAlignment(columnName, 'right'); + }, + }); return commands; }