diff --git a/extensions/src/common/types/framework/blocks.ts b/extensions/src/common/types/framework/blocks.ts index e877735d8..e7dfc9094 100644 --- a/extensions/src/common/types/framework/blocks.ts +++ b/extensions/src/common/types/framework/blocks.ts @@ -312,7 +312,7 @@ export type BlockDefinitions = { [k in keyof T["BlockFunctions"]]: T["BlockFunctions"][k] extends (...args: infer A) => infer R - ? DefineBlock R> + ? DefineBlock R> : never }; diff --git a/extensions/src/tables/View.svelte b/extensions/src/tables/View.svelte index e86435f87..af01dabcb 100644 --- a/extensions/src/tables/View.svelte +++ b/extensions/src/tables/View.svelte @@ -10,7 +10,7 @@ const set: ReactiveSet = (propertyName, value) => reactiveSet((extension = extension), propertyName, value); const container = activeClass; - const tableListDropdown = activeClass, tableBox = activeClass, tableValueInput = activeClass; + const tableListDropdown = activeClass, tableBox = activeClass, tableValueInput = activeClass, nameInput = activeClass; const tableNames = Object.keys(extension.tables); let selected: string = tableNames.length > 0 ? tableNames[0] : ""; @@ -18,11 +18,17 @@ type InputChangeEvent = Event & { currentTarget: EventTarget & HTMLInputElement}; const update = (e: InputChangeEvent, row: number, column: number) => invoke("changeTableValue", {name: selected, row, column, value: parseInt(e.currentTarget.value)}); + const updateColumnName = (e: InputChangeEvent, column: number) + => + invoke("changeColumnName", {name: selected, column, value: e.currentTarget.value}); + const updateRowName = (e: InputChangeEvent, row: number) + => + invoke("changeRowName", {name: selected, row, value: e.currentTarget.value}); @@ -71,18 +85,22 @@ - {#each [...Array(extension.tables[selected][0].length)] as _, i} - {i + 1} + {#each extension.columnNames[selected] as columnName, i} + + updateColumnName(e, i)} data-testid="columnNameCell"> + {/each} {#each extension.tables[selected] as row, i} - {i + 1} + + updateRowName(e, i)} data-testid="rowNameCell"> + {#each row as value, j} - update(e, i, j)} data-testid="tableCell"> + update(e, i, j)} data-testid="tableCell"> {/each} diff --git a/extensions/src/tables/index.test.ts b/extensions/src/tables/index.test.ts index daf247ee5..5c9fffb98 100644 --- a/extensions/src/tables/index.test.ts +++ b/extensions/src/tables/index.test.ts @@ -49,6 +49,8 @@ createTestSuite({ Extension, __dirname }, { const { extension, testHelper: { expect } } = fixture; extension.newTable({ name: 'newTable', rows: 2, columns: 2 }); expect(extension.tables.newTable.length).toBe(2); + expect(extension.rowNames.newTable[0]).toBe('row'); + expect(extension.columnNames.newTable[0]).toBe('col'); }, after: async (fixture) => { const { ui, extension, testHelper: { expect, fireEvent, updateHTMLInputValue } } = fixture; @@ -67,8 +69,14 @@ createTestSuite({ Extension, __dirname }, { expect(cells.length).toBe(4); const cell = cells[0] as HTMLInputElement; + const rows = await ui.findAllByTestId('rowNameCell'); + expect(rows.length).toBe(2); + const row = rows[0] as HTMLInputElement; + await fireEvent.change(cell, { target: { value: '3' } }); + await fireEvent.change(row, { target: { value: 'newName' } }); expect(extension.tables.newTable[0][0]).toBe(3); + expect(extension.rowNames.newTable[0]).toBe('newName'); delete extension.tables.newTable; } @@ -112,10 +120,16 @@ createTestSuite({ Extension, __dirname }, { input: 'newTable', before: ({ extension }) => { extension.tables.newTable = [[1]]; + extension.rowNames.newTable = ['row']; + extension.columnNames.newTable = ['col']; }, after: ({ extension }) => { expect(extension.tables.newTable).toBe(undefined); + expect(extension.rowNames.newTable).toBe(undefined); + expect(extension.columnNames.newTable).toBe(undefined); delete extension.tables.newTable; + delete extension.rowNames.newTable; + delete extension.columnNames.newTable; } } }, @@ -124,11 +138,17 @@ createTestSuite({ Extension, __dirname }, { input: 'newTable', before: ({ extension }) => { extension.tables.newTable = [[1]]; + extension.rowNames.newTable = ['row']; + extension.columnNames.newTable = ['col']; }, after: ({ extension }) => { expect(extension.tables.newTable[0].length).toBe(2); expect(extension.tables.newTable[0][1]).toBe(0); + expect(extension.columnNames.newTable.length).toBe(2); + expect(extension.rowNames.newTable.length).toBe(1); delete extension.tables.newTable; + delete extension.rowNames.newTable; + delete extension.columnNames.newTable; } } }, @@ -137,11 +157,63 @@ createTestSuite({ Extension, __dirname }, { input: 'newTable', before: ({ extension }) => { extension.tables.newTable = [[1]]; + extension.rowNames.newTable = ['row']; + extension.columnNames.newTable = ['col']; }, after: ({ extension }) => { expect(extension.tables.newTable.length).toBe(2); expect(extension.tables.newTable[1][0]).toBe(0); + expect(extension.columnNames.newTable.length).toBe(1); + expect(extension.rowNames.newTable.length).toBe(2); + delete extension.tables.newTable; + delete extension.rowNames.newTable; + delete extension.columnNames.newTable; + } + } + }, + deleteColumn: ({ expect }) => { + return { + input: ['newTable', 1], + before: ({ extension }) => { + extension.tables.newTable = [ + [0, 0], + [1, 0] + ]; + extension.rowNames.newTable = ['row', 'row']; + extension.columnNames.newTable = ['col', 'col']; + }, + after: ({ extension }) => { + expect(extension.tables.newTable.length).toBe(2); + expect(extension.tables.newTable[0].length).toBe(1); + expect(extension.tables.newTable[0][0]).toBe(0); + expect(extension.columnNames.newTable.length).toBe(1); + expect(extension.rowNames.newTable.length).toBe(2); + delete extension.tables.newTable; + delete extension.rowNames.newTable; + delete extension.columnNames.newTable; + } + } + }, + deleteRow: ({ expect }) => { + return { + input: ['newTable', 1], + before: ({ extension }) => { + extension.tables.newTable = [ + [0, 0], + [1, 1] + ]; + extension.rowNames.newTable = ['row', 'row']; + extension.columnNames.newTable = ['col', 'col']; + }, + after: ({ extension }) => { + expect(extension.tables.newTable.length).toBe(1); + expect(extension.tables.newTable[0].length).toBe(2); + expect(extension.tables.newTable[0][0]).toBe(1); + expect(extension.columnNames.newTable.length).toBe(2); + expect(extension.rowNames.newTable.length).toBe(1); delete extension.tables.newTable; + delete extension.rowNames.newTable; + delete extension.columnNames.newTable; } } }, diff --git a/extensions/src/tables/index.ts b/extensions/src/tables/index.ts index 7e1ce764f..06040b19a 100644 --- a/extensions/src/tables/index.ts +++ b/extensions/src/tables/index.ts @@ -18,6 +18,8 @@ type Blocks = { removeTable: (table: string) => void; insertColumn: (table: string) => void; insertRow: (table: string) => void; + deleteColumn: (table: string, column: number) => void; + deleteRow: (table: string, row: number) => void; insertValueAt: (table: string, value: number, row: number, column: number) => void; getValueAt: (table: string, row: number, column: number) => number; numberOfRows: (table: string) => number; @@ -32,21 +34,59 @@ type Blocks = { @validGenericExtension() export default class Tables extends Extension { tables: Record; + rowNames: Record; + columnNames: Record; tableNamesArg: any; defaultNumberArg: any; // save tables to .sb3 when project is saved override saveDataHandler = new SaveDataHandler({ Extension: Tables, - onSave: (self) => { return self.tables }, - onLoad: (self, tables) => { self.tables = tables }, + onSave: (self) => { + return { + tables: self.tables, + rowNames: self.rowNames, + columnNames: self.columnNames + }; + }, + onLoad: (self, data) => { + if (data.rowNames === undefined) { + // save happened before column names and row names were added + // as a feature. + // Load data into tables, and then construct default names. + + self.tables = data as any; + const tableNames = Object.keys(self.tables); + + self.rowNames = tableNames.reduce((rowNames, tableName, i) => { + rowNames[tableName] = self.tables[tableName].map( + (row, i) => `row${i + 1}` + ); + return rowNames; + }, {}); + self.columnNames = tableNames.reduce((columnNames, tableName, i) => { + columnNames[tableName] = self.tables[tableName][0].map( + (col, i) => `col${i + 1}` + ); + return columnNames; + }, {}); + } else { + self.tables = data.tables; + self.rowNames = data.rowNames; + self.columnNames = data.columnNames; + } + }, }); init(env: Environment) { if (!this.tables) { this.tables = {}; + this.rowNames = {}; + this.columnNames = {}; this.tables.myTable = []; this.tables.myTable.push([0]); + this.rowNames.myTable = ['row']; + this.columnNames.myTable = ['col']; } // dynamic retriever of table names for block dropdowns @@ -80,10 +120,11 @@ export default class Tables extends Extension { ); } - newTable(info: { name: string, rows: number, columns: number }) { const { name, rows, columns } = info; this.tables[name] = []; + this.rowNames[name] = new Array(rows).fill('row'); + this.columnNames[name] = new Array(columns).fill('col'); for (let i = 0; i < rows; i++) { let newRow = []; for (let j = 0; j < columns; j++) newRow.push(0); @@ -96,6 +137,16 @@ export default class Tables extends Extension { this.tables[name][row][column] = value; } + changeColumnName(info: { name: string, column: number, value: string }) { + const { name, column, value } = info; + this.columnNames[name][column] = value; + } + + changeRowName(info: { name: string, row: number, value: string }) { + const { name, row, value } = info; + this.rowNames[name][row] = value; + } + defineBlocks(): Tables["BlockDefinitions"] { return { // button that opens a modal to create a new table @@ -129,7 +180,9 @@ export default class Tables extends Extension { text: (table) => `remove ${table}`, operation: (table) => { if (this.tables[table]) { - delete this.tables[table] + delete this.tables[table]; + delete this.rowNames[table]; + delete this.columnNames[table]; return; } else { alert(`that table doesn't exist`); @@ -150,6 +203,7 @@ export default class Tables extends Extension { for (let i = 0; i < this.tables[table].length; i++) { this.tables[table][i].push(0); } + this.columnNames[table].push('col'); } }), // add a row to the given table @@ -167,6 +221,51 @@ export default class Tables extends Extension { newRow.push(0); } this.tables[table].push(newRow); + this.rowNames[table].push('row'); + } + }), + // removes the specified column from the table + deleteColumn: (self: Tables) => ({ + type: BlockType.Command, + args: [self.tableNamesArg, self.defaultNumberArg], + text: (table, column) => `delete column ${column} from ${table}`, + operation: (table, column) => { + if (!(table in self.tables)) { + alert(`that table does not exist.`); + return; + } + if ( + column > this.tables[table][0].length || + column < 1 + ) { + alert(`that column number doesn't exist.`); + return; + } + for (let i = 0; i < this.tables[table].length; i++) { + this.tables[table][i].splice((column - 1), 1); + } + this.columnNames[table].splice((column - 1), 1); + } + }), + // removes the specified row from th table + deleteRow: (self: Tables) => ({ + type: BlockType.Command, + args: [self.tableNamesArg, self.defaultNumberArg], + text: (table, row) => `delete row ${row} from ${table}`, + operation: (table, row) => { + if (!(table in self.tables)) { + alert(`that table does not exist.`); + return; + } + if ( + row > this.tables[table].length || + row < 1 + ) { + alert(`that row number doesn't exist.`); + return; + } + this.tables[table].splice((row - 1), 1); + this.rowNames[table].splice((row - 1), 1); } }), // change the value in a given table cell