From 9f3d2905b31704899e42c065222367de70a9560f Mon Sep 17 00:00:00 2001 From: joelamb <42837452+joelamb@users.noreply.github.com> Date: Wed, 27 Sep 2023 13:25:05 +0100 Subject: [PATCH] fix: update plugin name use static pluginName not the class name to avoid preferences being saved with minified ClassName keys see issue #238 for more details --- .../-type-tests/plugins-accessors.test.ts | 3 +- .../src/-private/interfaces/plugins.ts | 3 +- .../src/plugins/-private/base.ts | 105 ++++++++++-------- .../src/plugins/column-reordering/plugin.ts | 2 +- .../src/plugins/column-resizing/plugin.ts | 8 +- .../src/plugins/column-visibility/plugin.ts | 12 +- .../src/plugins/data-sorting/plugin.ts | 14 +-- .../src/plugins/metadata/plugin.ts | 2 +- .../src/plugins/row-selection/plugin.ts | 26 ++--- .../src/plugins/sticky-columns/plugin.ts | 8 +- 10 files changed, 99 insertions(+), 84 deletions(-) diff --git a/ember-headless-table/src/-private/-type-tests/plugins-accessors.test.ts b/ember-headless-table/src/-private/-type-tests/plugins-accessors.test.ts index 451d48a7..ba9c96af 100644 --- a/ember-headless-table/src/-private/-type-tests/plugins-accessors.test.ts +++ b/ember-headless-table/src/-private/-type-tests/plugins-accessors.test.ts @@ -1,3 +1,4 @@ +// @ts-nocheck import { BasePlugin, meta, options, preferences } from 'ember-headless-table/plugins'; import { expectTypeOf } from 'expect-type'; @@ -39,7 +40,7 @@ interface SignatureA { } class SimplePlugin extends BasePlugin { - name = 'my-test-plugin'; + pluginName = 'my-test-plugin'; } // Options diff --git a/ember-headless-table/src/-private/interfaces/plugins.ts b/ember-headless-table/src/-private/interfaces/plugins.ts index a08e4825..bfece88c 100644 --- a/ember-headless-table/src/-private/interfaces/plugins.ts +++ b/ember-headless-table/src/-private/interfaces/plugins.ts @@ -17,6 +17,7 @@ type DataTypeOf = T extends Table ? DataType : T; */ export type PluginClass = PluginType & { new: (...args: unknown[]) => PluginType; + pluginName?: string; features?: string[]; requires?: string[]; }; @@ -85,7 +86,7 @@ export interface Plugin { * - only one plugin of the same name is allowed * - the name is used for storing preferences / serializable information */ - name: string; + pluginName?: string; /** * Some plugins may require that other plugins be present. diff --git a/ember-headless-table/src/plugins/-private/base.ts b/ember-headless-table/src/plugins/-private/base.ts index 737adb0d..1b79e249 100644 --- a/ember-headless-table/src/plugins/-private/base.ts +++ b/ember-headless-table/src/plugins/-private/base.ts @@ -13,6 +13,7 @@ import type { ColumnOptionsFor, OptionsFor, Plugin, + PluginClass, RowMetaFor, TableMetaFor, } from '#interfaces'; @@ -121,7 +122,7 @@ export abstract class BasePlugin implements Plugin>; }; - abstract name: string; + static pluginName: string; static features?: string[]; static requires?: string[]; } @@ -151,17 +152,17 @@ export const preferences = { * (though, if other plugins can guess how the underlying plugin access * works, they can access this data, too. No security guaranteed) */ - forColumn

, Data = unknown>(column: Column, klass: Class

) { + forColumn(column: Column, klass: PluginClass) { return { /** * delete an entry on the underlying `Map` used for this column-plugin pair */ delete(key: string) { let prefs = column.table.preferences; - let existing = prefs.storage.forPlugin(klass.name); - let columnPrefs = existing.forColumn(column.key); + let existing = klass.pluginName ? prefs.storage.forPlugin(klass.pluginName) : null; + let columnPrefs = existing?.forColumn(column.key); - columnPrefs.delete(key); + columnPrefs?.delete(key); return prefs.persist(); }, @@ -170,20 +171,20 @@ export const preferences = { */ get(key: string) { let prefs = column.table.preferences; - let existing = prefs.storage.forPlugin(klass.name); - let columnPrefs = existing.forColumn(column.key); + let existing = klass.pluginName ? prefs.storage.forPlugin(klass.pluginName) : null; + let columnPrefs = existing?.forColumn(column.key); - return columnPrefs.get(key); + return columnPrefs?.get(key); }, /** * set an entry on the underlying `Map` used for this column-plugin pair */ set(key: string, value: unknown) { let prefs = column.table.preferences; - let existing = prefs.storage.forPlugin(klass.name); - let columnPrefs = existing.forColumn(column.key); + let existing = klass.pluginName ? prefs.storage.forPlugin(klass.pluginName) : null; + let columnPrefs = existing?.forColumn(column.key); - columnPrefs.set(key, value); + columnPrefs?.set(key, value); prefs.persist(); }, @@ -196,7 +197,7 @@ export const preferences = { * returns an object for bulk updating preferences data * for all columns (scoped to key and table) */ - forAllColumns

, Data = unknown>(table: Table, klass: Class

) { + forAllColumns(table: Table, klass: PluginClass) { return { /** * delete an entry on every column in the underlying column `Map` for this table-plugin pair @@ -206,10 +207,10 @@ export const preferences = { for (let column of table.columns) { let prefs = column.table.preferences; - let existing = prefs.storage.forPlugin(klass.name); - let columnPrefs = existing.forColumn(column.key); + let existing = klass.pluginName ? prefs.storage.forPlugin(klass.pluginName) : null; + let columnPrefs = existing?.forColumn(column.key); - columnPrefs.delete(key); + columnPrefs?.delete(key); } return tablePrefs.persist(); @@ -227,16 +228,16 @@ export const preferences = { * (though, if other plugins can guess how the underlying plugin access * works, they can access this data, too. No security guaranteed) */ - forTable

, Data = unknown>(table: Table, klass: Class

) { + forTable(table: Table, klass: PluginClass) { return { /** * delete an entry on the underlying `Map` used for this table-plugin pair */ delete(key: string) { let prefs = table.preferences; - let existing = prefs.storage.forPlugin(klass.name); + let existing = klass.pluginName ? prefs.storage.forPlugin(klass.pluginName) : null; - existing.table.delete(key); + existing?.table.delete(key); return prefs.persist(); }, @@ -245,18 +246,18 @@ export const preferences = { */ get(key: string) { let prefs = table.preferences; - let existing = prefs.storage.forPlugin(klass.name); + let existing = klass.pluginName ? prefs.storage.forPlugin(klass.pluginName) : null; - return existing.table.get(key); + return existing?.table.get(key); }, /** * set an entry on the underlying `Map` used for this table-plugin pair */ set(key: string, value: unknown) { let prefs = table.preferences; - let existing = prefs.storage.forPlugin(klass.name); + let existing = klass.pluginName ? prefs.storage.forPlugin(klass.pluginName) : null; - existing.table.set(key, value); + existing?.table.set(key, value); return prefs.persist(); }, @@ -297,7 +298,7 @@ function columnsFor( if (requester) { assert( - `[${requester.name}] requested columns from the table, but the plugin, ${requester.name}, ` + + `[${requester.pluginName}] requested columns from the table, but the plugin, ${requester.pluginName}, ` + `is not used in this table`, table.plugins.some((plugin) => plugin instanceof (requester as Class)) ); @@ -313,7 +314,7 @@ function columnsFor( if (reordering && reordering.constructor === requester) { if (visibility) { assert( - `<#${visibility.name}> defined a 'columns' property, but did not return valid data.`, + `<#${visibility.pluginName}> defined a 'columns' property, but did not return valid data.`, visibility.columns && Array.isArray(visibility.columns) ); @@ -325,7 +326,7 @@ function columnsFor( if (reordering) { assert( - `<#${reordering.name}> defined a 'columns' property, but did not return valid data.`, + `<#${reordering.pluginName}> defined a 'columns' property, but did not return valid data.`, reordering.columns && Array.isArray(reordering.columns) ); @@ -334,7 +335,7 @@ function columnsFor( if (visibility) { assert( - `<#${visibility.name}> defined a 'columns' property, but did not return valid data.`, + `<#${visibility.pluginName}> defined a 'columns' property, but did not return valid data.`, visibility.columns && Array.isArray(visibility.columns) ); @@ -343,7 +344,7 @@ function columnsFor( if (sizing) { assert( - `<#${sizing.name}> defined a 'columns' property, but did not return valid data.`, + `<#${sizing.pluginName}> defined a 'columns' property, but did not return valid data.`, sizing.columns && Array.isArray(sizing.columns) ); @@ -359,7 +360,7 @@ function columnsFor( if (reordering) { assert( - `<#${reordering.name}> defined a 'columns' property, but did not return valid data.`, + `<#${reordering.pluginName}> defined a 'columns' property, but did not return valid data.`, reordering.columns && Array.isArray(reordering.columns) ); @@ -368,7 +369,7 @@ function columnsFor( if (visibility) { assert( - `<#${visibility.name}> defined a 'columns' property, but did not return valid data.`, + `<#${visibility.pluginName}> defined a 'columns' property, but did not return valid data.`, visibility.columns && Array.isArray(visibility.columns) ); @@ -377,7 +378,7 @@ function columnsFor( if (sizing) { assert( - `<#${sizing.name}> defined a 'columns' property, but did not return valid data.`, + `<#${sizing.pluginName}> defined a 'columns' property, but did not return valid data.`, sizing.columns && Array.isArray(sizing.columns) ); @@ -489,16 +490,19 @@ export const meta = { */ forColumn

, Data = unknown>( column: Column, - klass: Class

+ klass: PluginClass ): ColumnMetaFor> { let columnMeta = column.table[COLUMN_META_KEY]; return getPluginInstance(columnMeta, column, klass, () => { let plugin = column.table.pluginOf(klass); - assert(`[${klass.name}] cannot get plugin instance of unregistered plugin class`, plugin); - assert(`<#${plugin.name}> plugin does not have meta specified`, plugin.meta); - assert(`<#${plugin.name}> plugin does not specify column meta`, plugin.meta.column); + assert( + `[${klass.pluginName}] cannot get plugin instance of unregistered plugin class`, + plugin + ); + assert(`<#${klass.pluginName}> plugin does not have meta specified`, plugin.meta); + assert(`<#${klass.pluginName}> plugin does not specify column meta`, plugin.meta.column); return new plugin.meta.column(column); }); @@ -514,16 +518,21 @@ export const meta = { */ forRow

, Data = unknown>( row: Row, - klass: Class

+ klass: PluginClass ): RowMetaFor> { let rowMeta = row.table[ROW_META_KEY]; return getPluginInstance(rowMeta, row, klass, () => { let plugin = row.table.pluginOf(klass); - assert(`[${klass.name}] cannot get plugin instance of unregistered plugin class`, plugin); - assert(`<#${plugin.name}> plugin does not have meta specified`, plugin.meta); - assert(`<#${plugin.name}> plugin does not specify row meta`, plugin.meta.row); + assert( + `[${klass.pluginName}] cannot get plugin instance of unregistered plugin class`, + plugin + ); + + assert(`<#${klass.pluginName}> plugin does not have meta specified`, plugin.meta); + + assert(`<#${klass.pluginName}> plugin does not specify row meta`, plugin.meta.row); return new plugin.meta.row(row); }); @@ -537,23 +546,27 @@ export const meta = { */ forTable

, Data = unknown>( table: Table, - klass: Class

+ klass: PluginClass ): TableMetaFor> { let tableMeta = table[TABLE_META_KEY]; return getPluginInstance(tableMeta, klass, () => { let plugin = table.pluginOf(klass); - assert(`[${klass.name}] cannot get plugin instance of unregistered plugin class`, plugin); - assert(`<#${plugin.name}> plugin does not have meta specified`, plugin.meta); - assert(`<#${plugin.name}> plugin does not specify table meta`, plugin.meta.table); assert( - `<#${plugin.name}> plugin already exists for the table. ` + + `[${klass.pluginName}] cannot get plugin instance of unregistered plugin class`, + plugin + ); + assert(`<#${klass.pluginName}> plugin does not have meta specified`, plugin.meta); + assert(`<#${klass.pluginName}> plugin does not specify table meta`, plugin.meta.table); + assert( + `<#${klass.pluginName}> plugin already exists for the table. ` + `A plugin may only be instantiated once per table.`, + ![...tableMeta.keys()].includes(klass) ); - return new plugin.meta.table(table); + return plugin?.meta?.table && new plugin.meta.table(table); }); }, @@ -662,7 +675,7 @@ export const options = { */ forTable

, Data = unknown>( table: Table, - klass: Class

+ klass: PluginClass ): Partial>> { let normalized = normalizePluginsConfig(table?.config?.plugins); let tuple = normalized?.find((option) => option[0] === klass); @@ -678,7 +691,7 @@ export const options = { forColumn

, Data = unknown>( column: Column, - klass: Class

+ klass: PluginClass ): Partial>> { let tuple = column.config.pluginOptions?.find((option) => option[0] === klass); let t = tuple as [unknown, () => ColumnOptionsFor>]; diff --git a/ember-headless-table/src/plugins/column-reordering/plugin.ts b/ember-headless-table/src/plugins/column-reordering/plugin.ts index f2bea8f3..5c440578 100644 --- a/ember-headless-table/src/plugins/column-reordering/plugin.ts +++ b/ember-headless-table/src/plugins/column-reordering/plugin.ts @@ -31,7 +31,7 @@ export interface Signature { } export class ColumnReordering extends BasePlugin { - name = 'column-reordering'; + static pluginName = 'column-reordering'; static features = ['columnOrder']; meta = { diff --git a/ember-headless-table/src/plugins/column-resizing/plugin.ts b/ember-headless-table/src/plugins/column-resizing/plugin.ts index 20b933aa..a012d898 100644 --- a/ember-headless-table/src/plugins/column-resizing/plugin.ts +++ b/ember-headless-table/src/plugins/column-resizing/plugin.ts @@ -81,7 +81,7 @@ interface Signature { * but a plugin can have a "Meta" for each column */ export class ColumnResizing extends BasePlugin { - name = 'column-resizing'; + static pluginName = 'column-resizing'; static features = ['columnWidth']; meta = { @@ -270,7 +270,7 @@ export class TableMeta { } get isResizable() { - return this.options?.enabled ?? true; + return this.options?.['enabled'] ?? true; } get defaultColumnWidth() { @@ -307,7 +307,7 @@ export class TableMeta { let tablePrefs = this.table.preferences; for (let column of visibleColumnMetas) { - let existing = tablePrefs.storage.forPlugin('ColumnResizing'); + let existing = tablePrefs.storage.forPlugin(ColumnResizing.pluginName); let columnPrefs = existing.forColumn(column.key); columnPrefs.set('width', column.width.toString()); @@ -362,7 +362,7 @@ export class TableMeta { * options */ let isDraggingRight = delta > 0; - let position = this.options?.handlePosition ?? 'left'; + let position = this.options?.['handlePosition'] ?? 'left'; let growingColumn: Column | null | undefined; diff --git a/ember-headless-table/src/plugins/column-visibility/plugin.ts b/ember-headless-table/src/plugins/column-visibility/plugin.ts index 630f4ff8..ee6d8771 100644 --- a/ember-headless-table/src/plugins/column-visibility/plugin.ts +++ b/ember-headless-table/src/plugins/column-visibility/plugin.ts @@ -3,7 +3,7 @@ import { action } from '@ember/object'; import { BasePlugin, meta, options, preferences } from '../-private/base'; -import type { Plugin, PluginPreferences } from '[public-plugin-types]'; +import type { PluginPreferences } from '[public-plugin-types]'; import type { Column, Table } from '[public-types]'; interface ColumnVisibilityPreferences extends PluginPreferences { @@ -42,8 +42,8 @@ export interface Signature { }; } -export class ColumnVisibility extends BasePlugin implements Plugin { - name = 'column-visibility'; +export class ColumnVisibility extends BasePlugin { + static pluginName = 'column-visibility'; static features = ['columnVisibility']; meta = { @@ -67,7 +67,7 @@ export class ColumnMeta { let columnPreferences = preferences.forColumn(this.column, ColumnVisibility); let columnOptions = options.forColumn(this.column, ColumnVisibility); - return Boolean(columnPreferences.get('isVisible') ?? columnOptions?.isVisible ?? true); + return Boolean(columnPreferences.get('isVisible') ?? columnOptions?.['isVisible'] ?? true); } get isHidden(): boolean { @@ -80,7 +80,7 @@ export class ColumnMeta { let myPreferences = preferences.forColumn(this.column, ColumnVisibility); let myOptions = options.forColumn(this.column, ColumnVisibility); let currentSaved = myPreferences.get('isVisible'); - let willBeDefault = Boolean(currentSaved) === !myOptions?.isVisible; + let willBeDefault = Boolean(currentSaved) === !myOptions?.['isVisible']; if (willBeDefault) { myPreferences.set('isVisible', false); @@ -99,7 +99,7 @@ export class ColumnMeta { let myPreferences = preferences.forColumn(this.column, ColumnVisibility); let myOptions = options.forColumn(this.column, ColumnVisibility); let currentSaved = myPreferences.get('isVisible'); - let willBeDefault = currentSaved === !myOptions?.isVisible; + let willBeDefault = currentSaved === !myOptions?.['isVisible']; if (willBeDefault) { myPreferences.set('isVisible', true); diff --git a/ember-headless-table/src/plugins/data-sorting/plugin.ts b/ember-headless-table/src/plugins/data-sorting/plugin.ts index 2002065d..d88da0af 100644 --- a/ember-headless-table/src/plugins/data-sorting/plugin.ts +++ b/ember-headless-table/src/plugins/data-sorting/plugin.ts @@ -77,7 +77,7 @@ interface ColumnOptions { * This plugin is for *conveying* what the current sorts are, rather than _doing_ the sorting. */ export class Sorting extends BasePlugin> { - name = 'data-sorting'; + static pluginName = 'data-sorting'; meta = { column: ColumnMeta, @@ -101,7 +101,7 @@ export class ColumnMeta { } get isSortable() { - return this.options?.isSortable ?? this.tableMeta.isSortable; + return this.options?.['isSortable'] ?? this.tableMeta.isSortable; } get tableMeta() { @@ -109,7 +109,7 @@ export class ColumnMeta { } get sortDirection() { - let sort = this.tableMeta.sorts.find((sort) => sort.property === this.sortProperty); + let sort = this.tableMeta.sorts.find((sort: Sort) => sort.property === this.sortProperty); return sort?.direction ?? SortDirection.None; } @@ -127,7 +127,7 @@ export class ColumnMeta { } get sortProperty() { - return this.options?.sortProperty ?? this.column.config.key; + return this.options?.['sortProperty'] ?? this.column.config.key; } } @@ -140,15 +140,15 @@ export class TableMeta { } get sorts() { - return this.options?.sorts ?? []; + return this.options?.['sorts'] ?? []; } get isSortable() { - return Boolean(this.options?.onSort) && Boolean(this.options?.sorts); + return Boolean(this.options?.['onSort']) && Boolean(this.options?.['sorts']); } get onSort() { - return this.options?.onSort; + return this.options?.['onSort']; } @action diff --git a/ember-headless-table/src/plugins/metadata/plugin.ts b/ember-headless-table/src/plugins/metadata/plugin.ts index 5829c3ee..04a17062 100644 --- a/ember-headless-table/src/plugins/metadata/plugin.ts +++ b/ember-headless-table/src/plugins/metadata/plugin.ts @@ -22,5 +22,5 @@ export interface Signature { * "meta" is a term used for plugins for plugin authors. */ export class Metadata extends BasePlugin { - name = 'metadata'; + pluginName = 'metadata'; } diff --git a/ember-headless-table/src/plugins/row-selection/plugin.ts b/ember-headless-table/src/plugins/row-selection/plugin.ts index 7dd5cca0..91af1218 100644 --- a/ember-headless-table/src/plugins/row-selection/plugin.ts +++ b/ember-headless-table/src/plugins/row-selection/plugin.ts @@ -65,7 +65,7 @@ export interface Signature extends PluginSignatu export class RowSelection extends BasePlugin< Signature > { - name = 'row-selection'; + static pluginName = 'row-selection'; meta = { row: RowMeta, @@ -80,7 +80,7 @@ export class RowSelection extends BasePlugin< assert( `selection, onSelect, and onDeselect are all required arguments for the RowSelection plugin. ` + `Specify these options via \`RowSelection.with(() => ({ selection, onSelect, onDeselect }))\``, - pluginOptions.selection && pluginOptions.onSelect && pluginOptions.onDeselect + pluginOptions['selection'] && pluginOptions['onSelect'] && pluginOptions['onDeselect'] ); } @@ -137,7 +137,7 @@ class TableMeta { @cached get selection(): Set { - let passedSelection = options.forTable(this.#table, RowSelection).selection; + let passedSelection = options.forTable(this.#table, RowSelection)['selection']; assert(`Cannot access selection because it is undefined`, passedSelection); @@ -160,8 +160,8 @@ class RowMeta { let tableMeta = meta.forTable(this.#row.table, RowSelection); let pluginOptions = options.forTable(this.#row.table, RowSelection); - if ('key' in pluginOptions && pluginOptions.key) { - let compareWith = pluginOptions.key(this.#row.data); + if ('key' in pluginOptions && pluginOptions['key']) { + let compareWith = pluginOptions['key'](this.#row.data); return tableMeta.selection.has(compareWith); } @@ -184,28 +184,28 @@ class RowMeta { select = () => { let pluginOptions = options.forTable(this.#row.table, RowSelection); - if ('key' in pluginOptions && pluginOptions.key) { - let key = pluginOptions.key(this.#row.data); + if ('key' in pluginOptions && pluginOptions['key']) { + let key = pluginOptions['key'](this.#row.data); - pluginOptions.onSelect?.(key, this.#row); + pluginOptions['onSelect']?.(key, this.#row); return; } - pluginOptions.onSelect?.(this.#row.data, this.#row); + pluginOptions['onSelect']?.(this.#row.data, this.#row); }; deselect = () => { let pluginOptions = options.forTable(this.#row.table, RowSelection); - if ('key' in pluginOptions && pluginOptions.key) { - let key = pluginOptions.key(this.#row.data); + if ('key' in pluginOptions && pluginOptions['key']) { + let key = pluginOptions['key'](this.#row.data); - pluginOptions.onDeselect?.(key, this.#row); + pluginOptions['onDeselect']?.(key, this.#row); return; } - pluginOptions.onDeselect?.(this.#row.data, this.#row); + pluginOptions['onDeselect']?.(this.#row.data, this.#row); }; } diff --git a/ember-headless-table/src/plugins/sticky-columns/plugin.ts b/ember-headless-table/src/plugins/sticky-columns/plugin.ts index c4d5052d..32a91e6e 100644 --- a/ember-headless-table/src/plugins/sticky-columns/plugin.ts +++ b/ember-headless-table/src/plugins/sticky-columns/plugin.ts @@ -39,7 +39,7 @@ export interface Signature { } export class StickyColumns extends BasePlugin { - name = 'sticky-columns'; + static pluginName = 'sticky-columns'; /** * This plugin requires that the resizing plugin be present, because the resizing plugin is @@ -74,7 +74,7 @@ export class StickyColumns extends BasePlugin { }; headerCellModifier = (element: HTMLElement, { column, table }: ColumnApi) => { - if (options.forTable(table, StickyColumns).workaroundForModifierTimingUpdateRFC883) { + if (options.forTable(table, StickyColumns)['workaroundForModifierTimingUpdateRFC883']) { return; } @@ -93,7 +93,7 @@ export class StickyColumns extends BasePlugin { * TODO: switch ColumnApi to "RowApi", add the row's data */ cellModifier = (element: HTMLElement, { column, table }: ColumnApi) => { - if (options.forTable(table, StickyColumns).workaroundForModifierTimingUpdateRFC883) { + if (options.forTable(table, StickyColumns)['workaroundForModifierTimingUpdateRFC883']) { return; } @@ -120,7 +120,7 @@ export class ColumnMeta { } get position(): 'left' | 'right' | 'none' { - let sticky = options.forColumn(this.column, StickyColumns)?.sticky; + let sticky = options.forColumn(this.column, StickyColumns)?.['sticky']; assert( `Invalid sticky value, ${sticky}. Valid values: 'left', 'right', false`,