All files / owid-grapher/explorerAdminClient ExplorerCommands.ts

30.51% Statements 36/118
100% Branches 0/0
0% Functions 0/13
30.51% Lines 36/118

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 1361x 1x 1x 1x 1x 1x 1x 1x 1x 1x             1x 1x 1x 1x 1x 1x 1x         1x 1x       1x 1x       1x 1x 1x                                 1x 1x 1x 1x             1x 1x                             1x 1x     1x 1x     1x 1x                                                                                        
import { ExplorerGrammar } from "../explorer/ExplorerGrammar"
import { ExplorerProgram } from "../explorer/ExplorerProgram"
import { CellPosition } from "../gridLang/GridLangConstants"
import Handsontable from "handsontable"
import HotContextMenu from "handsontable/plugins/contextMenu"
 
abstract class HotCommand {
    protected program: ExplorerProgram
    protected setProgramCallback?: (newProgram: string) => void
    constructor(
        program: ExplorerProgram,
        setProgramCallback?: (newProgram: string) => void
    ) {
        this.program = program
        this.setProgramCallback = setProgramCallback
    }
 
    abstract name(hot: Handsontable): string
    abstract callback(hot: Handsontable): void
    abstract disabled(hot: Handsontable): boolean
    abstract hidden(hot: Handsontable): boolean
 
    protected selectedPosition(hot: Handsontable) {
        const coords = hot.getSelectedLast()
        if (!coords) return undefined
        return { row: coords[0], column: coords[1] } as CellPosition
    }
 
    protected cell(hot: Handsontable) {
        const pos = this.selectedPosition(hot)
        return pos ? this.program.getCell(pos) : undefined
    }
 
    protected searchResults(hot: Handsontable) {
        const pos = this.selectedPosition(hot)
        return pos ? this.program.findAll(pos) : []
    }
 
    // handles the "this" binding needed by HOT
    toHotCommand(): HotContextMenu.MenuItemConfig {
        const baseCommand = this
        return {
            name: function () {
                return baseCommand.name(this)
            },
            callback: function () {
                return baseCommand.callback(this)
            },
            disabled: function () {
                return baseCommand.disabled(this)
            },
            hidden: function () {
                return baseCommand.hidden(this)
            },
        }
    }
}
 
export class SelectAllHitsCommand extends HotCommand {
    name(hot: Handsontable) {
        const cell = this.cell(hot)
        if (!cell) return `Nothing selected`
        const searchResults = this.searchResults(hot)
        if (searchResults.length === 1) return `1 match of '${cell.contents}'`
        return `Select ${searchResults.length} matches of '${cell.contents}'`
    }
 
    callback(hot: Handsontable) {
        const searchResults = this.searchResults(hot)
        if (!searchResults.length) return
        hot.selectCells(
            searchResults.map(
                (pos) =>
                    [pos.row, pos.column, pos.row, pos.column] as [
                        number,
                        number,
                        number,
                        number
                    ]
            )
        )
    }
 
    hidden() {
        return false
    }
 
    disabled(hot: Handsontable) {
        return this.searchResults(hot).length < 2
    }
}
 
export class AutofillColDefCommand extends HotCommand {
    name() {
        return "⚡ Autofill missing column definitions"
    }
    commandName: keyof ExplorerProgram =
        "autofillMissingColumnDefinitionsForTableCommand"

    async callback(hot: Handsontable) {
        const selectedPosition = this.selectedPosition(hot)
        const { program, commandName } = this

        if (!selectedPosition) return
        const tableSlugCell = program.getCell({
            ...selectedPosition,
            column: selectedPosition.column + 1,
        })

        // todo: figure out typings. we need keyof ExplorerProgram but only if key is to a callable method.
        const newProgram = await (program as any)[commandName](
            tableSlugCell.contents
        )
        this.setProgramCallback!(newProgram.toString())
    }

    disabled() {
        return false
    }
 
    hidden(hot: Handsontable) {
        const cell = this.cell(hot)
        return cell
            ? cell.cellDef?.keyword !== ExplorerGrammar.table.keyword
            : true
    }
}
 
export class InlineDataCommand extends AutofillColDefCommand {
    name() {
        return "⚡ Inline data and autofill columns"
    }
    commandName: keyof ExplorerProgram =
        "replaceTableWithInlineDataAndAutofilledColumnDefsCommand"
}