@trebco/treb 37.0.0 → 37.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/package.json +119 -0
- package/build/treb-base-types/src/api_types.d.ts +11 -0
- package/build/treb-base-types/src/api_types.js +22 -0
- package/build/treb-base-types/src/api_types.js.map +1 -0
- package/build/treb-base-types/src/area-utils.d.ts +9 -0
- package/build/treb-base-types/src/area-utils.js +50 -0
- package/build/treb-base-types/src/area-utils.js.map +1 -0
- package/build/treb-base-types/src/area.d.ts +182 -0
- package/build/treb-base-types/src/area.js +715 -0
- package/build/treb-base-types/src/area.js.map +1 -0
- package/build/treb-base-types/src/basic_types.d.ts +20 -0
- package/build/treb-base-types/src/basic_types.js +22 -0
- package/build/treb-base-types/src/basic_types.js.map +1 -0
- package/build/treb-base-types/src/cell.d.ts +167 -0
- package/build/treb-base-types/src/cell.js +432 -0
- package/build/treb-base-types/src/cell.js.map +1 -0
- package/build/treb-base-types/src/cells.d.ts +251 -0
- package/build/treb-base-types/src/cells.js +1136 -0
- package/build/treb-base-types/src/cells.js.map +1 -0
- package/build/treb-base-types/src/color.d.ts +35 -0
- package/build/treb-base-types/src/color.js +162 -0
- package/build/treb-base-types/src/color.js.map +1 -0
- package/build/treb-base-types/src/dom-utilities.d.ts +70 -0
- package/build/treb-base-types/src/dom-utilities.js +144 -0
- package/build/treb-base-types/src/dom-utilities.js.map +1 -0
- package/build/treb-base-types/src/evaluate-options.d.ts +35 -0
- package/build/treb-base-types/src/evaluate-options.js +22 -0
- package/build/treb-base-types/src/evaluate-options.js.map +1 -0
- package/build/treb-base-types/src/font-stack.d.ts +37 -0
- package/build/treb-base-types/src/font-stack.js +93 -0
- package/build/treb-base-types/src/font-stack.js.map +1 -0
- package/build/treb-base-types/src/gradient.d.ts +18 -0
- package/build/treb-base-types/src/gradient.js +86 -0
- package/build/treb-base-types/src/gradient.js.map +1 -0
- package/build/treb-base-types/src/import.d.ts +48 -0
- package/build/treb-base-types/src/import.js +22 -0
- package/build/treb-base-types/src/import.js.map +1 -0
- package/build/treb-base-types/src/index-standalone.d.ts +6 -0
- package/build/treb-base-types/src/index-standalone.js +27 -0
- package/build/treb-base-types/src/index-standalone.js.map +1 -0
- package/build/treb-base-types/src/index.d.ts +22 -0
- package/build/treb-base-types/src/index.js +45 -0
- package/build/treb-base-types/src/index.js.map +1 -0
- package/build/treb-base-types/src/layout.d.ts +22 -0
- package/build/treb-base-types/src/layout.js +22 -0
- package/build/treb-base-types/src/layout.js.map +1 -0
- package/build/treb-base-types/src/localization.d.ts +37 -0
- package/build/treb-base-types/src/localization.js +157 -0
- package/build/treb-base-types/src/localization.js.map +1 -0
- package/build/treb-base-types/src/rectangle.d.ts +51 -0
- package/build/treb-base-types/src/rectangle.js +123 -0
- package/build/treb-base-types/src/rectangle.js.map +1 -0
- package/build/treb-base-types/src/render_text.d.ts +34 -0
- package/build/treb-base-types/src/render_text.js +22 -0
- package/build/treb-base-types/src/render_text.js.map +1 -0
- package/build/treb-base-types/src/style.d.ts +214 -0
- package/build/treb-base-types/src/style.js +373 -0
- package/build/treb-base-types/src/style.js.map +1 -0
- package/build/treb-base-types/src/table.d.ts +58 -0
- package/build/treb-base-types/src/table.js +27 -0
- package/build/treb-base-types/src/table.js.map +1 -0
- package/build/treb-base-types/src/text_part.d.ts +26 -0
- package/build/treb-base-types/src/text_part.js +47 -0
- package/build/treb-base-types/src/text_part.js.map +1 -0
- package/build/treb-base-types/src/theme.d.ts +120 -0
- package/build/treb-base-types/src/theme.js +460 -0
- package/build/treb-base-types/src/theme.js.map +1 -0
- package/build/treb-base-types/src/union.d.ts +73 -0
- package/build/treb-base-types/src/union.js +61 -0
- package/build/treb-base-types/src/union.js.map +1 -0
- package/build/treb-base-types/src/value-type.d.ts +86 -0
- package/build/treb-base-types/src/value-type.js +168 -0
- package/build/treb-base-types/src/value-type.js.map +1 -0
- package/build/treb-base-types/src/worker-proxy.d.ts +95 -0
- package/build/treb-base-types/src/worker-proxy.js +221 -0
- package/build/treb-base-types/src/worker-proxy.js.map +1 -0
- package/build/treb-calculator/src/calculator.d.ts +249 -0
- package/build/treb-calculator/src/calculator.js +2755 -0
- package/build/treb-calculator/src/calculator.js.map +1 -0
- package/build/treb-calculator/src/complex-math.d.ts +75 -0
- package/build/treb-calculator/src/complex-math.js +559 -0
- package/build/treb-calculator/src/complex-math.js.map +1 -0
- package/build/treb-calculator/src/dag/array-vertex.d.ts +71 -0
- package/build/treb-calculator/src/dag/array-vertex.js +156 -0
- package/build/treb-calculator/src/dag/array-vertex.js.map +1 -0
- package/build/treb-calculator/src/dag/calculation_leaf_vertex.d.ts +48 -0
- package/build/treb-calculator/src/dag/calculation_leaf_vertex.js +84 -0
- package/build/treb-calculator/src/dag/calculation_leaf_vertex.js.map +1 -0
- package/build/treb-calculator/src/dag/graph.d.ts +134 -0
- package/build/treb-calculator/src/dag/graph.js +842 -0
- package/build/treb-calculator/src/dag/graph.js.map +1 -0
- package/build/treb-calculator/src/dag/spreadsheet_vertex.d.ts +58 -0
- package/build/treb-calculator/src/dag/spreadsheet_vertex.js +232 -0
- package/build/treb-calculator/src/dag/spreadsheet_vertex.js.map +1 -0
- package/build/treb-calculator/src/dag/spreadsheet_vertex_base.d.ts +20 -0
- package/build/treb-calculator/src/dag/spreadsheet_vertex_base.js +25 -0
- package/build/treb-calculator/src/dag/spreadsheet_vertex_base.js.map +1 -0
- package/build/treb-calculator/src/dag/state_leaf_vertex.d.ts +43 -0
- package/build/treb-calculator/src/dag/state_leaf_vertex.js +81 -0
- package/build/treb-calculator/src/dag/state_leaf_vertex.js.map +1 -0
- package/build/treb-calculator/src/dag/vertex.d.ts +71 -0
- package/build/treb-calculator/src/dag/vertex.js +274 -0
- package/build/treb-calculator/src/dag/vertex.js.map +1 -0
- package/build/treb-calculator/src/descriptors.d.ts +189 -0
- package/build/treb-calculator/src/descriptors.js +22 -0
- package/build/treb-calculator/src/descriptors.js.map +1 -0
- package/build/treb-calculator/src/expression-calculator.d.ts +127 -0
- package/build/treb-calculator/src/expression-calculator.js +1033 -0
- package/build/treb-calculator/src/expression-calculator.js.map +1 -0
- package/build/treb-calculator/src/function-error.d.ts +35 -0
- package/build/treb-calculator/src/function-error.js +85 -0
- package/build/treb-calculator/src/function-error.js.map +1 -0
- package/build/treb-calculator/src/function-library.d.ts +22 -0
- package/build/treb-calculator/src/function-library.js +96 -0
- package/build/treb-calculator/src/function-library.js.map +1 -0
- package/build/treb-calculator/src/functions/base-functions.d.ts +7 -0
- package/build/treb-calculator/src/functions/base-functions.js +2611 -0
- package/build/treb-calculator/src/functions/base-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/beta.d.ts +17 -0
- package/build/treb-calculator/src/functions/beta.js +201 -0
- package/build/treb-calculator/src/functions/beta.js.map +1 -0
- package/build/treb-calculator/src/functions/checkbox.d.ts +3 -0
- package/build/treb-calculator/src/functions/checkbox.js +128 -0
- package/build/treb-calculator/src/functions/checkbox.js.map +1 -0
- package/build/treb-calculator/src/functions/complex-functions.d.ts +2 -0
- package/build/treb-calculator/src/functions/complex-functions.js +217 -0
- package/build/treb-calculator/src/functions/complex-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/date-utils.d.ts +3 -0
- package/build/treb-calculator/src/functions/date-utils.js +59 -0
- package/build/treb-calculator/src/functions/date-utils.js.map +1 -0
- package/build/treb-calculator/src/functions/finance-functions.d.ts +2 -0
- package/build/treb-calculator/src/functions/finance-functions.js +547 -0
- package/build/treb-calculator/src/functions/finance-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/fp.d.ts +2 -0
- package/build/treb-calculator/src/functions/fp.js +463 -0
- package/build/treb-calculator/src/functions/fp.js.map +1 -0
- package/build/treb-calculator/src/functions/function-utilities.d.ts +2 -0
- package/build/treb-calculator/src/functions/function-utilities.js +36 -0
- package/build/treb-calculator/src/functions/function-utilities.js.map +1 -0
- package/build/treb-calculator/src/functions/gamma.d.ts +20 -0
- package/build/treb-calculator/src/functions/gamma.js +142 -0
- package/build/treb-calculator/src/functions/gamma.js.map +1 -0
- package/build/treb-calculator/src/functions/information-functions.d.ts +2 -0
- package/build/treb-calculator/src/functions/information-functions.js +71 -0
- package/build/treb-calculator/src/functions/information-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/lambda-functions.d.ts +2 -0
- package/build/treb-calculator/src/functions/lambda-functions.js +85 -0
- package/build/treb-calculator/src/functions/lambda-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/matrix-functions.d.ts +2 -0
- package/build/treb-calculator/src/functions/matrix-functions.js +144 -0
- package/build/treb-calculator/src/functions/matrix-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/normal.d.ts +2 -0
- package/build/treb-calculator/src/functions/normal.js +32 -0
- package/build/treb-calculator/src/functions/normal.js.map +1 -0
- package/build/treb-calculator/src/functions/regex-functions.d.ts +2 -0
- package/build/treb-calculator/src/functions/regex-functions.js +188 -0
- package/build/treb-calculator/src/functions/regex-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/sparkline.d.ts +37 -0
- package/build/treb-calculator/src/functions/sparkline.js +264 -0
- package/build/treb-calculator/src/functions/sparkline.js.map +1 -0
- package/build/treb-calculator/src/functions/statistics-functions.d.ts +6 -0
- package/build/treb-calculator/src/functions/statistics-functions.js +989 -0
- package/build/treb-calculator/src/functions/statistics-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/students-t.d.ts +3 -0
- package/build/treb-calculator/src/functions/students-t.js +64 -0
- package/build/treb-calculator/src/functions/students-t.js.map +1 -0
- package/build/treb-calculator/src/functions/text-functions.d.ts +3 -0
- package/build/treb-calculator/src/functions/text-functions.js +320 -0
- package/build/treb-calculator/src/functions/text-functions.js.map +1 -0
- package/build/treb-calculator/src/index.d.ts +2 -0
- package/build/treb-calculator/src/index.js +22 -0
- package/build/treb-calculator/src/index.js.map +1 -0
- package/build/treb-calculator/src/notifier-types.d.ts +26 -0
- package/build/treb-calculator/src/notifier-types.js +22 -0
- package/build/treb-calculator/src/notifier-types.js.map +1 -0
- package/build/treb-calculator/src/primitives.d.ts +15 -0
- package/build/treb-calculator/src/primitives.js +398 -0
- package/build/treb-calculator/src/primitives.js.map +1 -0
- package/build/treb-calculator/src/utilities.d.ts +68 -0
- package/build/treb-calculator/src/utilities.js +324 -0
- package/build/treb-calculator/src/utilities.js.map +1 -0
- package/build/treb-charts/src/chart-functions.d.ts +8 -0
- package/build/treb-charts/src/chart-functions.js +209 -0
- package/build/treb-charts/src/chart-functions.js.map +1 -0
- package/build/treb-charts/src/chart-types.d.ts +233 -0
- package/build/treb-charts/src/chart-types.js +57 -0
- package/build/treb-charts/src/chart-types.js.map +1 -0
- package/build/treb-charts/src/chart-utils.d.ts +106 -0
- package/build/treb-charts/src/chart-utils.js +1060 -0
- package/build/treb-charts/src/chart-utils.js.map +1 -0
- package/build/treb-charts/src/chart.d.ts +23 -0
- package/build/treb-charts/src/chart.js +94 -0
- package/build/treb-charts/src/chart.js.map +1 -0
- package/build/treb-charts/src/default-chart-renderer.d.ts +16 -0
- package/build/treb-charts/src/default-chart-renderer.js +533 -0
- package/build/treb-charts/src/default-chart-renderer.js.map +1 -0
- package/build/treb-charts/src/index.d.ts +5 -0
- package/build/treb-charts/src/index.js +24 -0
- package/build/treb-charts/src/index.js.map +1 -0
- package/build/treb-charts/src/main.d.ts +1 -0
- package/build/treb-charts/src/main.js +34 -0
- package/build/treb-charts/src/main.js.map +1 -0
- package/build/treb-charts/src/quicksort.d.ts +1 -0
- package/build/treb-charts/src/quicksort.js +49 -0
- package/build/treb-charts/src/quicksort.js.map +1 -0
- package/build/treb-charts/src/rectangle.d.ts +18 -0
- package/build/treb-charts/src/rectangle.js +41 -0
- package/build/treb-charts/src/rectangle.js.map +1 -0
- package/build/treb-charts/src/renderer-type.d.ts +24 -0
- package/build/treb-charts/src/renderer-type.js +22 -0
- package/build/treb-charts/src/renderer-type.js.map +1 -0
- package/build/treb-charts/src/renderer.d.ts +127 -0
- package/build/treb-charts/src/renderer.js +1518 -0
- package/build/treb-charts/src/renderer.js.map +1 -0
- package/build/treb-charts/src/util.d.ts +18 -0
- package/build/treb-charts/src/util.js +71 -0
- package/build/treb-charts/src/util.js.map +1 -0
- package/build/treb-data-model/src/annotation.d.ts +167 -0
- package/build/treb-data-model/src/annotation.js +120 -0
- package/build/treb-data-model/src/annotation.js.map +1 -0
- package/build/treb-data-model/src/conditional_format.d.ts +155 -0
- package/build/treb-data-model/src/conditional_format.js +62 -0
- package/build/treb-data-model/src/conditional_format.js.map +1 -0
- package/build/treb-data-model/src/data-validation.d.ts +28 -0
- package/build/treb-data-model/src/data-validation.js +22 -0
- package/build/treb-data-model/src/data-validation.js.map +1 -0
- package/build/treb-data-model/src/data_model.d.ts +173 -0
- package/build/treb-data-model/src/data_model.js +637 -0
- package/build/treb-data-model/src/data_model.js.map +1 -0
- package/build/treb-data-model/src/index.d.ts +13 -0
- package/build/treb-data-model/src/index.js +28 -0
- package/build/treb-data-model/src/index.js.map +1 -0
- package/build/treb-data-model/src/language-model.d.ts +22 -0
- package/build/treb-data-model/src/language-model.js +22 -0
- package/build/treb-data-model/src/language-model.js.map +1 -0
- package/build/treb-data-model/src/named.d.ts +124 -0
- package/build/treb-data-model/src/named.js +372 -0
- package/build/treb-data-model/src/named.js.map +1 -0
- package/build/treb-data-model/src/serialize_options.d.ts +49 -0
- package/build/treb-data-model/src/serialize_options.js +22 -0
- package/build/treb-data-model/src/serialize_options.js.map +1 -0
- package/build/treb-data-model/src/sheet.d.ts +499 -0
- package/build/treb-data-model/src/sheet.js +2904 -0
- package/build/treb-data-model/src/sheet.js.map +1 -0
- package/build/treb-data-model/src/sheet_collection.d.ts +58 -0
- package/build/treb-data-model/src/sheet_collection.js +112 -0
- package/build/treb-data-model/src/sheet_collection.js.map +1 -0
- package/build/treb-data-model/src/sheet_selection.d.ts +42 -0
- package/build/treb-data-model/src/sheet_selection.js +39 -0
- package/build/treb-data-model/src/sheet_selection.js.map +1 -0
- package/build/treb-data-model/src/sheet_types.d.ts +104 -0
- package/build/treb-data-model/src/sheet_types.js +22 -0
- package/build/treb-data-model/src/sheet_types.js.map +1 -0
- package/build/treb-data-model/src/types.d.ts +59 -0
- package/build/treb-data-model/src/types.js +22 -0
- package/build/treb-data-model/src/types.js.map +1 -0
- package/build/treb-embed/src/custom-element/spreadsheet-constructor.d.ts +75 -0
- package/build/treb-embed/src/custom-element/spreadsheet-constructor.js +1144 -0
- package/build/treb-embed/src/custom-element/spreadsheet-constructor.js.map +1 -0
- package/build/treb-embed/src/custom-element/treb-global.d.ts +36 -0
- package/build/treb-embed/src/custom-element/treb-global.js +64 -0
- package/build/treb-embed/src/custom-element/treb-global.js.map +1 -0
- package/build/treb-embed/src/custom-element/treb-spreadsheet-element.d.ts +1 -0
- package/build/treb-embed/src/custom-element/treb-spreadsheet-element.js +61 -0
- package/build/treb-embed/src/custom-element/treb-spreadsheet-element.js.map +1 -0
- package/build/treb-embed/src/embedded-spreadsheet.d.ts +1358 -0
- package/build/treb-embed/src/embedded-spreadsheet.js +5205 -0
- package/build/treb-embed/src/embedded-spreadsheet.js.map +1 -0
- package/build/treb-embed/src/index.d.ts +12 -0
- package/build/treb-embed/src/index.js +34 -0
- package/build/treb-embed/src/index.js.map +1 -0
- package/build/treb-embed/src/options.d.ts +266 -0
- package/build/treb-embed/src/options.js +56 -0
- package/build/treb-embed/src/options.js.map +1 -0
- package/build/treb-embed/src/plugin.d.ts +9 -0
- package/build/treb-embed/src/plugin.js +22 -0
- package/build/treb-embed/src/plugin.js.map +1 -0
- package/build/treb-embed/src/progress-dialog.d.ts +49 -0
- package/build/treb-embed/src/progress-dialog.js +178 -0
- package/build/treb-embed/src/progress-dialog.js.map +1 -0
- package/build/treb-embed/src/selection-state.d.ts +15 -0
- package/build/treb-embed/src/selection-state.js +22 -0
- package/build/treb-embed/src/selection-state.js.map +1 -0
- package/build/treb-embed/src/spinner.d.ts +8 -0
- package/build/treb-embed/src/spinner.js +40 -0
- package/build/treb-embed/src/spinner.js.map +1 -0
- package/build/treb-embed/src/toolbar-message.d.ts +72 -0
- package/build/treb-embed/src/toolbar-message.js +22 -0
- package/build/treb-embed/src/toolbar-message.js.map +1 -0
- package/build/treb-embed/src/types.d.ts +185 -0
- package/build/treb-embed/src/types.js +45 -0
- package/build/treb-embed/src/types.js.map +1 -0
- package/build/treb-embed/tsconfig.tsbuildinfo +1 -0
- package/build/treb-export/src/address-type.d.ts +34 -0
- package/build/treb-export/src/address-type.js +53 -0
- package/build/treb-export/src/address-type.js.map +1 -0
- package/build/treb-export/src/base-template.d.ts +1 -0
- package/build/treb-export/src/base-template.js +22 -0
- package/build/treb-export/src/base-template.js.map +1 -0
- package/build/treb-export/src/column-width.d.ts +2 -0
- package/build/treb-export/src/column-width.js +80 -0
- package/build/treb-export/src/column-width.js.map +1 -0
- package/build/treb-export/src/drawing/bubble-chart-template.d.ts +514 -0
- package/build/treb-export/src/drawing/bubble-chart-template.js +544 -0
- package/build/treb-export/src/drawing/bubble-chart-template.js.map +1 -0
- package/build/treb-export/src/drawing/chart-template-components2.d.ts +365 -0
- package/build/treb-export/src/drawing/chart-template-components2.js +386 -0
- package/build/treb-export/src/drawing/chart-template-components2.js.map +1 -0
- package/build/treb-export/src/drawing/chart.d.ts +26 -0
- package/build/treb-export/src/drawing/chart.js +247 -0
- package/build/treb-export/src/drawing/chart.js.map +1 -0
- package/build/treb-export/src/drawing/column-chart-template2.d.ts +490 -0
- package/build/treb-export/src/drawing/column-chart-template2.js +518 -0
- package/build/treb-export/src/drawing/column-chart-template2.js.map +1 -0
- package/build/treb-export/src/drawing/donut-chart-template2.d.ts +272 -0
- package/build/treb-export/src/drawing/donut-chart-template2.js +293 -0
- package/build/treb-export/src/drawing/donut-chart-template2.js.map +1 -0
- package/build/treb-export/src/drawing/drawing.d.ts +49 -0
- package/build/treb-export/src/drawing/drawing.js +193 -0
- package/build/treb-export/src/drawing/drawing.js.map +1 -0
- package/build/treb-export/src/drawing/embedded-image.d.ts +12 -0
- package/build/treb-export/src/drawing/embedded-image.js +54 -0
- package/build/treb-export/src/drawing/embedded-image.js.map +1 -0
- package/build/treb-export/src/drawing/scatter-chart-template2.d.ts +520 -0
- package/build/treb-export/src/drawing/scatter-chart-template2.js +551 -0
- package/build/treb-export/src/drawing/scatter-chart-template2.js.map +1 -0
- package/build/treb-export/src/export.d.ts +72 -0
- package/build/treb-export/src/export.js +2039 -0
- package/build/treb-export/src/export.js.map +1 -0
- package/build/treb-export/src/import-export-messages.d.ts +31 -0
- package/build/treb-export/src/import-export-messages.js +22 -0
- package/build/treb-export/src/import-export-messages.js.map +1 -0
- package/build/treb-export/src/import.d.ts +33 -0
- package/build/treb-export/src/import.js +1258 -0
- package/build/treb-export/src/import.js.map +1 -0
- package/build/treb-export/src/index.worker.d.ts +1 -0
- package/build/treb-export/src/index.worker.js +93 -0
- package/build/treb-export/src/index.worker.js.map +1 -0
- package/build/treb-export/src/metadata.d.ts +51 -0
- package/build/treb-export/src/metadata.js +153 -0
- package/build/treb-export/src/metadata.js.map +1 -0
- package/build/treb-export/src/ooxml.d.ts +7 -0
- package/build/treb-export/src/ooxml.js +41 -0
- package/build/treb-export/src/ooxml.js.map +1 -0
- package/build/treb-export/src/relationship.d.ts +8 -0
- package/build/treb-export/src/relationship.js +27 -0
- package/build/treb-export/src/relationship.js.map +1 -0
- package/build/treb-export/src/shared-strings.d.ts +11 -0
- package/build/treb-export/src/shared-strings.js +105 -0
- package/build/treb-export/src/shared-strings.js.map +1 -0
- package/build/treb-export/src/template-2.d.ts +1 -0
- package/build/treb-export/src/template-2.js +22 -0
- package/build/treb-export/src/template-2.js.map +1 -0
- package/build/treb-export/src/unescape_xml.d.ts +1 -0
- package/build/treb-export/src/unescape_xml.js +61 -0
- package/build/treb-export/src/unescape_xml.js.map +1 -0
- package/build/treb-export/src/workbook-sheet.d.ts +75 -0
- package/build/treb-export/src/workbook-sheet.js +128 -0
- package/build/treb-export/src/workbook-sheet.js.map +1 -0
- package/build/treb-export/src/workbook-style.d.ts +110 -0
- package/build/treb-export/src/workbook-style.js +1134 -0
- package/build/treb-export/src/workbook-style.js.map +1 -0
- package/build/treb-export/src/workbook-theme.d.ts +13 -0
- package/build/treb-export/src/workbook-theme.js +85 -0
- package/build/treb-export/src/workbook-theme.js.map +1 -0
- package/build/treb-export/src/workbook.d.ts +123 -0
- package/build/treb-export/src/workbook.js +644 -0
- package/build/treb-export/src/workbook.js.map +1 -0
- package/build/treb-export/src/xml-test.d.ts +9 -0
- package/build/treb-export/src/xml-test.js +52 -0
- package/build/treb-export/src/xml-test.js.map +1 -0
- package/build/treb-export/src/xml-utils.d.ts +76 -0
- package/build/treb-export/src/xml-utils.js +223 -0
- package/build/treb-export/src/xml-utils.js.map +1 -0
- package/build/treb-export/src/zip-wrapper.d.ts +22 -0
- package/build/treb-export/src/zip-wrapper.js +93 -0
- package/build/treb-export/src/zip-wrapper.js.map +1 -0
- package/build/treb-format/src/format.d.ts +130 -0
- package/build/treb-format/src/format.js +805 -0
- package/build/treb-format/src/format.js.map +1 -0
- package/build/treb-format/src/format_cache.d.ts +55 -0
- package/build/treb-format/src/format_cache.js +166 -0
- package/build/treb-format/src/format_cache.js.map +1 -0
- package/build/treb-format/src/format_parser.d.ts +70 -0
- package/build/treb-format/src/format_parser.js +618 -0
- package/build/treb-format/src/format_parser.js.map +1 -0
- package/build/treb-format/src/index.d.ts +4 -0
- package/build/treb-format/src/index.js +25 -0
- package/build/treb-format/src/index.js.map +1 -0
- package/build/treb-format/src/number_format_section.d.ts +58 -0
- package/build/treb-format/src/number_format_section.js +78 -0
- package/build/treb-format/src/number_format_section.js.map +1 -0
- package/build/treb-format/src/value_parser.d.ts +48 -0
- package/build/treb-format/src/value_parser.js +244 -0
- package/build/treb-format/src/value_parser.js.map +1 -0
- package/build/treb-grid/src/editors/autocomplete.d.ts +39 -0
- package/build/treb-grid/src/editors/autocomplete.js +316 -0
- package/build/treb-grid/src/editors/autocomplete.js.map +1 -0
- package/build/treb-grid/src/editors/autocomplete_matcher.d.ts +74 -0
- package/build/treb-grid/src/editors/autocomplete_matcher.js +212 -0
- package/build/treb-grid/src/editors/autocomplete_matcher.js.map +1 -0
- package/build/treb-grid/src/editors/editor.d.ts +214 -0
- package/build/treb-grid/src/editors/editor.js +879 -0
- package/build/treb-grid/src/editors/editor.js.map +1 -0
- package/build/treb-grid/src/editors/external_editor.d.ts +11 -0
- package/build/treb-grid/src/editors/external_editor.js +118 -0
- package/build/treb-grid/src/editors/external_editor.js.map +1 -0
- package/build/treb-grid/src/editors/formula_bar.d.ts +85 -0
- package/build/treb-grid/src/editors/formula_bar.js +444 -0
- package/build/treb-grid/src/editors/formula_bar.js.map +1 -0
- package/build/treb-grid/src/editors/overlay_editor.d.ts +85 -0
- package/build/treb-grid/src/editors/overlay_editor.js +353 -0
- package/build/treb-grid/src/editors/overlay_editor.js.map +1 -0
- package/build/treb-grid/src/index.d.ts +12 -0
- package/build/treb-grid/src/index.js +28 -0
- package/build/treb-grid/src/index.js.map +1 -0
- package/build/treb-grid/src/layout/base_layout.d.ts +346 -0
- package/build/treb-grid/src/layout/base_layout.js +2050 -0
- package/build/treb-grid/src/layout/base_layout.js.map +1 -0
- package/build/treb-grid/src/layout/grid_layout.d.ts +19 -0
- package/build/treb-grid/src/layout/grid_layout.js +235 -0
- package/build/treb-grid/src/layout/grid_layout.js.map +1 -0
- package/build/treb-grid/src/layout/mock-layout.d.ts +10 -0
- package/build/treb-grid/src/layout/mock-layout.js +37 -0
- package/build/treb-grid/src/layout/mock-layout.js.map +1 -0
- package/build/treb-grid/src/render/selection-renderer.d.ts +97 -0
- package/build/treb-grid/src/render/selection-renderer.js +315 -0
- package/build/treb-grid/src/render/selection-renderer.js.map +1 -0
- package/build/treb-grid/src/render/svg_header_overlay.d.ts +20 -0
- package/build/treb-grid/src/render/svg_header_overlay.js +76 -0
- package/build/treb-grid/src/render/svg_header_overlay.js.map +1 -0
- package/build/treb-grid/src/render/svg_selection_block.d.ts +27 -0
- package/build/treb-grid/src/render/svg_selection_block.js +106 -0
- package/build/treb-grid/src/render/svg_selection_block.js.map +1 -0
- package/build/treb-grid/src/render/tile_renderer.d.ts +121 -0
- package/build/treb-grid/src/render/tile_renderer.js +1609 -0
- package/build/treb-grid/src/render/tile_renderer.js.map +1 -0
- package/build/treb-grid/src/types/border_constants.d.ts +9 -0
- package/build/treb-grid/src/types/border_constants.js +34 -0
- package/build/treb-grid/src/types/border_constants.js.map +1 -0
- package/build/treb-grid/src/types/clipboard_data.d.ts +11 -0
- package/build/treb-grid/src/types/clipboard_data.js +22 -0
- package/build/treb-grid/src/types/clipboard_data.js.map +1 -0
- package/build/treb-grid/src/types/clipboard_data2.d.ts +46 -0
- package/build/treb-grid/src/types/clipboard_data2.js +22 -0
- package/build/treb-grid/src/types/clipboard_data2.js.map +1 -0
- package/build/treb-grid/src/types/drag_mask.d.ts +10 -0
- package/build/treb-grid/src/types/drag_mask.js +78 -0
- package/build/treb-grid/src/types/drag_mask.js.map +1 -0
- package/build/treb-grid/src/types/external_editor_config.d.ts +33 -0
- package/build/treb-grid/src/types/external_editor_config.js +22 -0
- package/build/treb-grid/src/types/external_editor_config.js.map +1 -0
- package/build/treb-grid/src/types/grid.d.ts +806 -0
- package/build/treb-grid/src/types/grid.js +6410 -0
- package/build/treb-grid/src/types/grid.js.map +1 -0
- package/build/treb-grid/src/types/grid_base.d.ts +442 -0
- package/build/treb-grid/src/types/grid_base.js +3523 -0
- package/build/treb-grid/src/types/grid_base.js.map +1 -0
- package/build/treb-grid/src/types/grid_command.d.ts +408 -0
- package/build/treb-grid/src/types/grid_command.js +75 -0
- package/build/treb-grid/src/types/grid_command.js.map +1 -0
- package/build/treb-grid/src/types/grid_events.d.ts +93 -0
- package/build/treb-grid/src/types/grid_events.js +36 -0
- package/build/treb-grid/src/types/grid_events.js.map +1 -0
- package/build/treb-grid/src/types/grid_options.d.ts +50 -0
- package/build/treb-grid/src/types/grid_options.js +34 -0
- package/build/treb-grid/src/types/grid_options.js.map +1 -0
- package/build/treb-grid/src/types/scale-control.d.ts +21 -0
- package/build/treb-grid/src/types/scale-control.js +148 -0
- package/build/treb-grid/src/types/scale-control.js.map +1 -0
- package/build/treb-grid/src/types/set_range_options.d.ts +24 -0
- package/build/treb-grid/src/types/set_range_options.js +22 -0
- package/build/treb-grid/src/types/set_range_options.js.map +1 -0
- package/build/treb-grid/src/types/tab_bar.d.ts +84 -0
- package/build/treb-grid/src/types/tab_bar.js +426 -0
- package/build/treb-grid/src/types/tab_bar.js.map +1 -0
- package/build/treb-grid/src/types/tile.d.ts +29 -0
- package/build/treb-grid/src/types/tile.js +22 -0
- package/build/treb-grid/src/types/tile.js.map +1 -0
- package/build/treb-grid/src/types/update_flags.d.ts +48 -0
- package/build/treb-grid/src/types/update_flags.js +22 -0
- package/build/treb-grid/src/types/update_flags.js.map +1 -0
- package/build/treb-grid/src/util/fontmetrics.d.ts +21 -0
- package/build/treb-grid/src/util/fontmetrics.js +82 -0
- package/build/treb-grid/src/util/fontmetrics.js.map +1 -0
- package/build/treb-grid/src/util/ua.d.ts +33 -0
- package/build/treb-grid/src/util/ua.js +86 -0
- package/build/treb-grid/src/util/ua.js.map +1 -0
- package/build/treb-parser/src/csv-parser.d.ts +13 -0
- package/build/treb-parser/src/csv-parser.js +107 -0
- package/build/treb-parser/src/csv-parser.js.map +1 -0
- package/build/treb-parser/src/index.d.ts +4 -0
- package/build/treb-parser/src/index.js +25 -0
- package/build/treb-parser/src/index.js.map +1 -0
- package/build/treb-parser/src/md-parser.d.ts +97 -0
- package/build/treb-parser/src/md-parser.js +403 -0
- package/build/treb-parser/src/md-parser.js.map +1 -0
- package/build/treb-parser/src/parser-types.d.ts +345 -0
- package/build/treb-parser/src/parser-types.js +53 -0
- package/build/treb-parser/src/parser-types.js.map +1 -0
- package/build/treb-parser/src/parser.d.ts +422 -0
- package/build/treb-parser/src/parser.js +2418 -0
- package/build/treb-parser/src/parser.js.map +1 -0
- package/build/treb-utils/src/event_source.d.ts +34 -0
- package/build/treb-utils/src/event_source.js +110 -0
- package/build/treb-utils/src/event_source.js.map +1 -0
- package/build/treb-utils/src/ievent_source.d.ts +9 -0
- package/build/treb-utils/src/ievent_source.js +22 -0
- package/build/treb-utils/src/ievent_source.js.map +1 -0
- package/build/treb-utils/src/index.d.ts +6 -0
- package/build/treb-utils/src/index.js +30 -0
- package/build/treb-utils/src/index.js.map +1 -0
- package/build/treb-utils/src/measurement.d.ts +42 -0
- package/build/treb-utils/src/measurement.js +145 -0
- package/build/treb-utils/src/measurement.js.map +1 -0
- package/build/treb-utils/src/scale.d.ts +16 -0
- package/build/treb-utils/src/scale.js +106 -0
- package/build/treb-utils/src/scale.js.map +1 -0
- package/build/treb-utils/src/serialize_html.d.ts +5 -0
- package/build/treb-utils/src/serialize_html.js +128 -0
- package/build/treb-utils/src/serialize_html.js.map +1 -0
- package/build/treb-utils/src/validate_uri.d.ts +20 -0
- package/build/treb-utils/src/validate_uri.js +55 -0
- package/build/treb-utils/src/validate_uri.js.map +1 -0
- package/dist/{chunk-XD5PEZBZ.mjs → chunk-A2NJA5VB.mjs} +1 -1
- package/dist/treb-export-worker.mjs +2 -2
- package/dist/treb-spreadsheet.mjs +4 -4
- package/dist/treb.d.ts +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,3523 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of TREB.
|
|
3
|
+
*
|
|
4
|
+
* TREB is free software: you can redistribute it and/or modify it under the
|
|
5
|
+
* terms of the GNU General Public License as published by the Free Software
|
|
6
|
+
* Foundation, either version 3 of the License, or (at your option) any
|
|
7
|
+
* later version.
|
|
8
|
+
*
|
|
9
|
+
* TREB is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
10
|
+
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
11
|
+
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
12
|
+
* details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU General Public License along
|
|
15
|
+
* with TREB. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
*
|
|
17
|
+
* Copyright 2022-2026 trebco, llc.
|
|
18
|
+
* info@treb.app
|
|
19
|
+
*
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* grid base is a superclass for grid that takes over all (most) of the
|
|
23
|
+
* data operations, leaving UI operations (painting and interacting, plus
|
|
24
|
+
* layout) in the grid subclass.
|
|
25
|
+
*
|
|
26
|
+
* this is part of an effort to support running outside of the browser,
|
|
27
|
+
* but still using the command log to handle deltas.
|
|
28
|
+
*
|
|
29
|
+
* this turns out to be a little like the (old) layout where we had modern
|
|
30
|
+
* and legacy layouts -- a lot of stuff can be reused, but a lot can't.
|
|
31
|
+
*
|
|
32
|
+
* calling this "grid" doesn't really make sense anymore, but we're not in
|
|
33
|
+
* a hurry to change it either.
|
|
34
|
+
*
|
|
35
|
+
*/
|
|
36
|
+
import { EventSource } from 'treb-utils';
|
|
37
|
+
import { IllegalSheetNameRegex, ParseCSV, DecimalMarkType } from 'treb-parser';
|
|
38
|
+
import { Area, IsCellAddress, ValueType, DefaultTableSortOptions } from 'treb-base-types';
|
|
39
|
+
import { Sheet } from 'treb-data-model';
|
|
40
|
+
import { AutocompleteMatcher } from '../editors/autocomplete_matcher';
|
|
41
|
+
import { NumberFormat, ValueParser } from 'treb-format';
|
|
42
|
+
import { ErrorCode } from './grid_events';
|
|
43
|
+
import { DefaultGridOptions } from './grid_options';
|
|
44
|
+
import { BorderConstants } from './border_constants';
|
|
45
|
+
import { CommandKey } from './grid_command';
|
|
46
|
+
// this is an assert, bascially, for completeness. we could probably
|
|
47
|
+
// resolve the eslint issue (there's no type option) but not sure it's
|
|
48
|
+
// useful to do so
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
50
|
+
const AssertNever = (value) => {
|
|
51
|
+
console.error('invalid case');
|
|
52
|
+
};
|
|
53
|
+
export class GridBase {
|
|
54
|
+
// --- public members --------------------------------------------------------
|
|
55
|
+
/** events */
|
|
56
|
+
grid_events = new EventSource();
|
|
57
|
+
/** for recording */
|
|
58
|
+
command_log = new EventSource();
|
|
59
|
+
model;
|
|
60
|
+
view;
|
|
61
|
+
// --- public accessors ------------------------------------------------------
|
|
62
|
+
get active_sheet() {
|
|
63
|
+
return this.view.active_sheet;
|
|
64
|
+
}
|
|
65
|
+
set active_sheet(sheet) {
|
|
66
|
+
this.view.active_sheet = sheet;
|
|
67
|
+
}
|
|
68
|
+
/** access the view index, if needed */
|
|
69
|
+
get view_index() {
|
|
70
|
+
return this.view.view_index;
|
|
71
|
+
}
|
|
72
|
+
// --- protected members -----------------------------------------------------
|
|
73
|
+
/**
|
|
74
|
+
* switching to a stack, in case batching is nested. we don't need
|
|
75
|
+
* actual data so (atm) just count the depth.
|
|
76
|
+
*/
|
|
77
|
+
batch = 0; // false;
|
|
78
|
+
/**
|
|
79
|
+
* if any batch method along the way requests a paint update, we
|
|
80
|
+
* want to toll it until the last batch call is complete, but we don't
|
|
81
|
+
* want to lose it. just remember to reset. [FIXME: isn't tolling this
|
|
82
|
+
* paint implicit, since it's async? ...]
|
|
83
|
+
*/
|
|
84
|
+
batch_paint = false;
|
|
85
|
+
batch_events = [];
|
|
86
|
+
/**
|
|
87
|
+
* single instance of AC. editors (function bar, ICE) have references.
|
|
88
|
+
* this is in base, instead of subclass, because we use it to check
|
|
89
|
+
* for valid names.
|
|
90
|
+
*/
|
|
91
|
+
autocomplete_matcher = new AutocompleteMatcher();
|
|
92
|
+
/**
|
|
93
|
+
* flags/state (used for some recordkeeping -- not super important)
|
|
94
|
+
*/
|
|
95
|
+
flags = {};
|
|
96
|
+
/** */
|
|
97
|
+
options;
|
|
98
|
+
/**
|
|
99
|
+
* spreadsheet language parser. used to pull out address
|
|
100
|
+
* references from functions, for highlighting
|
|
101
|
+
*
|
|
102
|
+
* ...
|
|
103
|
+
*
|
|
104
|
+
* it's used for lots of stuff now, in addition to highlighting.
|
|
105
|
+
* copy/paste with translation; csv; defines; and some other stuff.
|
|
106
|
+
* still would like to share w/ parent though, if possible.
|
|
107
|
+
*
|
|
108
|
+
*
|
|
109
|
+
* FIXME: need a way to share/pass parser flags
|
|
110
|
+
* UPDATE: sharing parser w/ owner (embedded sheet)
|
|
111
|
+
*/
|
|
112
|
+
parser;
|
|
113
|
+
// --- constructor -----------------------------------------------------------
|
|
114
|
+
constructor(options = {}, model) {
|
|
115
|
+
this.model = model;
|
|
116
|
+
this.view = {
|
|
117
|
+
active_sheet: this.model.sheets.list[0],
|
|
118
|
+
view_index: this.model.view_count++,
|
|
119
|
+
};
|
|
120
|
+
// shared parser
|
|
121
|
+
this.parser = model.parser;
|
|
122
|
+
// apply default options, meaning that you need to explicitly set/unset
|
|
123
|
+
// in order to change behavior. FIXME: this is ok for flat structure, but
|
|
124
|
+
// anything more complicated will need a nested merge
|
|
125
|
+
this.options = { ...DefaultGridOptions, ...options };
|
|
126
|
+
}
|
|
127
|
+
// --- API methods -----------------------------------------------------------
|
|
128
|
+
RemoveConditionalFormat(options) {
|
|
129
|
+
this.ExecCommand({
|
|
130
|
+
key: CommandKey.RemoveConditionalFormat,
|
|
131
|
+
...options,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
AddConditionalFormat(format) {
|
|
135
|
+
this.ExecCommand({
|
|
136
|
+
key: CommandKey.AddConditionalFormat,
|
|
137
|
+
format,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
/** remove a table. doesn't remove any data, just removes the overlay. */
|
|
141
|
+
RemoveTable(table) {
|
|
142
|
+
this.ExecCommand({
|
|
143
|
+
key: CommandKey.RemoveTable,
|
|
144
|
+
table,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* create a table in the given area. the area cannot contain any
|
|
149
|
+
* merge cells, arrays, or be part of another table. if you add a table
|
|
150
|
+
* with a totals row, we don't insert a new row -- allocate enough space
|
|
151
|
+
* when you create it.
|
|
152
|
+
*
|
|
153
|
+
* @param area - the total area for the table, including headers and totals
|
|
154
|
+
* @param totals - set true to include a totals row. tables have different
|
|
155
|
+
* formatting and slightly different behavior when there's a totals row.
|
|
156
|
+
*/
|
|
157
|
+
InsertTable(area, totals = true, sortable = undefined, theme) {
|
|
158
|
+
// we should validate here, so that we can throw.
|
|
159
|
+
if (!area.start.sheet_id) {
|
|
160
|
+
area.start.sheet_id = this.active_sheet.id;
|
|
161
|
+
}
|
|
162
|
+
const sheet = this.FindSheet(area);
|
|
163
|
+
for (let row = area.start.row; row <= area.end.row; row++) {
|
|
164
|
+
for (let column = area.start.column; column <= area.end.column; column++) {
|
|
165
|
+
const cell = sheet.cells.GetCell({ row, column }, false);
|
|
166
|
+
if (cell && (cell.area || cell.merge_area || cell.table)) {
|
|
167
|
+
// throw new Error('invalid area for table');
|
|
168
|
+
this.Error(ErrorCode.invalid_area_for_table);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
this.ExecCommand({
|
|
174
|
+
key: CommandKey.InsertTable,
|
|
175
|
+
area: JSON.parse(JSON.stringify(area)),
|
|
176
|
+
totals,
|
|
177
|
+
sortable,
|
|
178
|
+
theme,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* activate sheet, by name or index number
|
|
183
|
+
* @param sheet number (index into the array) or string (name)
|
|
184
|
+
*/
|
|
185
|
+
ActivateSheet(sheet, user) {
|
|
186
|
+
const index = (typeof sheet === 'number') ? sheet : undefined;
|
|
187
|
+
const name = (typeof sheet === 'string') ? sheet : undefined;
|
|
188
|
+
this.ExecCommand({
|
|
189
|
+
key: CommandKey.ActivateSheet,
|
|
190
|
+
index,
|
|
191
|
+
name,
|
|
192
|
+
user,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* activate sheet, by ID
|
|
197
|
+
*/
|
|
198
|
+
ActivateSheetID(id) {
|
|
199
|
+
this.ExecCommand({
|
|
200
|
+
key: CommandKey.ActivateSheet,
|
|
201
|
+
id,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* duplicate sheet by index or (omitting index) the current active sheet
|
|
206
|
+
*/
|
|
207
|
+
DuplicateSheet(index, name, insert_before) {
|
|
208
|
+
const command = {
|
|
209
|
+
key: CommandKey.DuplicateSheet,
|
|
210
|
+
new_name: name,
|
|
211
|
+
insert_before,
|
|
212
|
+
};
|
|
213
|
+
if (typeof index === 'undefined') {
|
|
214
|
+
command.id = this.active_sheet.id;
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
command.index = index;
|
|
218
|
+
}
|
|
219
|
+
this.ExecCommand(command);
|
|
220
|
+
}
|
|
221
|
+
AddSheet(name) {
|
|
222
|
+
this.ExecCommand({
|
|
223
|
+
key: CommandKey.AddSheet,
|
|
224
|
+
name,
|
|
225
|
+
show: true,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* delete sheet, by index or (omitting index) the current active sheet
|
|
230
|
+
*/
|
|
231
|
+
DeleteSheet(index) {
|
|
232
|
+
if (typeof index === 'undefined') {
|
|
233
|
+
if (!this.model.sheets.list.some((sheet, i) => {
|
|
234
|
+
if (sheet === this.active_sheet) {
|
|
235
|
+
index = i;
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
return false;
|
|
239
|
+
})) {
|
|
240
|
+
throw new Error('invalid index');
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
this.ExecCommand({
|
|
244
|
+
key: CommandKey.DeleteSheet,
|
|
245
|
+
index,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
/** insert sheet at the given index (or current index) */
|
|
249
|
+
InsertSheet(index, name) {
|
|
250
|
+
if (typeof index === 'undefined') {
|
|
251
|
+
if (!this.model.sheets.list.some((sheet, i) => {
|
|
252
|
+
if (sheet === this.active_sheet) {
|
|
253
|
+
index = i + 1;
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
return false;
|
|
257
|
+
})) {
|
|
258
|
+
throw new Error('invalid index');
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
this.ExecCommand({
|
|
262
|
+
key: CommandKey.AddSheet,
|
|
263
|
+
insert_index: index,
|
|
264
|
+
name,
|
|
265
|
+
show: true,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
DeleteSheetID(id) {
|
|
269
|
+
this.ExecCommand({
|
|
270
|
+
key: CommandKey.DeleteSheet,
|
|
271
|
+
id,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* clear sheet, reset all data
|
|
276
|
+
*/
|
|
277
|
+
Reset() {
|
|
278
|
+
this.ExecCommand({ key: CommandKey.Reset });
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* set hyperlink, like set note
|
|
282
|
+
*/
|
|
283
|
+
SetLink(address, reference) {
|
|
284
|
+
/*
|
|
285
|
+
if (!address) {
|
|
286
|
+
if (this.primary_selection.empty) return;
|
|
287
|
+
address = this.primary_selection.target;
|
|
288
|
+
}
|
|
289
|
+
*/
|
|
290
|
+
this.ExecCommand({
|
|
291
|
+
key: CommandKey.SetLink,
|
|
292
|
+
area: address,
|
|
293
|
+
reference,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
ShowAll() {
|
|
297
|
+
// obviously there are better ways to do this, but this
|
|
298
|
+
// will use the execcommand system and _should_ only fire
|
|
299
|
+
// a single event (FIXME: check)
|
|
300
|
+
const commands = [];
|
|
301
|
+
for (let index = 0; index < this.model.sheets.length; index++) {
|
|
302
|
+
commands.push({
|
|
303
|
+
key: CommandKey.ShowSheet,
|
|
304
|
+
index,
|
|
305
|
+
show: true,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
this.ExecCommand(commands);
|
|
309
|
+
}
|
|
310
|
+
ShowSheet(index = 0, show = true) {
|
|
311
|
+
const command = {
|
|
312
|
+
key: CommandKey.ShowSheet,
|
|
313
|
+
show,
|
|
314
|
+
};
|
|
315
|
+
if (typeof index === 'string') {
|
|
316
|
+
command.name = index;
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
command.index = index;
|
|
320
|
+
}
|
|
321
|
+
this.ExecCommand(command);
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* sort table. column is absolute.
|
|
325
|
+
*/
|
|
326
|
+
SortTable(table, options = {}) {
|
|
327
|
+
//
|
|
328
|
+
// table typically has an actual area, while we want a plain
|
|
329
|
+
// object in the command queue for serialization purposes. not
|
|
330
|
+
// sure how we wound up with this situation, it's problematic.
|
|
331
|
+
//
|
|
332
|
+
this.ExecCommand({
|
|
333
|
+
key: CommandKey.SortTable,
|
|
334
|
+
table: JSON.parse(JSON.stringify(table)),
|
|
335
|
+
...DefaultTableSortOptions,
|
|
336
|
+
...options,
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
/** return freeze area */
|
|
340
|
+
GetFreeze() {
|
|
341
|
+
return { ...this.active_sheet.freeze };
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* insert rows(s) at some specific point
|
|
345
|
+
*/
|
|
346
|
+
InsertRows(before_row = 0, count = 1) {
|
|
347
|
+
this.ExecCommand({
|
|
348
|
+
key: CommandKey.InsertRows,
|
|
349
|
+
before_row,
|
|
350
|
+
count,
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* return the table (if any) at the given address
|
|
355
|
+
*/
|
|
356
|
+
GetTableReference(address) {
|
|
357
|
+
const sheet = this.model.sheets.Find(address.sheet_id || this.active_sheet.id);
|
|
358
|
+
return sheet?.CellData(address).table || undefined;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* reset sheet, set data from CSV
|
|
362
|
+
*
|
|
363
|
+
* FIXME: this is problematic, because it runs around the exec command
|
|
364
|
+
* system. however it doesn't seem like a good candidate for a separate
|
|
365
|
+
* command. it should maybe move to the import class? (...)
|
|
366
|
+
*
|
|
367
|
+
* one problem with that is that import is really, really heavy (jszip).
|
|
368
|
+
* it seems wasteful to require all that just to import csv.
|
|
369
|
+
*/
|
|
370
|
+
FromCSV(text) {
|
|
371
|
+
// CSV assumes dot-decimal, correct? if we want to use the
|
|
372
|
+
// parser we will have to check (and set/reset) the separator
|
|
373
|
+
this.parser.Save();
|
|
374
|
+
this.parser.SetLocaleSettings(DecimalMarkType.Period);
|
|
375
|
+
/*
|
|
376
|
+
const toggle_separator = this.parser.decimal_mark === DecimalMarkType.Comma;
|
|
377
|
+
|
|
378
|
+
if (toggle_separator)
|
|
379
|
+
{
|
|
380
|
+
// swap
|
|
381
|
+
this.parser.argument_separator = ArgumentSeparatorType.Comma;
|
|
382
|
+
this.parser.decimal_mark = DecimalMarkType.Period;
|
|
383
|
+
}
|
|
384
|
+
*/
|
|
385
|
+
const records = ParseCSV(text);
|
|
386
|
+
const arr = records.map((record) => record.map((field) => {
|
|
387
|
+
if (field) {
|
|
388
|
+
const tmp = this.parser.Parse(field);
|
|
389
|
+
if (tmp.expression?.type === 'complex') {
|
|
390
|
+
return tmp.expression;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return ValueParser.TryParse(field).value;
|
|
394
|
+
}));
|
|
395
|
+
/*
|
|
396
|
+
if (toggle_separator) {
|
|
397
|
+
// reset
|
|
398
|
+
this.parser.argument_separator = ArgumentSeparatorType.Semicolon;
|
|
399
|
+
this.parser.decimal_mark = DecimalMarkType.Comma;
|
|
400
|
+
}
|
|
401
|
+
*/
|
|
402
|
+
this.parser.Restore();
|
|
403
|
+
const end = {
|
|
404
|
+
row: Math.max(0, arr.length - 1),
|
|
405
|
+
column: arr.reduce((max, row) => Math.max(max, Math.max(0, row.length - 1)), 0),
|
|
406
|
+
};
|
|
407
|
+
// NOTE: SetRange here does not need to be translated, because
|
|
408
|
+
// we're not expecting spreadsheet functions in the CSV. CSV should
|
|
409
|
+
// be data only. Famous last words.
|
|
410
|
+
this.ExecCommand([
|
|
411
|
+
{ key: CommandKey.Reset },
|
|
412
|
+
{
|
|
413
|
+
key: CommandKey.SetRange,
|
|
414
|
+
area: { start: { row: 0, column: 0 }, end },
|
|
415
|
+
value: arr,
|
|
416
|
+
},
|
|
417
|
+
// we took this out because the data may require a layout update
|
|
418
|
+
// (rebuilding tiles); in that case, this will be duplicative. maybe
|
|
419
|
+
// should use setTimeout or some sort of queue...
|
|
420
|
+
// { key: CommandKey.ResizeColumns }, // auto
|
|
421
|
+
]);
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* insert column(s) at some specific point
|
|
425
|
+
*/
|
|
426
|
+
InsertColumns(before_column = 0, count = 1) {
|
|
427
|
+
this.ExecCommand({
|
|
428
|
+
key: CommandKey.InsertColumns,
|
|
429
|
+
before_column,
|
|
430
|
+
count,
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
/** move sheet (X) before sheet (Y) */
|
|
434
|
+
ReorderSheet(index, move_before) {
|
|
435
|
+
this.ExecCommand({
|
|
436
|
+
key: CommandKey.ReorderSheet,
|
|
437
|
+
index,
|
|
438
|
+
move_before,
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* rename active sheet
|
|
443
|
+
*/
|
|
444
|
+
RenameSheet(sheet, name) {
|
|
445
|
+
this.ExecCommand({
|
|
446
|
+
key: CommandKey.RenameSheet,
|
|
447
|
+
new_name: name,
|
|
448
|
+
id: sheet.id,
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* freeze rows or columns. set to 0 (or call with no arguments) to un-freeze.
|
|
453
|
+
*
|
|
454
|
+
* highglight is shown by default, but we can hide it(mostly for document load)
|
|
455
|
+
*/
|
|
456
|
+
Freeze(rows = 0, columns = 0, highlight_transition = true) {
|
|
457
|
+
this.ExecCommand({
|
|
458
|
+
key: CommandKey.Freeze,
|
|
459
|
+
rows,
|
|
460
|
+
columns,
|
|
461
|
+
highlight_transition,
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* API method
|
|
466
|
+
*/
|
|
467
|
+
SetRowHeight(row, height, shrink = true) {
|
|
468
|
+
this.ExecCommand({
|
|
469
|
+
key: CommandKey.ResizeRows,
|
|
470
|
+
row,
|
|
471
|
+
height,
|
|
472
|
+
shrink,
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* API method
|
|
477
|
+
*
|
|
478
|
+
* @param column - column, columns, or undefined means all columns
|
|
479
|
+
* @param width - target width, or undefined means auto-size
|
|
480
|
+
* @param allow_shrinking - for auto-size, allow shrinking. defaults to true.
|
|
481
|
+
* set false to disallow shrinking.
|
|
482
|
+
*/
|
|
483
|
+
SetColumnWidth(column, width, allow_shrinking) {
|
|
484
|
+
this.ExecCommand({
|
|
485
|
+
key: CommandKey.ResizeColumns,
|
|
486
|
+
column,
|
|
487
|
+
width,
|
|
488
|
+
allow_shrinking,
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* filter table. what this means is "show the rows that match the filter
|
|
493
|
+
* and hide the other rows". it doesn't actually change data, but it does
|
|
494
|
+
* show/hide rows which (now) has some data effects.
|
|
495
|
+
*
|
|
496
|
+
* note that we don't pass the filter command through the command queue.
|
|
497
|
+
* it uses a callback, so that would not work. rather we filter first,
|
|
498
|
+
* then send hide/show row commands through the command queue. that will
|
|
499
|
+
* propagate updates.
|
|
500
|
+
*/
|
|
501
|
+
FilterTable(table, column, filter) {
|
|
502
|
+
const command = [];
|
|
503
|
+
if (!table.area.start.sheet_id) {
|
|
504
|
+
throw new Error('invalid table area');
|
|
505
|
+
}
|
|
506
|
+
const sheet = this.model.sheets.Find(table.area.start.sheet_id);
|
|
507
|
+
if (!sheet) {
|
|
508
|
+
throw new Error('invalid table sheet');
|
|
509
|
+
}
|
|
510
|
+
const show_rows = [];
|
|
511
|
+
const hide_rows = [];
|
|
512
|
+
const end = table.totals_row ? table.area.end.row - 1 : table.area.end.row;
|
|
513
|
+
column += table.area.start.column;
|
|
514
|
+
for (let row = table.area.start.row + 1; row <= end; row++) {
|
|
515
|
+
const cell = sheet.CellData({ row, column });
|
|
516
|
+
const show = filter(cell);
|
|
517
|
+
const current = sheet.GetRowHeight(row);
|
|
518
|
+
if (show && !current) {
|
|
519
|
+
show_rows.push(row);
|
|
520
|
+
}
|
|
521
|
+
else if (!show && current) {
|
|
522
|
+
hide_rows.push(row);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
if (show_rows) {
|
|
526
|
+
command.push({
|
|
527
|
+
key: CommandKey.ResizeRows,
|
|
528
|
+
sheet_id: sheet.id,
|
|
529
|
+
row: show_rows,
|
|
530
|
+
height: sheet.default_row_height,
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
if (hide_rows) {
|
|
534
|
+
command.push({
|
|
535
|
+
key: CommandKey.ResizeRows,
|
|
536
|
+
sheet_id: sheet.id,
|
|
537
|
+
row: hide_rows,
|
|
538
|
+
height: 0,
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
if (command.length) {
|
|
542
|
+
this.ExecCommand(command);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* UpdateSheets means "set these as the sheets, drop any old stuff". there's
|
|
547
|
+
* an implicit reset (in fact we may do that twice in some cases).
|
|
548
|
+
*
|
|
549
|
+
* this is non-UI; specialization should handle the UI part
|
|
550
|
+
*/
|
|
551
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
552
|
+
UpdateSheets(data, render = false, activate_sheet) {
|
|
553
|
+
Sheet.Reset(); // reset ID generation
|
|
554
|
+
const sheets = data.map((sheet) => Sheet.FromJSON(sheet, this.model.theme_style_properties));
|
|
555
|
+
// ensure we have a sheets[0] so we can set active
|
|
556
|
+
if (sheets.length === 0) {
|
|
557
|
+
sheets.push(Sheet.Blank(this.model.theme_style_properties));
|
|
558
|
+
}
|
|
559
|
+
// now assign sheets
|
|
560
|
+
this.model.sheets.Assign(sheets);
|
|
561
|
+
this.ResetMetadata(); // FIXME: shouldn't we just set metadata from the file?
|
|
562
|
+
// set active
|
|
563
|
+
this.active_sheet = sheets[0];
|
|
564
|
+
// possibly set an active sheet on load (shortcut)
|
|
565
|
+
// could we not use a command for this?
|
|
566
|
+
if (activate_sheet) {
|
|
567
|
+
const sheet = this.model.sheets.Find(activate_sheet);
|
|
568
|
+
if (sheet) {
|
|
569
|
+
this.active_sheet = sheet;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
// NOTE: we're not handling annotations here. do we need to? (...)
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* set functions for AC matcher. should be called by calculator on init,
|
|
576
|
+
* or when any functions are added/removed.
|
|
577
|
+
*
|
|
578
|
+
* FIXME: we should use this to normalize function names, on insert and
|
|
579
|
+
* on paste (if we're doing that).
|
|
580
|
+
*
|
|
581
|
+
* FIXME: are named expressions included here? (this function predates
|
|
582
|
+
* named expressions).
|
|
583
|
+
*
|
|
584
|
+
*
|
|
585
|
+
* this moved to grid base because we use the list to check for conflicts
|
|
586
|
+
* when setting names.
|
|
587
|
+
*
|
|
588
|
+
*/
|
|
589
|
+
SetAutocompleteFunctions(functions) {
|
|
590
|
+
const expressions = [];
|
|
591
|
+
for (const entry of this.model.named.list) {
|
|
592
|
+
expressions.push({
|
|
593
|
+
name: entry.name,
|
|
594
|
+
named: true,
|
|
595
|
+
type: 'token',
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
/*
|
|
599
|
+
for (const name of this.model.named_expressions.keys()) {
|
|
600
|
+
expressions.push({
|
|
601
|
+
name,
|
|
602
|
+
named: true,
|
|
603
|
+
type: 'token',
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
*/
|
|
607
|
+
const consolidated = functions.slice(0).concat(expressions);
|
|
608
|
+
/*
|
|
609
|
+
const consolidated = functions.slice(0).concat(
|
|
610
|
+
this.model.named_ranges.List().map((named_range) => {
|
|
611
|
+
return {
|
|
612
|
+
name: named_range.name,
|
|
613
|
+
named: true,
|
|
614
|
+
type: 'token'
|
|
615
|
+
};
|
|
616
|
+
}),
|
|
617
|
+
expressions,
|
|
618
|
+
);
|
|
619
|
+
*/
|
|
620
|
+
this.autocomplete_matcher.SetFunctions(consolidated);
|
|
621
|
+
}
|
|
622
|
+
ResetMetadata() {
|
|
623
|
+
this.model.document_name = undefined;
|
|
624
|
+
this.model.user_data = undefined;
|
|
625
|
+
}
|
|
626
|
+
// --- protected methods -----------------------------------------------------
|
|
627
|
+
/**
|
|
628
|
+
* see ResizeRowsInternal
|
|
629
|
+
*/
|
|
630
|
+
ResizeColumnsInternal(command) {
|
|
631
|
+
const sheet = command.sheet_id ? this.FindSheet(command.sheet_id) : this.active_sheet;
|
|
632
|
+
// normalize
|
|
633
|
+
let column = command.column;
|
|
634
|
+
if (typeof column === 'undefined') {
|
|
635
|
+
column = [];
|
|
636
|
+
for (let i = 0; i < sheet.columns; i++)
|
|
637
|
+
column.push(i);
|
|
638
|
+
}
|
|
639
|
+
if (typeof column === 'number')
|
|
640
|
+
column = [column];
|
|
641
|
+
if (command.width) {
|
|
642
|
+
for (const entry of column) {
|
|
643
|
+
sheet.SetColumnWidth(entry, command.width);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
else {
|
|
647
|
+
console.error('auto size not supported');
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
RemoveAnnotationInternal(command) {
|
|
651
|
+
for (let i = 0; i < command.sheet.annotations.length; i++) {
|
|
652
|
+
if (command.annotation === command.sheet.annotations[i]) {
|
|
653
|
+
command.sheet.annotations.splice(i, 1);
|
|
654
|
+
// subclass only // this.layout.RemoveAnnotation(annotation);
|
|
655
|
+
// do we still need this message? not sure
|
|
656
|
+
this.grid_events.Publish({
|
|
657
|
+
type: 'annotation',
|
|
658
|
+
annotation: command.annotation,
|
|
659
|
+
event: 'delete',
|
|
660
|
+
});
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
/** placeholder */
|
|
666
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
667
|
+
CreateAnnotationInternal(command) {
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* resize rows. this supports auto size, but that will fail in !ui grid,
|
|
671
|
+
* because it uses HTML. also non-ui doesn't really need to worry about
|
|
672
|
+
* scale... we should split.
|
|
673
|
+
*/
|
|
674
|
+
ResizeRowsInternal(command) {
|
|
675
|
+
// we're guaranteed this now, we should have a way to represent that...
|
|
676
|
+
const sheet = command.sheet_id ? this.FindSheet(command.sheet_id) : this.active_sheet;
|
|
677
|
+
// normalize rows -> array. undefined means all rows.
|
|
678
|
+
let row = command.row;
|
|
679
|
+
if (typeof row === 'undefined') {
|
|
680
|
+
row = [];
|
|
681
|
+
for (let i = 0; i < sheet.rows; i++)
|
|
682
|
+
row.push(i);
|
|
683
|
+
}
|
|
684
|
+
if (typeof row === 'number')
|
|
685
|
+
row = [row];
|
|
686
|
+
// I guess this was intended to prevent auto-size, but what about 0?
|
|
687
|
+
if (command.height) {
|
|
688
|
+
for (const entry of row) {
|
|
689
|
+
sheet.SetRowHeight(entry, command.height);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
else {
|
|
693
|
+
console.error('auto size not supported');
|
|
694
|
+
}
|
|
695
|
+
return undefined;
|
|
696
|
+
}
|
|
697
|
+
ResetInternal() {
|
|
698
|
+
Sheet.Reset();
|
|
699
|
+
this.UpdateSheets([], true);
|
|
700
|
+
this.model.named.Reset();
|
|
701
|
+
this.model.macro_functions.clear(); // = {};
|
|
702
|
+
this.model.tables.clear();
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* check if we can paste into the target area(s). this will
|
|
706
|
+
* return false if the areas contain locked cells, or part of
|
|
707
|
+
* an array or merge but not the whole array or merge.
|
|
708
|
+
*
|
|
709
|
+
* @param areas
|
|
710
|
+
* @returns
|
|
711
|
+
*/
|
|
712
|
+
ValidatePasteAreas(areas) {
|
|
713
|
+
for (const area of areas) {
|
|
714
|
+
let sheet = this.active_sheet;
|
|
715
|
+
if (area.start.sheet_id && area.start.sheet_id !== sheet.id) {
|
|
716
|
+
sheet = this.model.sheets.Find(area.start.sheet_id);
|
|
717
|
+
}
|
|
718
|
+
if (!sheet) {
|
|
719
|
+
return false;
|
|
720
|
+
}
|
|
721
|
+
// let valid = true;
|
|
722
|
+
for (const cell of sheet.cells.Iterate(area)) {
|
|
723
|
+
if (cell.style?.locked) {
|
|
724
|
+
console.info('invalid: locked cells');
|
|
725
|
+
return false;
|
|
726
|
+
}
|
|
727
|
+
if (cell.merge_area) {
|
|
728
|
+
if (!area.Contains(cell.merge_area.start) || !area.Contains(cell.merge_area.end)) {
|
|
729
|
+
console.info('invalid: merge area');
|
|
730
|
+
return false;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
if (cell.area) {
|
|
734
|
+
if (!area.Contains(cell.area.start) || !area.Contains(cell.area.end)) {
|
|
735
|
+
console.info('invalid: array');
|
|
736
|
+
return false;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
/*
|
|
741
|
+
sheet.cells.Apply2(area, cell => {
|
|
742
|
+
if (cell.style?.locked) {
|
|
743
|
+
console.info('invalid: locked cells');
|
|
744
|
+
valid = false;
|
|
745
|
+
}
|
|
746
|
+
if (cell.merge_area) {
|
|
747
|
+
if (!area.Contains(cell.merge_area.start) || !area.Contains(cell.merge_area.end)) {
|
|
748
|
+
console.info('invalid: merge area');
|
|
749
|
+
valid = false;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
if (cell.area) {
|
|
753
|
+
if (!area.Contains(cell.area.start) || !area.Contains(cell.area.end)) {
|
|
754
|
+
console.info('invalid: array');
|
|
755
|
+
valid = false;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
return valid;
|
|
760
|
+
});
|
|
761
|
+
*/
|
|
762
|
+
// if (!valid) {
|
|
763
|
+
// return false;
|
|
764
|
+
// }
|
|
765
|
+
}
|
|
766
|
+
return true;
|
|
767
|
+
}
|
|
768
|
+
SetValidationInternal(command) {
|
|
769
|
+
const sheet = this.FindSheet(command.area);
|
|
770
|
+
if (!sheet) {
|
|
771
|
+
throw new Error('invalid sheet in set validation');
|
|
772
|
+
}
|
|
773
|
+
const target = { start: command.area.start, end: command.area.end };
|
|
774
|
+
if (command.range) {
|
|
775
|
+
sheet.AddValidation({
|
|
776
|
+
type: 'range',
|
|
777
|
+
error: !!command.error,
|
|
778
|
+
area: command.range,
|
|
779
|
+
target: [target],
|
|
780
|
+
});
|
|
781
|
+
/*
|
|
782
|
+
cell.validation = {
|
|
783
|
+
type: ValidationType.Range,
|
|
784
|
+
area: command.range,
|
|
785
|
+
error: !!command.error,
|
|
786
|
+
};
|
|
787
|
+
*/
|
|
788
|
+
}
|
|
789
|
+
else if (command.list) {
|
|
790
|
+
sheet.AddValidation({
|
|
791
|
+
type: 'list',
|
|
792
|
+
error: !!command.error,
|
|
793
|
+
list: JSON.parse(JSON.stringify(command.list)),
|
|
794
|
+
target: [target],
|
|
795
|
+
});
|
|
796
|
+
/*
|
|
797
|
+
cell.validation = {
|
|
798
|
+
type: ValidationType.List,
|
|
799
|
+
list: JSON.parse(JSON.stringify(command.list)),
|
|
800
|
+
error: !!command.error,
|
|
801
|
+
}
|
|
802
|
+
*/
|
|
803
|
+
}
|
|
804
|
+
else {
|
|
805
|
+
// cell.validation = undefined;
|
|
806
|
+
sheet.RemoveValidations(target);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* get values from a range of data
|
|
811
|
+
* @param area
|
|
812
|
+
*/
|
|
813
|
+
GetValidationRange(area) {
|
|
814
|
+
let list;
|
|
815
|
+
const sheet = this.FindSheet(area);
|
|
816
|
+
if (sheet) {
|
|
817
|
+
list = [];
|
|
818
|
+
// clamp to actual area to avoid screwing up sheet
|
|
819
|
+
// FIXME: what does that cause [problem with selections], why, and fix it
|
|
820
|
+
area = sheet.RealArea(new Area(area.start, area.end), true);
|
|
821
|
+
for (let row = area.start.row; row <= area.end.row; row++) {
|
|
822
|
+
for (let column = area.start.column; column <= area.end.column; column++) {
|
|
823
|
+
const cell = sheet.CellData({ row, column });
|
|
824
|
+
if (cell && cell.formatted) {
|
|
825
|
+
if (typeof cell.formatted === 'string') {
|
|
826
|
+
list.push(cell.formatted);
|
|
827
|
+
}
|
|
828
|
+
else {
|
|
829
|
+
list.push(NumberFormat.FormatPartsAsText(cell.formatted));
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
return list;
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* @returns true if we need a recalc, because references have broken.
|
|
839
|
+
*/
|
|
840
|
+
DeleteSheetInternal(command) {
|
|
841
|
+
let is_active = false;
|
|
842
|
+
let index = -1;
|
|
843
|
+
let target_name = '';
|
|
844
|
+
let requires_recalc = false;
|
|
845
|
+
// remove from array. check if this is the active sheet
|
|
846
|
+
const named_sheet = command.name ? command.name.toLowerCase() : '';
|
|
847
|
+
const sheets = this.model.sheets.list.filter((sheet, i) => {
|
|
848
|
+
if (i === command.index || sheet.id === command.id || sheet.name.toLowerCase() === named_sheet) {
|
|
849
|
+
is_active = (sheet === this.active_sheet);
|
|
850
|
+
this.model.named.RemoveRangesForSheet(sheet.id);
|
|
851
|
+
target_name = sheet.name;
|
|
852
|
+
index = i;
|
|
853
|
+
return false;
|
|
854
|
+
}
|
|
855
|
+
return true;
|
|
856
|
+
});
|
|
857
|
+
// NOTE: we might want to remove references to this sheet. see
|
|
858
|
+
// how we patch references in insert columns/rows functions.
|
|
859
|
+
// actually note the logic we need is already in the rename sheet
|
|
860
|
+
// function; we just need to split it out from actually renaming the
|
|
861
|
+
// sheet, then we can use it
|
|
862
|
+
if (target_name) {
|
|
863
|
+
const count = this.RenameSheetReferences(sheets, target_name, '#REF');
|
|
864
|
+
if (count > 0) {
|
|
865
|
+
requires_recalc = true;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
// empty? create new, activate
|
|
869
|
+
// UPDATE: we also need to create if all remaining sheets are hidden
|
|
870
|
+
if (!sheets.length) {
|
|
871
|
+
sheets.push(Sheet.Blank(this.model.theme_style_properties));
|
|
872
|
+
index = 0;
|
|
873
|
+
}
|
|
874
|
+
else if (sheets.every(test => !test.visible)) {
|
|
875
|
+
// why insert at 0 here? shouldn't it still be last,
|
|
876
|
+
// even if all the others are empty?
|
|
877
|
+
sheets.unshift(Sheet.Blank(this.model.theme_style_properties));
|
|
878
|
+
index = 0;
|
|
879
|
+
}
|
|
880
|
+
else {
|
|
881
|
+
if (index >= sheets.length) {
|
|
882
|
+
index = 0;
|
|
883
|
+
}
|
|
884
|
+
while (!sheets[index].visible) {
|
|
885
|
+
index++;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
// this.model.sheets = sheets;
|
|
889
|
+
this.model.sheets.Assign(sheets);
|
|
890
|
+
// need to activate a new sheet? use the next one (now in the slot
|
|
891
|
+
// we just removed). this will roll over properly if we're at the end.
|
|
892
|
+
// UPDATE: we need to make sure that the target is not hidden, or we
|
|
893
|
+
// can't activate it
|
|
894
|
+
if (is_active) {
|
|
895
|
+
// console.info('activate @', index);
|
|
896
|
+
this.ActivateSheetInternal({ key: CommandKey.ActivateSheet, index });
|
|
897
|
+
}
|
|
898
|
+
return requires_recalc;
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* rename a sheet. this requires changing any formulae that refer to the
|
|
902
|
+
* old name to refer to the new name. if there are any references by ID
|
|
903
|
+
* those don't have to change.
|
|
904
|
+
*
|
|
905
|
+
* FIXME: can we do this using the dependency graph? (...)
|
|
906
|
+
*/
|
|
907
|
+
RenameSheetInternal(target, name) {
|
|
908
|
+
// validate name... ?
|
|
909
|
+
if (!name || IllegalSheetNameRegex.test(name)) {
|
|
910
|
+
throw new Error('invalid sheet name');
|
|
911
|
+
}
|
|
912
|
+
// also can't have two sheets with the same name
|
|
913
|
+
const compare = name.toLowerCase();
|
|
914
|
+
for (const sheet of this.model.sheets.list) {
|
|
915
|
+
if (sheet !== target && sheet.name.toLowerCase() === compare) {
|
|
916
|
+
throw new Error('sheet name already exists');
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
// function will LC the name
|
|
920
|
+
// const old_name = target.name.toLowerCase();
|
|
921
|
+
const old_name = target.name;
|
|
922
|
+
target.name = name;
|
|
923
|
+
// need to update indexes
|
|
924
|
+
this.model.sheets.Assign(this.model.sheets.list);
|
|
925
|
+
this.RenameSheetReferences(this.model.sheets.list, old_name, name);
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
*
|
|
929
|
+
*/
|
|
930
|
+
SortTableInternal(command) {
|
|
931
|
+
if (!command.table.area.start.sheet_id) {
|
|
932
|
+
throw new Error('table has invalid area');
|
|
933
|
+
}
|
|
934
|
+
const sheet = this.model.sheets.Find(command.table.area.start.sheet_id);
|
|
935
|
+
if (!sheet) {
|
|
936
|
+
throw new Error('invalid sheet in table area');
|
|
937
|
+
}
|
|
938
|
+
// I guess we're sorting on calculated value? seems weird.
|
|
939
|
+
// NOTE: only sort hidden rows... what to do with !hidden rows? do they
|
|
940
|
+
// get sorted anyway? [A: no, leave them as-is]
|
|
941
|
+
const ranked = [];
|
|
942
|
+
// get a list of visible table rows. that will be our insert map at the end
|
|
943
|
+
const visible = [];
|
|
944
|
+
let end = command.table.area.end.row;
|
|
945
|
+
if (command.table.totals_row) {
|
|
946
|
+
end--;
|
|
947
|
+
}
|
|
948
|
+
// for auto-sort
|
|
949
|
+
let text_count = 0;
|
|
950
|
+
let number_count = 0;
|
|
951
|
+
for (let row = command.table.area.start.row + 1; row <= end; row++) {
|
|
952
|
+
const height = sheet.GetRowHeight(row);
|
|
953
|
+
if (height) {
|
|
954
|
+
visible.push(row);
|
|
955
|
+
}
|
|
956
|
+
else {
|
|
957
|
+
continue;
|
|
958
|
+
}
|
|
959
|
+
const row_data = {
|
|
960
|
+
row,
|
|
961
|
+
number: 0,
|
|
962
|
+
text: '',
|
|
963
|
+
type: ValueType.undefined,
|
|
964
|
+
data: [],
|
|
965
|
+
};
|
|
966
|
+
for (let column = command.table.area.start.column; column <= command.table.area.end.column; column++) {
|
|
967
|
+
const cd = sheet.CellData({ row, column });
|
|
968
|
+
// sort column is relative to table
|
|
969
|
+
if (column === command.column + command.table.area.start.column) {
|
|
970
|
+
const check_type = cd.calculated_type || cd.type;
|
|
971
|
+
if (check_type === ValueType.string) {
|
|
972
|
+
text_count++;
|
|
973
|
+
}
|
|
974
|
+
else if (check_type === ValueType.number) {
|
|
975
|
+
number_count++;
|
|
976
|
+
}
|
|
977
|
+
// we can precalculate the type for sorting
|
|
978
|
+
const value = cd.calculated_type ? cd.calculated : cd.value;
|
|
979
|
+
row_data.text = value?.toString() || '';
|
|
980
|
+
row_data.number = Number(value) || 0;
|
|
981
|
+
row_data.type = cd.calculated_type || cd.type;
|
|
982
|
+
}
|
|
983
|
+
row_data.data.push({
|
|
984
|
+
address: { row, column },
|
|
985
|
+
data: cd.value,
|
|
986
|
+
type: cd.type,
|
|
987
|
+
style: cd.style,
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
ranked.push(row_data);
|
|
991
|
+
}
|
|
992
|
+
// auto sort - default to text, unless we see more numbers
|
|
993
|
+
let sort_type = command.type;
|
|
994
|
+
if (sort_type === 'auto') {
|
|
995
|
+
if (number_count > text_count) {
|
|
996
|
+
sort_type = 'numeric';
|
|
997
|
+
}
|
|
998
|
+
else {
|
|
999
|
+
sort_type = 'text';
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
// console.info(visible, ranked);
|
|
1003
|
+
// rank
|
|
1004
|
+
const invert = command.asc ? 1 : -1;
|
|
1005
|
+
switch (sort_type) {
|
|
1006
|
+
case 'numeric':
|
|
1007
|
+
ranked.sort((a, b) => {
|
|
1008
|
+
if (a.type === ValueType.undefined) {
|
|
1009
|
+
return ((b.type === ValueType.undefined) ? 0 : 1);
|
|
1010
|
+
}
|
|
1011
|
+
if (b.type === ValueType.undefined) {
|
|
1012
|
+
return -1;
|
|
1013
|
+
}
|
|
1014
|
+
return (a.number - b.number) * invert;
|
|
1015
|
+
});
|
|
1016
|
+
break;
|
|
1017
|
+
case 'text':
|
|
1018
|
+
default:
|
|
1019
|
+
ranked.sort((a, b) => {
|
|
1020
|
+
if (a.type === ValueType.undefined) {
|
|
1021
|
+
return ((b.type === ValueType.undefined) ? 0 : 1);
|
|
1022
|
+
}
|
|
1023
|
+
if (b.type === ValueType.undefined) {
|
|
1024
|
+
return -1;
|
|
1025
|
+
}
|
|
1026
|
+
return a.text.localeCompare(b.text) * invert;
|
|
1027
|
+
});
|
|
1028
|
+
break;
|
|
1029
|
+
}
|
|
1030
|
+
// now apply the sort
|
|
1031
|
+
const insert = { row: command.table.area.start.row + 1, column: command.table.area.start.column };
|
|
1032
|
+
for (let i = 0; i < visible.length; i++) {
|
|
1033
|
+
insert.row = visible[i];
|
|
1034
|
+
const entry = ranked[i];
|
|
1035
|
+
insert.column = command.table.area.start.column; // reset
|
|
1036
|
+
for (const cell of entry.data) {
|
|
1037
|
+
if (cell.type === ValueType.formula) {
|
|
1038
|
+
let data = cell.data;
|
|
1039
|
+
const offsets = { columns: 0, rows: insert.row - entry.row };
|
|
1040
|
+
const parse_result = this.parser.Parse(data);
|
|
1041
|
+
if (parse_result.expression) {
|
|
1042
|
+
data = '=' + this.parser.Render(parse_result.expression, { offset: offsets, missing: '' });
|
|
1043
|
+
}
|
|
1044
|
+
sheet.SetCellValue(insert, data);
|
|
1045
|
+
}
|
|
1046
|
+
else {
|
|
1047
|
+
sheet.SetCellValue(insert, cell.data);
|
|
1048
|
+
}
|
|
1049
|
+
sheet.UpdateCellStyle(insert, cell.style || {}, false);
|
|
1050
|
+
insert.column++; // step
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
// keep reference. we don't have the actual table, we have a copy.
|
|
1054
|
+
// this is done because the command queue might be broadcast, so
|
|
1055
|
+
// references won't work.
|
|
1056
|
+
const ref = this.model.tables.get(command.table.name.toLowerCase());
|
|
1057
|
+
if (ref) {
|
|
1058
|
+
ref.sort = {
|
|
1059
|
+
type: command.type,
|
|
1060
|
+
asc: !!command.asc,
|
|
1061
|
+
column: command.column,
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
// flush style in rows that don't change, to force repainting. this
|
|
1065
|
+
// has to do with how table styles are overlaid on other styles; it's
|
|
1066
|
+
// not optimal at the moment.
|
|
1067
|
+
{
|
|
1068
|
+
let row = command.table.area.start.row;
|
|
1069
|
+
for (let column = command.table.area.start.column; column <= command.table.area.end.column; column++) {
|
|
1070
|
+
sheet.cells.data[row][column]?.FlushStyle();
|
|
1071
|
+
}
|
|
1072
|
+
if (command.table.totals_row) {
|
|
1073
|
+
row = command.table.area.end.row;
|
|
1074
|
+
for (let column = command.table.area.start.column; column <= command.table.area.end.column; column++) {
|
|
1075
|
+
sheet.cells.data[row][column]?.FlushStyle();
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
// console.info(ordered);
|
|
1080
|
+
return new Area(command.table.area.start, command.table.area.end);
|
|
1081
|
+
}
|
|
1082
|
+
/**
|
|
1083
|
+
* update all columns of a table (collect column names). this
|
|
1084
|
+
* method rebuilds all columns; that's probably unecessary in
|
|
1085
|
+
* many cases, but we'll start here and we can drill down later.
|
|
1086
|
+
*
|
|
1087
|
+
* we do two things here: we normalize column header values, and
|
|
1088
|
+
* we collect them for table headers.
|
|
1089
|
+
*
|
|
1090
|
+
* @param table
|
|
1091
|
+
*/
|
|
1092
|
+
UpdateTableColumns(table) {
|
|
1093
|
+
if (!table.area.start.sheet_id) {
|
|
1094
|
+
throw new Error('invalid area in table');
|
|
1095
|
+
}
|
|
1096
|
+
const sheet = this.model.sheets.Find(table.area.start.sheet_id);
|
|
1097
|
+
if (!sheet) {
|
|
1098
|
+
throw new Error('invalid sheet in table');
|
|
1099
|
+
}
|
|
1100
|
+
// this can get called when a document is loaded, we might
|
|
1101
|
+
// not have column names when we start. but if we do, we will
|
|
1102
|
+
// need to keep the old ones so we can check deltas.
|
|
1103
|
+
const current_columns = table.columns?.slice(0) || undefined;
|
|
1104
|
+
const columns = [];
|
|
1105
|
+
const row = table.area.start.row;
|
|
1106
|
+
const count = table.area.end.column - table.area.start.column + 1;
|
|
1107
|
+
let column = table.area.start.column;
|
|
1108
|
+
for (let i = 0; i < count; i++, column++) {
|
|
1109
|
+
const header = sheet.CellData({ row, column });
|
|
1110
|
+
let value = '';
|
|
1111
|
+
if (header.type !== ValueType.string) {
|
|
1112
|
+
if (typeof header.formatted !== 'undefined') {
|
|
1113
|
+
value = (header.formatted).toString();
|
|
1114
|
+
}
|
|
1115
|
+
else if (typeof header.calculated !== 'undefined') {
|
|
1116
|
+
value = (header.calculated).toString();
|
|
1117
|
+
}
|
|
1118
|
+
else if (typeof header.value !== 'undefined') {
|
|
1119
|
+
value = (header.value).toString();
|
|
1120
|
+
}
|
|
1121
|
+
header.Set(value, ValueType.string);
|
|
1122
|
+
}
|
|
1123
|
+
else {
|
|
1124
|
+
value = header.value || '';
|
|
1125
|
+
}
|
|
1126
|
+
if (!value) {
|
|
1127
|
+
value = `Column${i + 1}`;
|
|
1128
|
+
}
|
|
1129
|
+
let proposed = value;
|
|
1130
|
+
let success = false;
|
|
1131
|
+
let index = 1;
|
|
1132
|
+
while (!success) {
|
|
1133
|
+
success = true;
|
|
1134
|
+
inner_loop: for (const check of columns) {
|
|
1135
|
+
if (check.toLowerCase() === proposed.toLowerCase()) {
|
|
1136
|
+
success = false;
|
|
1137
|
+
proposed = `${value}${++index}`;
|
|
1138
|
+
break inner_loop;
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
header.Set(proposed, ValueType.string);
|
|
1143
|
+
columns.push(proposed.toLowerCase());
|
|
1144
|
+
}
|
|
1145
|
+
// TODO: this is good, and works, but we are going to have to
|
|
1146
|
+
// look for structured references and update them if the column
|
|
1147
|
+
// names change.
|
|
1148
|
+
if (current_columns) {
|
|
1149
|
+
// if we are inserting/removing columns, we're probably
|
|
1150
|
+
// not changing names at the same time. on remove, some
|
|
1151
|
+
// references will break, but that's to be expected. on
|
|
1152
|
+
// insert, new columns will get added but we don't have
|
|
1153
|
+
// to change references.
|
|
1154
|
+
if (current_columns.length === columns.length) {
|
|
1155
|
+
const update = new Map();
|
|
1156
|
+
for (let i = 0; i < current_columns.length; i++) {
|
|
1157
|
+
const compare = current_columns[i].toLowerCase();
|
|
1158
|
+
if (compare !== columns[i]) {
|
|
1159
|
+
update.set(compare, columns[i]); // add old -> new
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
if (update.size) {
|
|
1163
|
+
// OK, we need to update. we're iterating cells, then
|
|
1164
|
+
// updates, so we don't accidentally oscillate if we have
|
|
1165
|
+
// columns that swap names. going through once should
|
|
1166
|
+
// ensure that doesn't happen.
|
|
1167
|
+
const table_name = table.name.toLowerCase();
|
|
1168
|
+
for (const sheet of this.model.sheets.list) {
|
|
1169
|
+
// there's an additional complication: we support anonymous
|
|
1170
|
+
// tables, if the cell is in the table. so we also have to
|
|
1171
|
+
// know the address. so we can't use the IterateAll method.
|
|
1172
|
+
// duh, no we don't. if the cell is in the table it will have
|
|
1173
|
+
// a reference.
|
|
1174
|
+
for (const cell of sheet.cells.Iterate()) {
|
|
1175
|
+
if (cell.ValueIsFormula()) {
|
|
1176
|
+
let updated_formula = false;
|
|
1177
|
+
const parse_result = this.parser.Parse(cell.value);
|
|
1178
|
+
if (parse_result.expression) {
|
|
1179
|
+
this.parser.Walk(parse_result.expression, (unit) => {
|
|
1180
|
+
if (unit.type === 'structured-reference') {
|
|
1181
|
+
if (unit.table.toLowerCase() === table_name ||
|
|
1182
|
+
(!unit.table && cell.table === table)) {
|
|
1183
|
+
// we may need to rewrite...
|
|
1184
|
+
for (const [key, value] of update.entries()) {
|
|
1185
|
+
if (unit.column.toLowerCase() === key) {
|
|
1186
|
+
// ok we need to update
|
|
1187
|
+
unit.column = value;
|
|
1188
|
+
updated_formula = true;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
return true;
|
|
1194
|
+
});
|
|
1195
|
+
if (updated_formula) {
|
|
1196
|
+
console.info('updating value');
|
|
1197
|
+
cell.value = '=' + this.parser.Render(parse_result.expression, {
|
|
1198
|
+
missing: '',
|
|
1199
|
+
});
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
/*
|
|
1205
|
+
sheet.cells.IterateAll(cell => {
|
|
1206
|
+
if (cell.ValueIsFormula()) {
|
|
1207
|
+
let updated_formula = false;
|
|
1208
|
+
const parse_result = this.parser.Parse(cell.value);
|
|
1209
|
+
if (parse_result.expression) {
|
|
1210
|
+
|
|
1211
|
+
this.parser.Walk(parse_result.expression, (unit) => {
|
|
1212
|
+
if (unit.type === 'structured-reference') {
|
|
1213
|
+
|
|
1214
|
+
if (unit.table.toLowerCase() === table_name ||
|
|
1215
|
+
(!unit.table && cell.table === table)) {
|
|
1216
|
+
|
|
1217
|
+
// we may need to rewrite...
|
|
1218
|
+
for (const [key, value] of update.entries()) {
|
|
1219
|
+
if (unit.column.toLowerCase() === key) {
|
|
1220
|
+
|
|
1221
|
+
// ok we need to update
|
|
1222
|
+
unit.column = value;
|
|
1223
|
+
updated_formula = true;
|
|
1224
|
+
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
return true;
|
|
1231
|
+
});
|
|
1232
|
+
if (updated_formula) {
|
|
1233
|
+
console.info('updating value');
|
|
1234
|
+
cell.value = '=' + this.parser.Render(parse_result.expression, {
|
|
1235
|
+
missing: '',
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
});
|
|
1241
|
+
*/
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
table.columns = columns;
|
|
1247
|
+
return {
|
|
1248
|
+
start: {
|
|
1249
|
+
...table.area.start,
|
|
1250
|
+
}, end: {
|
|
1251
|
+
row: table.area.start.row,
|
|
1252
|
+
column: table.area.end.column,
|
|
1253
|
+
}
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
/**
|
|
1257
|
+
* set range, via command. returns affected area.
|
|
1258
|
+
*
|
|
1259
|
+
* Adding a flags parameter (in/out) to support indicating
|
|
1260
|
+
* that we need to update layout.
|
|
1261
|
+
*/
|
|
1262
|
+
SetRangeInternal(command, flags = {}) {
|
|
1263
|
+
// NOTE: apparently if we call SetRange with a single target
|
|
1264
|
+
// and the array flag set, it gets translated to an area. which
|
|
1265
|
+
// is OK, I guess, but there may be an unecessary branch in here.
|
|
1266
|
+
const area = IsCellAddress(command.area)
|
|
1267
|
+
? new Area(command.area)
|
|
1268
|
+
: new Area(command.area.start, command.area.end);
|
|
1269
|
+
const sheet = this.FindSheet(area);
|
|
1270
|
+
if (!area.start.sheet_id) {
|
|
1271
|
+
area.start.sheet_id = sheet.id;
|
|
1272
|
+
}
|
|
1273
|
+
if (!area.entire_row && !area.entire_column && (area.end.row >= sheet.rows
|
|
1274
|
+
|| area.end.column >= sheet.columns)) {
|
|
1275
|
+
// we have to call this because the 'set area' method calls RealArea
|
|
1276
|
+
sheet.cells.EnsureCell(area.end);
|
|
1277
|
+
// should we send a structure event here? we may be increasing the
|
|
1278
|
+
// size, in which case we should send the event. even though no addresses
|
|
1279
|
+
// change, there are new cells.
|
|
1280
|
+
if (sheet === this.active_sheet) {
|
|
1281
|
+
flags.layout = true;
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
// originally we called sheet methods here, but all the sheet
|
|
1285
|
+
// does is call methods on the cells object -- we can shortcut.
|
|
1286
|
+
// is that a good idea? (...)
|
|
1287
|
+
// at a minimum we can consolidate...
|
|
1288
|
+
if (IsCellAddress(command.area)) {
|
|
1289
|
+
// FIXME: should throw if we try to set part of an array
|
|
1290
|
+
const cell = sheet.CellData(command.area);
|
|
1291
|
+
if (cell.area && (cell.area.rows > 1 || cell.area.columns > 1)) {
|
|
1292
|
+
this.Error(ErrorCode.array);
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
// single cell
|
|
1296
|
+
// UPDATE: could be array
|
|
1297
|
+
// type is value|value[][], pull out first value. at some point
|
|
1298
|
+
// we may have supported value[], or maybe they were passed in
|
|
1299
|
+
// accidentally, but check regardless.
|
|
1300
|
+
// FIXME: no, that should throw (or otherwise error) (or fix the data?).
|
|
1301
|
+
// we can't handle errors all the way down the call stack.
|
|
1302
|
+
let value = Array.isArray(command.value) ?
|
|
1303
|
+
Array.isArray(command.value[0]) ? command.value[0][0] : command.value[0] : command.value;
|
|
1304
|
+
// translate R1C1. in this case, we translate relative to the
|
|
1305
|
+
// target address, irrspective of the array flag. this is the
|
|
1306
|
+
// easiest case?
|
|
1307
|
+
// NOTE: as noted above (top of function), if a single cell target
|
|
1308
|
+
// is set with the array flag, it may fall into the next branch. not
|
|
1309
|
+
// sure that makes much of a difference.
|
|
1310
|
+
if (command.r1c1) {
|
|
1311
|
+
value = this.TranslateR1C1(command.area, value);
|
|
1312
|
+
}
|
|
1313
|
+
if (command.array) {
|
|
1314
|
+
// what is the case for this? not saying it doesn't happen, just
|
|
1315
|
+
// when is it useful?
|
|
1316
|
+
// A: there is the case in Excel where there are different semantics
|
|
1317
|
+
// for array calculation; something we mentioned in one of the kb
|
|
1318
|
+
// articles, something about array functions... [FIXME: ref?]
|
|
1319
|
+
sheet.SetArrayValue(area, value);
|
|
1320
|
+
}
|
|
1321
|
+
else {
|
|
1322
|
+
sheet.SetCellValue(command.area, value);
|
|
1323
|
+
}
|
|
1324
|
+
return area;
|
|
1325
|
+
}
|
|
1326
|
+
else {
|
|
1327
|
+
// there are a couple of options here, from the methods that
|
|
1328
|
+
// have accumulated in Sheet.
|
|
1329
|
+
// SetArrayValue -- set data as an array
|
|
1330
|
+
// SetAreaValues -- set values from data one-to-one
|
|
1331
|
+
// SetAreaValue -- single value repeated in range
|
|
1332
|
+
// FIXME: clean this up!
|
|
1333
|
+
if (command.array) {
|
|
1334
|
+
let value = Array.isArray(command.value) ?
|
|
1335
|
+
Array.isArray(command.value[0]) ? command.value[0][0] : command.value[0] : command.value;
|
|
1336
|
+
if (command.r1c1) {
|
|
1337
|
+
value = this.TranslateR1C1(area.start, value);
|
|
1338
|
+
}
|
|
1339
|
+
sheet.SetArrayValue(area, value);
|
|
1340
|
+
}
|
|
1341
|
+
else {
|
|
1342
|
+
// in this case, either value is a single value or it's a 2D array;
|
|
1343
|
+
// and area is a range of unknown size. we do a 1-1 map from area
|
|
1344
|
+
// member to data member. if the data is not the same shape, it just
|
|
1345
|
+
// results in empty cells (if area is larger) or dropped data (if value
|
|
1346
|
+
// is larger).
|
|
1347
|
+
// so for the purposes of R1C1, we have to run the same loop that
|
|
1348
|
+
// happens internally in the Cells.SetArea routine. but I definitely
|
|
1349
|
+
// don't want R1C1 to get all the way in there.
|
|
1350
|
+
// FIXME/TODO: we're doing this the naive way for now. it could be
|
|
1351
|
+
// optimized in several ways.
|
|
1352
|
+
if (command.r1c1) {
|
|
1353
|
+
if (Array.isArray(command.value)) {
|
|
1354
|
+
// loop on DATA, since that's what we care about here. we can
|
|
1355
|
+
// expand data, since it won't spill in the next call (spill is
|
|
1356
|
+
// handled earlier in the call stack).
|
|
1357
|
+
for (let r = 0; r < command.value.length && r < area.rows; r++) {
|
|
1358
|
+
if (!command.value[r]) {
|
|
1359
|
+
command.value[r] = [];
|
|
1360
|
+
}
|
|
1361
|
+
const row = command.value[r];
|
|
1362
|
+
for (let c = 0; c < row.length && c < area.columns; c++) {
|
|
1363
|
+
const target = { ...area.start, row: area.start.row + r, column: area.start.column + c };
|
|
1364
|
+
row[c] = this.TranslateR1C1(target, row[c]);
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
else {
|
|
1369
|
+
// only have to do this for strings
|
|
1370
|
+
if (typeof command.value === 'string' && command.value[0] === '=') {
|
|
1371
|
+
// we need to rebuild the value so it is an array, so that
|
|
1372
|
+
// relative addresses will be relative to the cell.
|
|
1373
|
+
const value = [];
|
|
1374
|
+
for (let r = 0; r < area.rows; r++) {
|
|
1375
|
+
const row = [];
|
|
1376
|
+
for (let c = 0; c < area.columns; c++) {
|
|
1377
|
+
const target = { ...area.start, row: area.start.row + r, column: area.start.column + c };
|
|
1378
|
+
row.push(this.TranslateR1C1(target, command.value));
|
|
1379
|
+
}
|
|
1380
|
+
value.push(row);
|
|
1381
|
+
}
|
|
1382
|
+
command.value = value;
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
sheet.SetAreaValues2(area, command.value);
|
|
1387
|
+
}
|
|
1388
|
+
return area;
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
/**
|
|
1392
|
+
* basic implementation does not handle any UI, painting, or layout.
|
|
1393
|
+
*/
|
|
1394
|
+
ActivateSheetInternal(command) {
|
|
1395
|
+
const candidate = this.ResolveSheet(command) || this.model.sheets.list[0];
|
|
1396
|
+
if (this.active_sheet === candidate && !command.force) {
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1399
|
+
if (!candidate.visible) {
|
|
1400
|
+
throw new Error('cannot activate hidden sheet');
|
|
1401
|
+
}
|
|
1402
|
+
// hold this for the event (later)
|
|
1403
|
+
const deactivate = this.active_sheet;
|
|
1404
|
+
// select target
|
|
1405
|
+
this.active_sheet = candidate;
|
|
1406
|
+
/*
|
|
1407
|
+
// scrub, then add any sheet annotations. note the caller will
|
|
1408
|
+
// still have to inflate these or do whatever step is necessary to
|
|
1409
|
+
// render.
|
|
1410
|
+
|
|
1411
|
+
const annotations = this.active_sheet.annotations;
|
|
1412
|
+
for (const element of annotations) {
|
|
1413
|
+
this.AddAnnotation(element, true);
|
|
1414
|
+
}
|
|
1415
|
+
*/
|
|
1416
|
+
this.grid_events.Publish({
|
|
1417
|
+
type: 'sheet-change',
|
|
1418
|
+
deactivate,
|
|
1419
|
+
activate: this.active_sheet,
|
|
1420
|
+
});
|
|
1421
|
+
}
|
|
1422
|
+
ShowSheetInternal(command) {
|
|
1423
|
+
const sheet = this.ResolveSheet(command);
|
|
1424
|
+
// invalid
|
|
1425
|
+
if (!sheet) {
|
|
1426
|
+
return;
|
|
1427
|
+
}
|
|
1428
|
+
// not changed
|
|
1429
|
+
if (sheet.visible === command.show) {
|
|
1430
|
+
return;
|
|
1431
|
+
}
|
|
1432
|
+
// make sure at least one will be visible after the operation
|
|
1433
|
+
if (!command.show) {
|
|
1434
|
+
let count = 0;
|
|
1435
|
+
for (const test of this.model.sheets.list) {
|
|
1436
|
+
if (!sheet.visible || test === sheet) {
|
|
1437
|
+
count++;
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
if (count >= this.model.sheets.length) {
|
|
1441
|
+
throw new Error('can\'t hide all sheets');
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
// ok, set
|
|
1445
|
+
sheet.visible = command.show;
|
|
1446
|
+
// is this current?
|
|
1447
|
+
if (sheet === this.active_sheet) {
|
|
1448
|
+
// this needs to check the visibility field, or else we'll throw
|
|
1449
|
+
// when we call the activate method. given the above check we know
|
|
1450
|
+
// that there's at least one visible sheet.
|
|
1451
|
+
const list = this.model.sheets.list;
|
|
1452
|
+
// first find the _next_ visible sheet...
|
|
1453
|
+
for (let i = 0; i < list.length; i++) {
|
|
1454
|
+
if (list[i] === this.active_sheet) {
|
|
1455
|
+
for (let j = i + 1; j < list.length; j++) {
|
|
1456
|
+
if (list[j].visible) {
|
|
1457
|
+
this.ActivateSheetInternal({
|
|
1458
|
+
key: CommandKey.ActivateSheet,
|
|
1459
|
+
index: j,
|
|
1460
|
+
});
|
|
1461
|
+
return;
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
// if we got here, then we need to start again from the beginning
|
|
1465
|
+
for (let j = 0; j < list.length; j++) {
|
|
1466
|
+
if (list[j].visible) {
|
|
1467
|
+
this.ActivateSheetInternal({
|
|
1468
|
+
key: CommandKey.ActivateSheet,
|
|
1469
|
+
index: j,
|
|
1470
|
+
});
|
|
1471
|
+
return;
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
// should not be possible
|
|
1475
|
+
throw new Error('no visible sheet');
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
/**
|
|
1481
|
+
* normalize commands. for co-editing support we need to ensure that
|
|
1482
|
+
* commands properly have sheet IDs in areas/addresses (and explicit
|
|
1483
|
+
* fields in some cases).
|
|
1484
|
+
*
|
|
1485
|
+
* at the same time we're editing the commands a little bit to make
|
|
1486
|
+
* them a little more consistent (within reason).
|
|
1487
|
+
*
|
|
1488
|
+
* @param commands
|
|
1489
|
+
*/
|
|
1490
|
+
NormalizeCommands(commands) {
|
|
1491
|
+
if (!Array.isArray(commands)) {
|
|
1492
|
+
commands = [commands];
|
|
1493
|
+
}
|
|
1494
|
+
const id = this.active_sheet.id;
|
|
1495
|
+
for (const command of commands) {
|
|
1496
|
+
switch (command.key) {
|
|
1497
|
+
// nothing
|
|
1498
|
+
case CommandKey.Null:
|
|
1499
|
+
case CommandKey.TabColor:
|
|
1500
|
+
case CommandKey.ShowHeaders:
|
|
1501
|
+
case CommandKey.ShowSheet:
|
|
1502
|
+
case CommandKey.AddSheet:
|
|
1503
|
+
case CommandKey.DuplicateSheet:
|
|
1504
|
+
case CommandKey.DeleteSheet:
|
|
1505
|
+
case CommandKey.ActivateSheet:
|
|
1506
|
+
case CommandKey.RenameSheet:
|
|
1507
|
+
case CommandKey.ReorderSheet:
|
|
1508
|
+
case CommandKey.Reset:
|
|
1509
|
+
case CommandKey.CreateAnnotation:
|
|
1510
|
+
case CommandKey.RemoveAnnotation:
|
|
1511
|
+
break;
|
|
1512
|
+
/*
|
|
1513
|
+
// both
|
|
1514
|
+
case CommandKey.Clear:
|
|
1515
|
+
if (command.area) {
|
|
1516
|
+
if (!command.area.start.sheet_id) {
|
|
1517
|
+
command.area.start.sheet_id = id;
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
else {
|
|
1521
|
+
if (!command.sheet_id) {
|
|
1522
|
+
command.sheet_id = id;
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
break;
|
|
1526
|
+
*/
|
|
1527
|
+
// special cases
|
|
1528
|
+
case CommandKey.SortTable:
|
|
1529
|
+
case CommandKey.RemoveTable:
|
|
1530
|
+
if (!command.table.area.start.sheet_id) {
|
|
1531
|
+
command.table.area.start.sheet_id = id;
|
|
1532
|
+
}
|
|
1533
|
+
break;
|
|
1534
|
+
case CommandKey.AddConditionalFormat:
|
|
1535
|
+
if (!command.format.area.start.sheet_id) {
|
|
1536
|
+
command.format.area.start.sheet_id = id;
|
|
1537
|
+
}
|
|
1538
|
+
break;
|
|
1539
|
+
// field
|
|
1540
|
+
case CommandKey.ResizeRows:
|
|
1541
|
+
case CommandKey.ResizeColumns:
|
|
1542
|
+
case CommandKey.InsertColumns:
|
|
1543
|
+
case CommandKey.InsertRows:
|
|
1544
|
+
case CommandKey.Freeze:
|
|
1545
|
+
if (!command.sheet_id) {
|
|
1546
|
+
command.sheet_id = id;
|
|
1547
|
+
}
|
|
1548
|
+
break;
|
|
1549
|
+
// area: Area|Address (may be optional)
|
|
1550
|
+
case CommandKey.Clear:
|
|
1551
|
+
case CommandKey.SetNote:
|
|
1552
|
+
case CommandKey.SetLink:
|
|
1553
|
+
case CommandKey.Indent:
|
|
1554
|
+
case CommandKey.UpdateBorders:
|
|
1555
|
+
case CommandKey.MergeCells:
|
|
1556
|
+
case CommandKey.UnmergeCells:
|
|
1557
|
+
case CommandKey.DataValidation:
|
|
1558
|
+
case CommandKey.SetRange:
|
|
1559
|
+
case CommandKey.UpdateStyle:
|
|
1560
|
+
case CommandKey.SetName:
|
|
1561
|
+
case CommandKey.Select:
|
|
1562
|
+
case CommandKey.InsertTable:
|
|
1563
|
+
// note that remove conditional format could have a format
|
|
1564
|
+
// object (with an area) instead of an area. but in that case,
|
|
1565
|
+
// the format object must match an existing format, so it would
|
|
1566
|
+
// have to have a proper area. there's no case where we would
|
|
1567
|
+
// want to add it. so we only handle the area case.
|
|
1568
|
+
// eslint-disable-next-line no-fallthrough
|
|
1569
|
+
case CommandKey.RemoveConditionalFormat:
|
|
1570
|
+
if (command.area) {
|
|
1571
|
+
if (IsCellAddress(command.area)) {
|
|
1572
|
+
if (!command.area.sheet_id) {
|
|
1573
|
+
command.area.sheet_id = id;
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
else {
|
|
1577
|
+
if (!command.area.start.sheet_id) {
|
|
1578
|
+
command.area.start.sheet_id = id;
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
break;
|
|
1583
|
+
default:
|
|
1584
|
+
// this is intended to check that we've handled all cases. if you
|
|
1585
|
+
// add additional commands and they're not handled, this should
|
|
1586
|
+
// raise a ts error.
|
|
1587
|
+
AssertNever(command);
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
return commands;
|
|
1591
|
+
}
|
|
1592
|
+
TabColor(sheet, color) {
|
|
1593
|
+
this.ExecCommand({
|
|
1594
|
+
key: CommandKey.TabColor,
|
|
1595
|
+
sheet,
|
|
1596
|
+
color,
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1599
|
+
/**
|
|
1600
|
+
* add sheet. data only.
|
|
1601
|
+
*/
|
|
1602
|
+
AddSheetInternal(name = Sheet.default_sheet_name, insert_index = -1) {
|
|
1603
|
+
if (!this.options.add_tab) {
|
|
1604
|
+
console.warn('add tab option not set or false');
|
|
1605
|
+
return;
|
|
1606
|
+
}
|
|
1607
|
+
// validate name...
|
|
1608
|
+
while (this.model.sheets.list.some((test) => test.name === name)) {
|
|
1609
|
+
const match = name.match(/^(.*?)(\d+)$/);
|
|
1610
|
+
if (match) {
|
|
1611
|
+
name = match[1] + (Number(match[2]) + 1);
|
|
1612
|
+
}
|
|
1613
|
+
else {
|
|
1614
|
+
name = name + '2';
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
// FIXME: structure event
|
|
1618
|
+
const sheet = Sheet.Blank(this.model.theme_style_properties, name);
|
|
1619
|
+
if (insert_index >= 0) {
|
|
1620
|
+
this.model.sheets.Splice(insert_index, 0, sheet);
|
|
1621
|
+
}
|
|
1622
|
+
else {
|
|
1623
|
+
this.model.sheets.Add(sheet);
|
|
1624
|
+
}
|
|
1625
|
+
// moved to ExecCmomand
|
|
1626
|
+
// if (this.tab_bar) { this.tab_bar.Update(); }
|
|
1627
|
+
return sheet.id;
|
|
1628
|
+
}
|
|
1629
|
+
/**
|
|
1630
|
+
* resolve sheet in a command that uses the SheetSelection interface;
|
|
1631
|
+
* that allows sheet selection by name, id or index.
|
|
1632
|
+
*/
|
|
1633
|
+
ResolveSheet(command) {
|
|
1634
|
+
// NOTE: since you are using typeof here to check for undefined,
|
|
1635
|
+
// it seems like it would be efficient to use typeof to check
|
|
1636
|
+
// the actual type; hence merging "index" and "name" might be
|
|
1637
|
+
// more efficient than checking each one separately.
|
|
1638
|
+
if (typeof command.index !== 'undefined') {
|
|
1639
|
+
return this.model.sheets.list[command.index];
|
|
1640
|
+
}
|
|
1641
|
+
if (typeof command.name !== 'undefined') {
|
|
1642
|
+
return this.model.sheets.Find(command.name);
|
|
1643
|
+
}
|
|
1644
|
+
if (command.id) {
|
|
1645
|
+
return this.model.sheets.Find(command.id);
|
|
1646
|
+
}
|
|
1647
|
+
return undefined;
|
|
1648
|
+
}
|
|
1649
|
+
/**
|
|
1650
|
+
* find sheet matching sheet_id in area.start, or active sheet
|
|
1651
|
+
*
|
|
1652
|
+
* FIXME: should return undefined on !match
|
|
1653
|
+
* FIXME: should be in model, which should be a class
|
|
1654
|
+
*/
|
|
1655
|
+
FindSheet(identifier) {
|
|
1656
|
+
if (identifier === undefined) {
|
|
1657
|
+
return this.active_sheet;
|
|
1658
|
+
}
|
|
1659
|
+
const id = typeof identifier === 'number' ? identifier : IsCellAddress(identifier) ? identifier.sheet_id : identifier.start.sheet_id;
|
|
1660
|
+
if (!id || id === this.active_sheet.id) {
|
|
1661
|
+
return this.active_sheet;
|
|
1662
|
+
}
|
|
1663
|
+
const sheet = this.model.sheets.Find(id);
|
|
1664
|
+
if (sheet) {
|
|
1665
|
+
return sheet;
|
|
1666
|
+
}
|
|
1667
|
+
/*
|
|
1668
|
+
for (const test of this.model.sheets) {
|
|
1669
|
+
if (test.id === id) {
|
|
1670
|
+
return test;
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
*/
|
|
1674
|
+
// FIXME: should return undefined here
|
|
1675
|
+
return this.active_sheet;
|
|
1676
|
+
}
|
|
1677
|
+
/**
|
|
1678
|
+
* this function now works for both rows and columns, and can handle
|
|
1679
|
+
* sheets other than the active sheet. it does assume that you only ever
|
|
1680
|
+
* add rows/columns on the active sheet, but since that's all parameterized
|
|
1681
|
+
* you could get it to work either way.
|
|
1682
|
+
*
|
|
1683
|
+
* in fact we should change the names of those parameters so it's a little
|
|
1684
|
+
* more generic.
|
|
1685
|
+
*/
|
|
1686
|
+
PatchFormulasInternal(source, before_row, row_count, before_column, column_count, target_sheet_name, is_target) {
|
|
1687
|
+
const parsed = this.parser.Parse(source || '');
|
|
1688
|
+
let modified = false;
|
|
1689
|
+
// the sheet test is different for active sheet/non-active sheet.
|
|
1690
|
+
// on the active sheet, check for no name OR name === active sheet name.
|
|
1691
|
+
// on other sheets, check for name AND name === active sheet name.
|
|
1692
|
+
if (parsed.expression) {
|
|
1693
|
+
this.parser.Walk(parsed.expression, (element) => {
|
|
1694
|
+
if (element.type === 'range' || element.type === 'address') {
|
|
1695
|
+
// we can test if we need to modify a range or an address, but the
|
|
1696
|
+
// second address in a range can't be tested properly. so the solution
|
|
1697
|
+
// here is to just capture the addresses that need to be modified
|
|
1698
|
+
// from the range, and then not recurse (we should never get here
|
|
1699
|
+
// as an address in a range).
|
|
1700
|
+
const addresses = [];
|
|
1701
|
+
if (element.type === 'range') {
|
|
1702
|
+
// there's a problem: this breaks because the inner test fails when
|
|
1703
|
+
// this is TRUE... we may need to modify
|
|
1704
|
+
// recurse if (1) explicit name match; or (2) no name AND we are on the active sheet
|
|
1705
|
+
// return ((element.start.sheet && element.start.sheet.toLowerCase() === active_sheet_name) || (!element.start.sheet && active_sheet));
|
|
1706
|
+
if ((element.start.sheet && element.start.sheet.toLowerCase() === target_sheet_name) || (!element.start.sheet && is_target)) {
|
|
1707
|
+
addresses.push(element.start, element.end);
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
else if (element.type === 'address') {
|
|
1711
|
+
if ((element.sheet && element.sheet.toLowerCase() === target_sheet_name) || (!element.sheet && is_target)) {
|
|
1712
|
+
addresses.push(element);
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
// could switch the tests around? (referring to the count
|
|
1716
|
+
// tests, which switch on operation)
|
|
1717
|
+
for (const address of addresses) {
|
|
1718
|
+
if (row_count && address.row >= before_row) {
|
|
1719
|
+
if (row_count < 0 && address.row + row_count < before_row) {
|
|
1720
|
+
address.column = address.row = -1;
|
|
1721
|
+
}
|
|
1722
|
+
else {
|
|
1723
|
+
address.row += row_count;
|
|
1724
|
+
}
|
|
1725
|
+
modified = true;
|
|
1726
|
+
}
|
|
1727
|
+
if (column_count && address.column >= before_column) {
|
|
1728
|
+
if (column_count < 0 && address.column + column_count < before_column) {
|
|
1729
|
+
address.column = address.row = -1; // set as invalid (-1)
|
|
1730
|
+
}
|
|
1731
|
+
else {
|
|
1732
|
+
address.column += column_count;
|
|
1733
|
+
}
|
|
1734
|
+
modified = true;
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
return false; // always explicit
|
|
1738
|
+
}
|
|
1739
|
+
return true; // recurse for everything else
|
|
1740
|
+
});
|
|
1741
|
+
if (modified) {
|
|
1742
|
+
return '=' + this.parser.Render(parsed.expression, { missing: '' });
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
return undefined;
|
|
1746
|
+
}
|
|
1747
|
+
PatchExpressionSheetName(expression, old_name, name) {
|
|
1748
|
+
let modified = false;
|
|
1749
|
+
const parsed = this.parser.Parse(expression || '');
|
|
1750
|
+
if (parsed.expression) {
|
|
1751
|
+
this.parser.Walk(parsed.expression, (element) => {
|
|
1752
|
+
if (element.type === 'address') {
|
|
1753
|
+
if (element.sheet && element.sheet.toLowerCase() === old_name) {
|
|
1754
|
+
element.sheet = name;
|
|
1755
|
+
modified = true;
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
return true; // continue walk
|
|
1759
|
+
});
|
|
1760
|
+
if (modified) {
|
|
1761
|
+
return '=' + this.parser.Render(parsed.expression, { missing: '' });
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
return undefined;
|
|
1765
|
+
}
|
|
1766
|
+
/**
|
|
1767
|
+
* splitting this logic into a new function so we can reuse it
|
|
1768
|
+
* for invalidating broken references. generally we'll call this
|
|
1769
|
+
* on all sheets, but I wanted to leave the option open.
|
|
1770
|
+
*
|
|
1771
|
+
* @returns count of changes made. it's useful for the delete routine,
|
|
1772
|
+
* so we can force a recalc.
|
|
1773
|
+
*/
|
|
1774
|
+
RenameSheetReferences(sheets, old_name, name) {
|
|
1775
|
+
let changes = 0;
|
|
1776
|
+
old_name = old_name.toLowerCase();
|
|
1777
|
+
for (const sheet of sheets) {
|
|
1778
|
+
// cells
|
|
1779
|
+
for (const cell of sheet.cells.Iterate()) {
|
|
1780
|
+
if (cell.ValueIsFormula()) {
|
|
1781
|
+
const updated = this.PatchExpressionSheetName(cell.value || '', old_name, name);
|
|
1782
|
+
if (updated) {
|
|
1783
|
+
cell.value = updated;
|
|
1784
|
+
changes++;
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
/*
|
|
1789
|
+
sheet.cells.IterateAll((cell: Cell) => {
|
|
1790
|
+
if (cell.ValueIsFormula()) {
|
|
1791
|
+
const updated = this.PatchExpressionSheetName(cell.value||'', old_name, name);
|
|
1792
|
+
if (updated) {
|
|
1793
|
+
cell.value = updated;
|
|
1794
|
+
changes++;
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
});
|
|
1798
|
+
*/
|
|
1799
|
+
// conditionals
|
|
1800
|
+
if (sheet.conditional_formats?.length) {
|
|
1801
|
+
for (const format of sheet.conditional_formats) {
|
|
1802
|
+
switch (format.type) {
|
|
1803
|
+
case 'cell-match':
|
|
1804
|
+
case 'expression':
|
|
1805
|
+
{
|
|
1806
|
+
const updated = this.PatchExpressionSheetName(format.expression, old_name, name);
|
|
1807
|
+
if (updated) {
|
|
1808
|
+
format.expression = updated;
|
|
1809
|
+
changes++;
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
break;
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
// annotations
|
|
1817
|
+
for (const annotation of sheet.annotations) {
|
|
1818
|
+
if (annotation.data.formula) {
|
|
1819
|
+
const updated = this.PatchExpressionSheetName(annotation.data.formula, old_name, name);
|
|
1820
|
+
if (updated) {
|
|
1821
|
+
annotation.data.formula = updated;
|
|
1822
|
+
changes++;
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
for (const element of this.model.connected_elements.values()) {
|
|
1828
|
+
if (element.formula) {
|
|
1829
|
+
const updated = this.PatchExpressionSheetName(element.formula, old_name, name);
|
|
1830
|
+
if (updated) {
|
|
1831
|
+
element.formula = updated;
|
|
1832
|
+
changes++;
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
return changes;
|
|
1837
|
+
}
|
|
1838
|
+
/**
|
|
1839
|
+
* these are all addative except for "none", which removes all borders.
|
|
1840
|
+
*
|
|
1841
|
+
* we no longer put borders into two cells at once (hurrah!). however
|
|
1842
|
+
* we still need to do some maintenance on the mirror cells -- because
|
|
1843
|
+
* if you apply a border to cell A1, then that should take precedence
|
|
1844
|
+
* over any border previously applied to cell A2.
|
|
1845
|
+
*
|
|
1846
|
+
* FIXME: is that right? perhaps we should just leave whatever the user
|
|
1847
|
+
* did -- with the exception of clearing, which should always mirror.
|
|
1848
|
+
*
|
|
1849
|
+
*
|
|
1850
|
+
* UPDATE: modifying function for use with ExecCommand. runs the style
|
|
1851
|
+
* updates and returns the affected area.
|
|
1852
|
+
*
|
|
1853
|
+
*/
|
|
1854
|
+
ApplyBordersInternal(command) {
|
|
1855
|
+
const borders = command.borders;
|
|
1856
|
+
const width = (command.borders === BorderConstants.None)
|
|
1857
|
+
? 0 : command.width;
|
|
1858
|
+
const area = new Area(command.area.start, command.area.end);
|
|
1859
|
+
const sheet = this.FindSheet(area);
|
|
1860
|
+
area.start.sheet_id = sheet.id; // ensure
|
|
1861
|
+
/*
|
|
1862
|
+
let sheet = this.active_sheet;
|
|
1863
|
+
if (command.area.start.sheet_id && command.area.start.sheet_id !== this.active_sheet.id) {
|
|
1864
|
+
for (const compare of this.model.sheets) {
|
|
1865
|
+
if (compare.id === command.area.start.sheet_id) {
|
|
1866
|
+
sheet = compare;
|
|
1867
|
+
break;
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
*/
|
|
1872
|
+
const top = { border_top: width };
|
|
1873
|
+
const bottom = { border_bottom: width };
|
|
1874
|
+
const left = { border_left: width };
|
|
1875
|
+
const right = { border_right: width };
|
|
1876
|
+
const clear_top = { border_top: 0, border_top_fill: {} };
|
|
1877
|
+
const clear_bottom = { border_bottom: 0, border_bottom_fill: {} };
|
|
1878
|
+
const clear_left = { border_left: 0, border_left_fill: {} };
|
|
1879
|
+
const clear_right = { border_right: 0, border_right_fill: {} };
|
|
1880
|
+
// default to "none", which means "default"
|
|
1881
|
+
//if (!command.color) {
|
|
1882
|
+
// command.color = 'none';
|
|
1883
|
+
//}
|
|
1884
|
+
//if (typeof command.color !== 'undefined') {
|
|
1885
|
+
if (command.color) {
|
|
1886
|
+
// this is now an object so we need to clone it (might be faster to JSON->JSON)
|
|
1887
|
+
top.border_top_fill = { ...command.color };
|
|
1888
|
+
bottom.border_bottom_fill = { ...command.color };
|
|
1889
|
+
left.border_left_fill = { ...command.color };
|
|
1890
|
+
right.border_right_fill = { ...command.color };
|
|
1891
|
+
}
|
|
1892
|
+
else {
|
|
1893
|
+
// otherwise we should be sure to clear any color
|
|
1894
|
+
top.border_top_fill = {};
|
|
1895
|
+
bottom.border_bottom_fill = {};
|
|
1896
|
+
left.border_left_fill = {};
|
|
1897
|
+
right.border_right_fill = {};
|
|
1898
|
+
}
|
|
1899
|
+
// inside all/none
|
|
1900
|
+
if (borders === BorderConstants.None || borders === BorderConstants.All) {
|
|
1901
|
+
sheet.UpdateAreaStyle(area, {
|
|
1902
|
+
...top, ...bottom, ...left, ...right,
|
|
1903
|
+
}, true);
|
|
1904
|
+
}
|
|
1905
|
+
// top
|
|
1906
|
+
if (borders === BorderConstants.Top || borders === BorderConstants.Outside) {
|
|
1907
|
+
if (!area.entire_column) {
|
|
1908
|
+
sheet.UpdateAreaStyle(area.top, { ...top }, true);
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
// mirror top (CLEAR)
|
|
1912
|
+
if (borders === BorderConstants.None || borders === BorderConstants.All ||
|
|
1913
|
+
borders === BorderConstants.Outside || borders === BorderConstants.Top) {
|
|
1914
|
+
if (!area.entire_column) {
|
|
1915
|
+
if (area.start.row) {
|
|
1916
|
+
sheet.UpdateAreaStyle(new Area({ row: area.start.row - 1, column: area.start.column }, { row: area.start.row - 1, column: area.end.column }), { ...clear_bottom }, true);
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
// bottom
|
|
1921
|
+
if (borders === BorderConstants.Bottom || borders === BorderConstants.Outside) {
|
|
1922
|
+
if (!area.entire_column) {
|
|
1923
|
+
sheet.UpdateAreaStyle(area.bottom, { ...bottom }, true);
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
// mirror bottom (CLEAR)
|
|
1927
|
+
if (borders === BorderConstants.None || borders === BorderConstants.All ||
|
|
1928
|
+
borders === BorderConstants.Outside || borders === BorderConstants.Bottom) {
|
|
1929
|
+
if (!area.entire_column) {
|
|
1930
|
+
sheet.UpdateAreaStyle(new Area({ row: area.end.row + 1, column: area.start.column }, { row: area.end.row + 1, column: area.end.column }), { ...clear_top }, true);
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
// left
|
|
1934
|
+
if (borders === BorderConstants.Left || borders === BorderConstants.Outside) {
|
|
1935
|
+
if (!area.entire_row) {
|
|
1936
|
+
sheet.UpdateAreaStyle(area.left, { ...left }, true);
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
// mirror left (CLEAR)
|
|
1940
|
+
if (borders === BorderConstants.None || borders === BorderConstants.All ||
|
|
1941
|
+
borders === BorderConstants.Outside || borders === BorderConstants.Left) {
|
|
1942
|
+
if (!area.entire_row) {
|
|
1943
|
+
if (area.start.column) {
|
|
1944
|
+
sheet.UpdateAreaStyle(new Area({ row: area.start.row, column: area.start.column - 1 }, { row: area.end.row, column: area.start.column - 1 }), { ...clear_right }, true);
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
// right
|
|
1949
|
+
if (borders === BorderConstants.Right || borders === BorderConstants.Outside) {
|
|
1950
|
+
if (!area.entire_row) {
|
|
1951
|
+
sheet.UpdateAreaStyle(area.right, { ...right }, true);
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
// mirror right (CLEAR)
|
|
1955
|
+
if (borders === BorderConstants.None || borders === BorderConstants.All ||
|
|
1956
|
+
borders === BorderConstants.Outside || borders === BorderConstants.Right) {
|
|
1957
|
+
if (!area.entire_row) {
|
|
1958
|
+
sheet.UpdateAreaStyle(new Area({ row: area.start.row, column: area.end.column + 1 }, { row: area.end.row, column: area.end.column + 1 }), { ...clear_left }, true);
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
/*
|
|
1962
|
+
// why is there not an expand method on area? (FIXME)
|
|
1963
|
+
|
|
1964
|
+
this.DelayedRender(false, new Area({
|
|
1965
|
+
row: Math.max(0, area.start.row - 1),
|
|
1966
|
+
column: Math.max(0, area.start.column - 1),
|
|
1967
|
+
}, {
|
|
1968
|
+
row: area.end.row + 1,
|
|
1969
|
+
column: area.end.column + 1,
|
|
1970
|
+
}));
|
|
1971
|
+
|
|
1972
|
+
// NOTE: we don't have to route through the sheet. we are the only client
|
|
1973
|
+
// (we republish). we can just publish directly.
|
|
1974
|
+
|
|
1975
|
+
this.grid_events.Publish({ type: 'style', area });
|
|
1976
|
+
*/
|
|
1977
|
+
return Area.Bleed(area);
|
|
1978
|
+
/*
|
|
1979
|
+
return new Area(
|
|
1980
|
+
{
|
|
1981
|
+
row: Math.max(0, area.start.row - 1),
|
|
1982
|
+
column: Math.max(0, area.start.column - 1),
|
|
1983
|
+
}, {
|
|
1984
|
+
row: area.end.row + 1,
|
|
1985
|
+
column: area.end.column + 1,
|
|
1986
|
+
},
|
|
1987
|
+
);
|
|
1988
|
+
*/
|
|
1989
|
+
}
|
|
1990
|
+
TranslateR1C1(address, value) {
|
|
1991
|
+
let transformed = false;
|
|
1992
|
+
const cached = this.parser.flags.r1c1;
|
|
1993
|
+
this.parser.flags.r1c1 = true; // set
|
|
1994
|
+
if (typeof value === 'string' && value[0] === '=') {
|
|
1995
|
+
const result = this.parser.Parse(value);
|
|
1996
|
+
if (result.expression) {
|
|
1997
|
+
this.parser.Walk(result.expression, unit => {
|
|
1998
|
+
if (unit.type === 'address' && unit.r1c1) {
|
|
1999
|
+
transformed = true;
|
|
2000
|
+
// translate...
|
|
2001
|
+
if (unit.offset_column) {
|
|
2002
|
+
unit.absolute_column = false;
|
|
2003
|
+
unit.column = unit.column + address.column;
|
|
2004
|
+
}
|
|
2005
|
+
else {
|
|
2006
|
+
unit.absolute_column = true;
|
|
2007
|
+
}
|
|
2008
|
+
if (unit.offset_row) {
|
|
2009
|
+
unit.absolute_row = false;
|
|
2010
|
+
unit.row = unit.row + address.row;
|
|
2011
|
+
}
|
|
2012
|
+
else {
|
|
2013
|
+
unit.absolute_row = true;
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
return true;
|
|
2017
|
+
});
|
|
2018
|
+
if (transformed) {
|
|
2019
|
+
/*
|
|
2020
|
+
if (!this.flags.warned_r1c1) {
|
|
2021
|
+
|
|
2022
|
+
// 1-time warning
|
|
2023
|
+
|
|
2024
|
+
this.flags.warned_r1c1 = true;
|
|
2025
|
+
console.warn('NOTE: R1C1 support is experimental. the semantics may change in the future.');
|
|
2026
|
+
}
|
|
2027
|
+
*/
|
|
2028
|
+
value = '=' + this.parser.Render(result.expression, { missing: '' });
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
this.parser.flags.r1c1 = cached; // reset
|
|
2033
|
+
return value;
|
|
2034
|
+
}
|
|
2035
|
+
ClearAreaInternal(area) {
|
|
2036
|
+
// updated to use sheet ID. not sure why this was still using
|
|
2037
|
+
// active sheet without checking ID.
|
|
2038
|
+
let sheet;
|
|
2039
|
+
if (area.start.sheet_id) {
|
|
2040
|
+
sheet = this.model.sheets.Find(area.start.sheet_id);
|
|
2041
|
+
}
|
|
2042
|
+
else {
|
|
2043
|
+
sheet = this.active_sheet;
|
|
2044
|
+
}
|
|
2045
|
+
if (!sheet) {
|
|
2046
|
+
console.warn(`can't resolve sheet in ClearAreaInternal`);
|
|
2047
|
+
return;
|
|
2048
|
+
}
|
|
2049
|
+
// let error = false;
|
|
2050
|
+
area = sheet.RealArea(area); // collapse
|
|
2051
|
+
for (const cell of sheet.cells.Iterate(area)) {
|
|
2052
|
+
if (cell.area && !area.ContainsArea(cell.area)) {
|
|
2053
|
+
this.Error(ErrorCode.array);
|
|
2054
|
+
return;
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
/*
|
|
2058
|
+
sheet.cells.Apply(area, (cell) => {
|
|
2059
|
+
if (cell.area && !area.ContainsArea(cell.area)) {
|
|
2060
|
+
// throw new Error('can\'t change part of an array');
|
|
2061
|
+
error = true;
|
|
2062
|
+
}
|
|
2063
|
+
});
|
|
2064
|
+
*/
|
|
2065
|
+
// if the area completely encloses a table, delete the table
|
|
2066
|
+
const table_keys = this.model.tables.keys();
|
|
2067
|
+
for (const key of table_keys) {
|
|
2068
|
+
const table = this.model.tables.get(key);
|
|
2069
|
+
if (table && table.area.start.sheet_id === sheet.id) {
|
|
2070
|
+
const table_area = new Area(table.area.start, table.area.end);
|
|
2071
|
+
if (area.ContainsArea(table_area)) {
|
|
2072
|
+
for (let row = table_area.start.row; row <= table_area.end.row; row++) {
|
|
2073
|
+
for (let column = table_area.start.column; column <= table.area.end.column; column++) {
|
|
2074
|
+
const cell = sheet.cells.GetCell({ row, column }, false);
|
|
2075
|
+
if (cell) {
|
|
2076
|
+
cell.table = undefined;
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
this.model.tables.delete(key);
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
/*
|
|
2085
|
+
if (error) {
|
|
2086
|
+
this.Error(ErrorCode.array); // `You can't change part of an array.`
|
|
2087
|
+
}
|
|
2088
|
+
else {
|
|
2089
|
+
sheet.ClearArea(area);
|
|
2090
|
+
}
|
|
2091
|
+
*/
|
|
2092
|
+
sheet.ClearArea(area);
|
|
2093
|
+
}
|
|
2094
|
+
/**
|
|
2095
|
+
* send an error message. subscriber can figure out how to communicate it
|
|
2096
|
+
* to users.
|
|
2097
|
+
*
|
|
2098
|
+
* dropping strings, now we only allow error constants (via enum)
|
|
2099
|
+
*
|
|
2100
|
+
* @param message
|
|
2101
|
+
*/
|
|
2102
|
+
Error(message) {
|
|
2103
|
+
/*
|
|
2104
|
+
console.info('Error', message);
|
|
2105
|
+
if (typeof message === 'string') {
|
|
2106
|
+
this.grid_events.Publish({
|
|
2107
|
+
type: 'error',
|
|
2108
|
+
message,
|
|
2109
|
+
});
|
|
2110
|
+
}
|
|
2111
|
+
else {
|
|
2112
|
+
this.grid_events.Publish({
|
|
2113
|
+
type: 'error',
|
|
2114
|
+
code: message,
|
|
2115
|
+
});
|
|
2116
|
+
}
|
|
2117
|
+
*/
|
|
2118
|
+
this.grid_events.Publish({
|
|
2119
|
+
type: 'error',
|
|
2120
|
+
code: message,
|
|
2121
|
+
});
|
|
2122
|
+
}
|
|
2123
|
+
/**
|
|
2124
|
+
* this breaks (or doesn't work) if the add_tab option is false; that's
|
|
2125
|
+
* fine, although we might want to make a distinction between UI add-tab
|
|
2126
|
+
* and API add-tab. And allow it from the API.
|
|
2127
|
+
*
|
|
2128
|
+
* @param command
|
|
2129
|
+
* @returns
|
|
2130
|
+
*/
|
|
2131
|
+
DuplicateSheetInternal(command) {
|
|
2132
|
+
if (!this.options.add_tab) {
|
|
2133
|
+
console.warn('add tab option not set or false');
|
|
2134
|
+
return;
|
|
2135
|
+
}
|
|
2136
|
+
const source = this.ResolveSheet(command);
|
|
2137
|
+
const next_id = this.model.sheets.list.reduce((id, sheet) => Math.max(id, sheet.id), 0) + 1;
|
|
2138
|
+
let insert_index = -1;
|
|
2139
|
+
for (let i = 0; i < this.model.sheets.length; i++) {
|
|
2140
|
+
if (this.model.sheets.list[i] === source) {
|
|
2141
|
+
insert_index = i + 1;
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
if (!source || insert_index < 0) {
|
|
2145
|
+
throw new Error('source sheet not found');
|
|
2146
|
+
}
|
|
2147
|
+
// explicit insert index
|
|
2148
|
+
if (typeof command.insert_before === 'number') {
|
|
2149
|
+
insert_index = command.insert_before;
|
|
2150
|
+
}
|
|
2151
|
+
else if (typeof command.insert_before === 'string') {
|
|
2152
|
+
const lc = command.insert_before.toLowerCase();
|
|
2153
|
+
for (let i = 0; i < this.model.sheets.length; i++) {
|
|
2154
|
+
if (this.model.sheets.list[i].name.toLowerCase() === lc) {
|
|
2155
|
+
insert_index = i;
|
|
2156
|
+
break;
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
const options = {
|
|
2161
|
+
rendered_values: true,
|
|
2162
|
+
};
|
|
2163
|
+
const clone = Sheet.FromJSON(source.toJSON(options), this.model.theme_style_properties);
|
|
2164
|
+
let name = command.new_name || source.name;
|
|
2165
|
+
while (this.model.sheets.list.some((test) => test.name === name)) {
|
|
2166
|
+
const match = name.match(/^(.*?)(\d+)$/);
|
|
2167
|
+
if (match) {
|
|
2168
|
+
name = match[1] + (Number(match[2]) + 1);
|
|
2169
|
+
}
|
|
2170
|
+
else {
|
|
2171
|
+
name = name + '2';
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
clone.name = name;
|
|
2175
|
+
clone.id = next_id;
|
|
2176
|
+
// console.info('CLONE', clone.id, clone);
|
|
2177
|
+
this.model.sheets.Splice(insert_index, 0, clone);
|
|
2178
|
+
// if (this.tab_bar) { this.tab_bar.Update(); }
|
|
2179
|
+
return clone.id;
|
|
2180
|
+
}
|
|
2181
|
+
/**
|
|
2182
|
+
* this is the callback method for the command-log select command
|
|
2183
|
+
* (which is not widely used). it does nothing. the specialization
|
|
2184
|
+
* should do something.
|
|
2185
|
+
*
|
|
2186
|
+
* @param command
|
|
2187
|
+
*/
|
|
2188
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2189
|
+
SelectInternal(command) {
|
|
2190
|
+
// does nothing
|
|
2191
|
+
}
|
|
2192
|
+
FreezeInternal(command) {
|
|
2193
|
+
const sheet = this.FindSheet(command.sheet_id || this.active_sheet.id);
|
|
2194
|
+
sheet.freeze.rows = command.rows;
|
|
2195
|
+
sheet.freeze.columns = command.columns;
|
|
2196
|
+
}
|
|
2197
|
+
/**
|
|
2198
|
+
* patch an expression for insert/delete row/column operations.
|
|
2199
|
+
* FIXME: should move, maybe to parser? (...)
|
|
2200
|
+
*
|
|
2201
|
+
* NOTE: did I write this twice? we only need one. check which one is better.
|
|
2202
|
+
* @see PatchFormulasInternal
|
|
2203
|
+
*
|
|
2204
|
+
* @returns the updated expression, or `undefined` if no changes were made.
|
|
2205
|
+
*/
|
|
2206
|
+
PatchExpression(expression, options) {
|
|
2207
|
+
let count = 0;
|
|
2208
|
+
const parse_result = this.parser.Parse(expression);
|
|
2209
|
+
if (parse_result.expression) {
|
|
2210
|
+
this.parser.Walk(parse_result.expression, unit => {
|
|
2211
|
+
if (unit.type === 'range') {
|
|
2212
|
+
if (!unit.start.sheet || (unit.start.sheet.toLowerCase() === options.sheet.name.toLowerCase())) {
|
|
2213
|
+
const updated = Area.PatchArea(unit, options);
|
|
2214
|
+
if (updated) {
|
|
2215
|
+
unit.start.row = updated.start.row;
|
|
2216
|
+
unit.start.column = updated.start.column;
|
|
2217
|
+
unit.end.row = updated.end.row;
|
|
2218
|
+
unit.end.column = updated.end.column;
|
|
2219
|
+
}
|
|
2220
|
+
else {
|
|
2221
|
+
// FIXME: maybe have options for this? we don't really have a
|
|
2222
|
+
// good way to replace nodes atm
|
|
2223
|
+
unit.start.row = unit.end.row = unit.start.column = unit.end.column = -1;
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
count++;
|
|
2227
|
+
return false;
|
|
2228
|
+
}
|
|
2229
|
+
else if (unit.type === 'address') {
|
|
2230
|
+
const updated = Area.PatchArea({ start: unit, end: unit }, options);
|
|
2231
|
+
if (updated) {
|
|
2232
|
+
unit.row = updated.start.row;
|
|
2233
|
+
unit.column = updated.start.column;
|
|
2234
|
+
}
|
|
2235
|
+
else {
|
|
2236
|
+
// see above
|
|
2237
|
+
unit.row = unit.column = -1;
|
|
2238
|
+
}
|
|
2239
|
+
count++;
|
|
2240
|
+
return false;
|
|
2241
|
+
}
|
|
2242
|
+
return true;
|
|
2243
|
+
});
|
|
2244
|
+
if (count) {
|
|
2245
|
+
const rendered = this.parser.Render(parse_result.expression, {
|
|
2246
|
+
missing: '',
|
|
2247
|
+
});
|
|
2248
|
+
// console.info("FROM", expression, "TO", rendered);
|
|
2249
|
+
return rendered;
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
return undefined;
|
|
2253
|
+
}
|
|
2254
|
+
/**
|
|
2255
|
+
* patch sheet conditionals for insert/delete row/column operations.
|
|
2256
|
+
* some of them may be deleted.
|
|
2257
|
+
*
|
|
2258
|
+
* UPDATE: using this routine to also patch data validations
|
|
2259
|
+
*/
|
|
2260
|
+
PatchConditionalsAndValidations(options) {
|
|
2261
|
+
if (options.sheet.data_validation.length) {
|
|
2262
|
+
const delete_list = new Set();
|
|
2263
|
+
for (const validation of options.sheet.data_validation) {
|
|
2264
|
+
const targets = [];
|
|
2265
|
+
for (const area of validation.target) {
|
|
2266
|
+
const updated = Area.PatchArea(area, options);
|
|
2267
|
+
if (updated) {
|
|
2268
|
+
targets.push(updated);
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
if (targets.length > 0) {
|
|
2272
|
+
validation.target = targets;
|
|
2273
|
+
// format the range, if necessary
|
|
2274
|
+
if (validation.type === 'range') {
|
|
2275
|
+
if (validation.area.start.sheet_id === options.sheet.id) {
|
|
2276
|
+
const updated = Area.PatchArea(validation.area, options);
|
|
2277
|
+
if (updated) {
|
|
2278
|
+
validation.area = updated;
|
|
2279
|
+
}
|
|
2280
|
+
else {
|
|
2281
|
+
delete_list.add(validation);
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
else {
|
|
2287
|
+
delete_list.add(validation);
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
if (delete_list.size) {
|
|
2291
|
+
options.sheet.data_validation = options.sheet.data_validation.filter(test => !delete_list.has(test));
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
if (options.sheet.conditional_formats?.length) {
|
|
2295
|
+
const delete_list = new Set();
|
|
2296
|
+
for (const format of options.sheet.conditional_formats) {
|
|
2297
|
+
// first adjust the format area
|
|
2298
|
+
const updated = Area.PatchArea(format.area, options);
|
|
2299
|
+
if (updated) {
|
|
2300
|
+
format.area = JSON.parse(JSON.stringify(updated));
|
|
2301
|
+
}
|
|
2302
|
+
else {
|
|
2303
|
+
delete_list.add(format);
|
|
2304
|
+
continue; // don't bother with formula
|
|
2305
|
+
}
|
|
2306
|
+
// next update the formula, if necessary. what do we do if the
|
|
2307
|
+
// area has disappeared? should be a #REF error, not sure we
|
|
2308
|
+
// can encode that properly
|
|
2309
|
+
switch (format.type) {
|
|
2310
|
+
case 'expression':
|
|
2311
|
+
case 'cell-match':
|
|
2312
|
+
{
|
|
2313
|
+
const updated = this.PatchExpression(format.expression, options);
|
|
2314
|
+
if (updated) {
|
|
2315
|
+
format.expression = updated;
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
break;
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
if (delete_list.size) {
|
|
2322
|
+
options.sheet.conditional_formats = options.sheet.conditional_formats.filter(test => !delete_list.has(test));
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
/**
|
|
2327
|
+
* FIXME: should be API method
|
|
2328
|
+
* FIXME: need to handle annotations that are address-based
|
|
2329
|
+
*
|
|
2330
|
+
* @see InsertColumns for inline comments
|
|
2331
|
+
*/
|
|
2332
|
+
InsertRowsInternal(command) {
|
|
2333
|
+
const target_sheet = this.FindSheet(command.sheet_id);
|
|
2334
|
+
if (command.count === Infinity) {
|
|
2335
|
+
command.count = 1; // ?
|
|
2336
|
+
}
|
|
2337
|
+
else if (command.count === -Infinity) {
|
|
2338
|
+
command.count = -target_sheet.rows; // delete all
|
|
2339
|
+
}
|
|
2340
|
+
if (!target_sheet.InsertRows(command.before_row, command.count)) {
|
|
2341
|
+
// this.Error(`You can't change part of an array.`);
|
|
2342
|
+
this.Error(ErrorCode.array);
|
|
2343
|
+
return { error: true };
|
|
2344
|
+
}
|
|
2345
|
+
// conditionals
|
|
2346
|
+
this.PatchConditionalsAndValidations({
|
|
2347
|
+
sheet: target_sheet,
|
|
2348
|
+
before_column: 0,
|
|
2349
|
+
column_count: 0,
|
|
2350
|
+
before_row: command.before_row,
|
|
2351
|
+
row_count: command.count
|
|
2352
|
+
});
|
|
2353
|
+
// connected elements
|
|
2354
|
+
for (const external of this.model.connected_elements.values()) {
|
|
2355
|
+
if (external.formula) {
|
|
2356
|
+
const modified = this.PatchFormulasInternal(external.formula, command.before_row, command.count, 0, 0, target_sheet.name.toLowerCase(), false);
|
|
2357
|
+
if (modified) {
|
|
2358
|
+
external.formula = modified;
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
// see InsertColumnsInternal re: tables. rows are less complicated,
|
|
2363
|
+
// except that if you delete the header row we want to remove the
|
|
2364
|
+
// table entirely.
|
|
2365
|
+
const tables = Array.from(this.model.tables.values());
|
|
2366
|
+
for (const table of tables) {
|
|
2367
|
+
if (table.area.start.sheet_id === command.sheet_id) {
|
|
2368
|
+
if (command.count > 0) {
|
|
2369
|
+
if (command.before_row <= table.area.start.row) {
|
|
2370
|
+
// shift the table down
|
|
2371
|
+
//console.info("shift table down");
|
|
2372
|
+
table.area.start.row += command.count;
|
|
2373
|
+
table.area.end.row += command.count;
|
|
2374
|
+
}
|
|
2375
|
+
else if (command.before_row <= table.area.end.row) {
|
|
2376
|
+
// insert rows. we need to add references to
|
|
2377
|
+
// cells that have been inserted.
|
|
2378
|
+
// console.info("insert table rows");
|
|
2379
|
+
table.area.end.row += command.count;
|
|
2380
|
+
for (let row = table.area.start.row; row <= table.area.end.row; row++) {
|
|
2381
|
+
for (let column = table.area.start.column; column <= table.area.end.column; column++) {
|
|
2382
|
+
const cell = target_sheet.CellData({ row, column });
|
|
2383
|
+
cell.table = table;
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
}
|
|
2388
|
+
else {
|
|
2389
|
+
if (command.before_row <= table.area.start.row) {
|
|
2390
|
+
if (command.before_row - command.count <= table.area.start.row) {
|
|
2391
|
+
// shift table up
|
|
2392
|
+
table.area.start.row += command.count;
|
|
2393
|
+
table.area.end.row += command.count;
|
|
2394
|
+
}
|
|
2395
|
+
else if (command.before_row - command.count >= table.area.end.row) {
|
|
2396
|
+
// remove the entire table
|
|
2397
|
+
this.model.tables.delete(table.name.toLowerCase());
|
|
2398
|
+
}
|
|
2399
|
+
else {
|
|
2400
|
+
// assuming this will remove the header row, drop the table
|
|
2401
|
+
// altogether. the alternative is to just not let you remove
|
|
2402
|
+
// this row. but that should be handled before you get here;
|
|
2403
|
+
// if you get here, and you want to delete the row, then the
|
|
2404
|
+
// table will go.
|
|
2405
|
+
this.model.tables.delete(table.name.toLowerCase());
|
|
2406
|
+
for (let row = command.before_row; row <= table.area.end.row; row++) {
|
|
2407
|
+
for (let column = table.area.start.column; column <= table.area.end.column; column++) {
|
|
2408
|
+
const cell = target_sheet.CellData({ row, column });
|
|
2409
|
+
if (cell.table === table) {
|
|
2410
|
+
cell.table = undefined;
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
else if (command.before_row <= table.area.end.row) {
|
|
2417
|
+
// remove table rows from the end. cap.
|
|
2418
|
+
// we may be removing the totals row -- in that case, update the table to reflect.
|
|
2419
|
+
if (command.before_row - command.count > table.area.end.row) {
|
|
2420
|
+
table.totals_row = false;
|
|
2421
|
+
}
|
|
2422
|
+
table.area.end.row = Math.max(0, table.area.end.row + command.count, command.before_row - 1);
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
this.model.named.PatchNamedRanges(target_sheet.id, 0, 0, command.before_row, command.count);
|
|
2428
|
+
const target_sheet_name = target_sheet.name.toLowerCase();
|
|
2429
|
+
for (const sheet of this.model.sheets.list) {
|
|
2430
|
+
const is_target = sheet === target_sheet;
|
|
2431
|
+
for (const cell of sheet.cells.Iterate()) {
|
|
2432
|
+
if (cell.ValueIsFormula()) {
|
|
2433
|
+
const modified = this.PatchFormulasInternal(cell.value || '', command.before_row, command.count, 0, 0, target_sheet_name, is_target);
|
|
2434
|
+
if (modified) {
|
|
2435
|
+
cell.value = modified;
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
/*
|
|
2440
|
+
sheet.cells.IterateAll((cell: Cell) => {
|
|
2441
|
+
if (cell.ValueIsFormula()) {
|
|
2442
|
+
const modified = this.PatchFormulasInternal(cell.value || '',
|
|
2443
|
+
command.before_row, command.count, 0, 0,
|
|
2444
|
+
target_sheet_name, is_target);
|
|
2445
|
+
if (modified) {
|
|
2446
|
+
cell.value = modified;
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
});
|
|
2450
|
+
*/
|
|
2451
|
+
for (const annotation of sheet.annotations) {
|
|
2452
|
+
if (annotation.data.formula) {
|
|
2453
|
+
const modified = this.PatchFormulasInternal(annotation.data.formula || '', command.before_row, command.count, 0, 0, target_sheet_name, is_target);
|
|
2454
|
+
if (modified) {
|
|
2455
|
+
annotation.data.formula = modified;
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
// annotations
|
|
2461
|
+
const update_annotations_list = [];
|
|
2462
|
+
const resize_annotations_list = [];
|
|
2463
|
+
const delete_annotations_list = [];
|
|
2464
|
+
if (command.count > 0) { // insert
|
|
2465
|
+
const first = command.before_row;
|
|
2466
|
+
for (const annotation of target_sheet.annotations) {
|
|
2467
|
+
if (annotation.data.layout) {
|
|
2468
|
+
const [start, end, endy] = [
|
|
2469
|
+
annotation.data.layout.tl.address.row,
|
|
2470
|
+
annotation.data.layout.br.address.row,
|
|
2471
|
+
annotation.data.layout.br.offset.y,
|
|
2472
|
+
];
|
|
2473
|
+
if (first <= start) {
|
|
2474
|
+
// start case 1: starts above the annotation (including exactly at the top)
|
|
2475
|
+
// shift
|
|
2476
|
+
annotation.data.layout.tl.address.row += command.count;
|
|
2477
|
+
annotation.data.layout.br.address.row += command.count;
|
|
2478
|
+
}
|
|
2479
|
+
else if (first < end || first === end && endy > 0) {
|
|
2480
|
+
// start case 2: starts in the annotation, omitting the first row
|
|
2481
|
+
annotation.data.layout.br.address.row += command.count;
|
|
2482
|
+
// size changing
|
|
2483
|
+
resize_annotations_list.push(annotation);
|
|
2484
|
+
}
|
|
2485
|
+
else {
|
|
2486
|
+
// do nothing
|
|
2487
|
+
continue;
|
|
2488
|
+
}
|
|
2489
|
+
update_annotations_list.push(annotation);
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
else if (command.count < 0) { // delete
|
|
2494
|
+
// first and last column deleted
|
|
2495
|
+
const first = command.before_row;
|
|
2496
|
+
const last = command.before_row - command.count - 1;
|
|
2497
|
+
for (const annotation of target_sheet.annotations) {
|
|
2498
|
+
if (annotation.data.layout) {
|
|
2499
|
+
// start and end row of the annotation. recall that in
|
|
2500
|
+
// this layout, the annotation may extend into the (first,last)
|
|
2501
|
+
// row but not beyond it. the offset is _within_ the row.
|
|
2502
|
+
const [start, end, endy] = [
|
|
2503
|
+
annotation.data.layout.tl.address.row,
|
|
2504
|
+
annotation.data.layout.br.address.row,
|
|
2505
|
+
annotation.data.layout.br.offset.y,
|
|
2506
|
+
];
|
|
2507
|
+
if (first <= start) {
|
|
2508
|
+
// start case 1: starts above the annotation (including exactly at the top)
|
|
2509
|
+
if (last < start) {
|
|
2510
|
+
// end case 1: ends before the annotation
|
|
2511
|
+
// shift
|
|
2512
|
+
annotation.data.layout.tl.address.row += command.count;
|
|
2513
|
+
annotation.data.layout.br.address.row += command.count;
|
|
2514
|
+
}
|
|
2515
|
+
else if (last < end - 1 || (last === end - 1 && endy > 0)) {
|
|
2516
|
+
// end case 2: ends before the end of the annotation
|
|
2517
|
+
// shift + cut
|
|
2518
|
+
annotation.data.layout.tl.address.row = first;
|
|
2519
|
+
annotation.data.layout.tl.offset.y = 0;
|
|
2520
|
+
annotation.data.layout.br.address.row += command.count;
|
|
2521
|
+
// size changing
|
|
2522
|
+
resize_annotations_list.push(annotation);
|
|
2523
|
+
}
|
|
2524
|
+
else {
|
|
2525
|
+
// end case 3: ends after the annotation
|
|
2526
|
+
// drop the annotation
|
|
2527
|
+
delete_annotations_list.push(annotation);
|
|
2528
|
+
continue;
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
else if (first < end || first === end && endy > 0) {
|
|
2532
|
+
// start case 2: starts in the annotation, omitting the first row
|
|
2533
|
+
if (last < end - 1 || (last === end - 1 && endy > 0)) {
|
|
2534
|
+
// end case 2: ends before the end of the annotation
|
|
2535
|
+
// shorten
|
|
2536
|
+
annotation.data.layout.br.address.row += command.count;
|
|
2537
|
+
// size changing
|
|
2538
|
+
resize_annotations_list.push(annotation);
|
|
2539
|
+
}
|
|
2540
|
+
else {
|
|
2541
|
+
// end case 3: ends after the annotation
|
|
2542
|
+
// clip
|
|
2543
|
+
annotation.data.layout.br.address.row = first;
|
|
2544
|
+
annotation.data.layout.br.offset.y = 0;
|
|
2545
|
+
// size changing
|
|
2546
|
+
resize_annotations_list.push(annotation);
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
else {
|
|
2550
|
+
// start case 3: starts after the annotation
|
|
2551
|
+
// do nothing
|
|
2552
|
+
continue;
|
|
2553
|
+
}
|
|
2554
|
+
update_annotations_list.push(annotation);
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
for (const annotation of delete_annotations_list) {
|
|
2559
|
+
target_sheet.annotations = target_sheet.annotations.filter(test => test !== annotation);
|
|
2560
|
+
}
|
|
2561
|
+
return {
|
|
2562
|
+
update_annotations_list,
|
|
2563
|
+
resize_annotations_list,
|
|
2564
|
+
delete_annotations_list,
|
|
2565
|
+
};
|
|
2566
|
+
}
|
|
2567
|
+
/**
|
|
2568
|
+
*
|
|
2569
|
+
*/
|
|
2570
|
+
InsertColumnsInternal(command) {
|
|
2571
|
+
const target_sheet = this.FindSheet(command.sheet_id);
|
|
2572
|
+
// it seems like we never get an insert infinity. not sure why,
|
|
2573
|
+
// but the UI is blocking that. we should handle it anyway jic
|
|
2574
|
+
if (command.count === Infinity) {
|
|
2575
|
+
command.count = 1; // ?
|
|
2576
|
+
}
|
|
2577
|
+
else if (command.count === -Infinity) {
|
|
2578
|
+
command.count = -target_sheet.columns; // delete all
|
|
2579
|
+
}
|
|
2580
|
+
// FIXME: we need to get this error out earlier. before this call,
|
|
2581
|
+
// in the call that generates the insert event. otherwise if we
|
|
2582
|
+
// have remotes, everyone will see the error -- we only want the
|
|
2583
|
+
// actual actor to see the error.
|
|
2584
|
+
if (!target_sheet.InsertColumns(command.before_column, command.count)) {
|
|
2585
|
+
// this.Error(`You can't change part of an array.`);
|
|
2586
|
+
this.Error(ErrorCode.array);
|
|
2587
|
+
return { error: true };
|
|
2588
|
+
}
|
|
2589
|
+
// conditionals
|
|
2590
|
+
this.PatchConditionalsAndValidations({
|
|
2591
|
+
sheet: target_sheet,
|
|
2592
|
+
before_column: command.before_column,
|
|
2593
|
+
column_count: command.count,
|
|
2594
|
+
before_row: 0,
|
|
2595
|
+
row_count: 0
|
|
2596
|
+
});
|
|
2597
|
+
// connected elements
|
|
2598
|
+
for (const element of this.model.connected_elements.values()) {
|
|
2599
|
+
if (element.formula) {
|
|
2600
|
+
const modified = this.PatchFormulasInternal(element.formula, 0, 0, command.before_column, command.count, target_sheet.name.toLowerCase(), false);
|
|
2601
|
+
if (modified) {
|
|
2602
|
+
element.formula = modified;
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
// patch tables. we removed this from the sheet routine entirely,
|
|
2607
|
+
// we need to rebuild any affected tables now.
|
|
2608
|
+
// NOTE: we may drop tables, so we can't use a live iterator. or
|
|
2609
|
+
// is the iterator precomputed? not sure. let's flatten immediately jic.
|
|
2610
|
+
const tables = Array.from(this.model.tables.values());
|
|
2611
|
+
for (const table of tables) {
|
|
2612
|
+
if (table.area.start.sheet_id === command.sheet_id) {
|
|
2613
|
+
if (command.count > 0) {
|
|
2614
|
+
if (command.before_column <= table.area.start.column) {
|
|
2615
|
+
// shift the table to the right. update the table reference,
|
|
2616
|
+
// we can skip updating headers as the columns haven't changed.
|
|
2617
|
+
// console.info("shift table right");
|
|
2618
|
+
table.area.start.column += command.count;
|
|
2619
|
+
table.area.end.column += command.count;
|
|
2620
|
+
}
|
|
2621
|
+
else if (command.before_column <= table.area.end.column) {
|
|
2622
|
+
// insert columns -- we need to add references to new
|
|
2623
|
+
// cells, and update headers.
|
|
2624
|
+
// console.info("insert table columns");
|
|
2625
|
+
table.area.end.column += command.count;
|
|
2626
|
+
for (let row = table.area.start.row; row <= table.area.end.row; row++) {
|
|
2627
|
+
for (let column = table.area.start.column; column <= table.area.end.column; column++) {
|
|
2628
|
+
const cell = target_sheet.CellData({ row, column });
|
|
2629
|
+
cell.table = table;
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
this.UpdateTableColumns(table);
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
else {
|
|
2636
|
+
if (command.before_column <= table.area.start.column) {
|
|
2637
|
+
if (command.before_column - command.count <= table.area.start.column) {
|
|
2638
|
+
// shift table left. update the table reference, we can skip headers.
|
|
2639
|
+
// console.info("shift table left");
|
|
2640
|
+
table.area.start.column += command.count;
|
|
2641
|
+
table.area.end.column += command.count;
|
|
2642
|
+
}
|
|
2643
|
+
else if (command.before_column - command.count >= table.area.end.column) {
|
|
2644
|
+
// remove entire table. cells are already removed, we can just
|
|
2645
|
+
// drop the table from the model.
|
|
2646
|
+
// console.info("remove table");
|
|
2647
|
+
this.model.tables.delete(table.name.toLowerCase());
|
|
2648
|
+
}
|
|
2649
|
+
else {
|
|
2650
|
+
// shift to the left, then remove table columns. cells are
|
|
2651
|
+
// already removed, so we don't need to touch cells; just
|
|
2652
|
+
// update the reference and column headers.
|
|
2653
|
+
// console.info("remove table columns (1)");
|
|
2654
|
+
table.area.start.column = command.before_column;
|
|
2655
|
+
table.area.end.column += command.count;
|
|
2656
|
+
this.UpdateTableColumns(table);
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
else if (command.before_column <= table.area.end.column) {
|
|
2660
|
+
// remove table columns. as above. cap.
|
|
2661
|
+
// console.info("remove table columns (2)");
|
|
2662
|
+
table.area.end.column = Math.max(0, table.area.end.column + command.count, command.before_column - 1);
|
|
2663
|
+
this.UpdateTableColumns(table);
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
this.model.named.PatchNamedRanges(target_sheet.id, command.before_column, command.count, 0, 0);
|
|
2669
|
+
// FIXME: we need an event here?
|
|
2670
|
+
// A: caller sends a "structure" event after this call. that doesn't include
|
|
2671
|
+
// affected areas, though. need to think about whether structure event
|
|
2672
|
+
// triggers a recalc (probably should). we could track whether we've made
|
|
2673
|
+
// any modifications (and maybe also whether we now have any invalid
|
|
2674
|
+
// references)
|
|
2675
|
+
// patch all sheets
|
|
2676
|
+
// you know we have a calculator that has backward-and-forward references.
|
|
2677
|
+
// we could theoretically ask the calculator what needs to be changed.
|
|
2678
|
+
//
|
|
2679
|
+
// for the most part, we try to maintain separation between the display
|
|
2680
|
+
// (this) and the calculator. we could ask, but this isn't terrible and
|
|
2681
|
+
// helps maintain that separation.
|
|
2682
|
+
const target_sheet_name = target_sheet.name.toLowerCase();
|
|
2683
|
+
for (const sheet of this.model.sheets.list) {
|
|
2684
|
+
const is_target = sheet === target_sheet;
|
|
2685
|
+
for (const cell of sheet.cells.Iterate()) {
|
|
2686
|
+
if (cell.ValueIsFormula()) {
|
|
2687
|
+
const modified = this.PatchFormulasInternal(cell.value || '', 0, 0, command.before_column, command.count, target_sheet_name, is_target);
|
|
2688
|
+
if (modified) {
|
|
2689
|
+
cell.value = modified;
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
/*
|
|
2694
|
+
sheet.cells.IterateAll((cell: Cell) => {
|
|
2695
|
+
if (cell.ValueIsFormula()) {
|
|
2696
|
+
const modified = this.PatchFormulasInternal(cell.value || '', 0, 0,
|
|
2697
|
+
command.before_column, command.count,
|
|
2698
|
+
target_sheet_name, is_target);
|
|
2699
|
+
if (modified) {
|
|
2700
|
+
cell.value = modified;
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
});
|
|
2704
|
+
*/
|
|
2705
|
+
for (const annotation of sheet.annotations) {
|
|
2706
|
+
if (annotation.data.formula) {
|
|
2707
|
+
const modified = this.PatchFormulasInternal(annotation.data.formula, 0, 0, command.before_column, command.count, target_sheet_name, is_target);
|
|
2708
|
+
if (modified) {
|
|
2709
|
+
annotation.data.formula = modified;
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
// annotations
|
|
2715
|
+
const update_annotations_list = [];
|
|
2716
|
+
const resize_annotations_list = [];
|
|
2717
|
+
const delete_annotations_list = [];
|
|
2718
|
+
if (command.count > 0) { // insert
|
|
2719
|
+
const first = command.before_column;
|
|
2720
|
+
for (const annotation of target_sheet.annotations) {
|
|
2721
|
+
if (annotation.data.layout) {
|
|
2722
|
+
const [start, end, endx] = [
|
|
2723
|
+
annotation.data.layout.tl.address.column,
|
|
2724
|
+
annotation.data.layout.br.address.column,
|
|
2725
|
+
annotation.data.layout.br.offset.x,
|
|
2726
|
+
];
|
|
2727
|
+
if (first <= start) {
|
|
2728
|
+
// start case 1: starts to the left of the annotation (including exactly at the left)
|
|
2729
|
+
// shift
|
|
2730
|
+
annotation.data.layout.tl.address.column += command.count;
|
|
2731
|
+
annotation.data.layout.br.address.column += command.count;
|
|
2732
|
+
}
|
|
2733
|
+
else if (first < end || first === end && endx > 0) {
|
|
2734
|
+
// start case 2: starts in the annotation, omitting the first column
|
|
2735
|
+
annotation.data.layout.br.address.column += command.count;
|
|
2736
|
+
// size changing
|
|
2737
|
+
resize_annotations_list.push(annotation);
|
|
2738
|
+
}
|
|
2739
|
+
else {
|
|
2740
|
+
// do nothing
|
|
2741
|
+
continue;
|
|
2742
|
+
}
|
|
2743
|
+
update_annotations_list.push(annotation);
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
else if (command.count < 0) { // delete
|
|
2748
|
+
// first and last column deleted
|
|
2749
|
+
const first = command.before_column;
|
|
2750
|
+
const last = command.before_column - command.count - 1;
|
|
2751
|
+
for (const annotation of target_sheet.annotations) {
|
|
2752
|
+
if (annotation.data.layout) {
|
|
2753
|
+
// start and end column of the annotation. recall that in
|
|
2754
|
+
// this layout, the annotation may extend into the (first,last)
|
|
2755
|
+
// column but not beyond it. the offset is _within_ the column.
|
|
2756
|
+
const [start, end, endx] = [
|
|
2757
|
+
annotation.data.layout.tl.address.column,
|
|
2758
|
+
annotation.data.layout.br.address.column,
|
|
2759
|
+
annotation.data.layout.br.offset.x,
|
|
2760
|
+
];
|
|
2761
|
+
if (first <= start) {
|
|
2762
|
+
// start case 1: starts to the left of the annotation (including exactly at the left)
|
|
2763
|
+
if (last < start) {
|
|
2764
|
+
// end case 1: ends before the annotation
|
|
2765
|
+
// shift
|
|
2766
|
+
annotation.data.layout.tl.address.column += command.count;
|
|
2767
|
+
annotation.data.layout.br.address.column += command.count;
|
|
2768
|
+
}
|
|
2769
|
+
else if (last < end - 1 || (last === end - 1 && endx > 0)) {
|
|
2770
|
+
// end case 2: ends before the end of the annotation
|
|
2771
|
+
// shift + cut
|
|
2772
|
+
annotation.data.layout.tl.address.column = first;
|
|
2773
|
+
annotation.data.layout.tl.offset.x = 0;
|
|
2774
|
+
annotation.data.layout.br.address.column += command.count;
|
|
2775
|
+
// size changing
|
|
2776
|
+
resize_annotations_list.push(annotation);
|
|
2777
|
+
}
|
|
2778
|
+
else {
|
|
2779
|
+
// end case 3: ends after the annotation
|
|
2780
|
+
// drop the annotation
|
|
2781
|
+
delete_annotations_list.push(annotation);
|
|
2782
|
+
continue;
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
else if (first < end || first === end && endx > 0) {
|
|
2786
|
+
// start case 2: starts in the annotation, omitting the first column
|
|
2787
|
+
if (last < end - 1 || (last === end - 1 && endx > 0)) {
|
|
2788
|
+
// end case 2: ends before the end of the annotation
|
|
2789
|
+
// shorten
|
|
2790
|
+
annotation.data.layout.br.address.column += command.count;
|
|
2791
|
+
// size changing
|
|
2792
|
+
resize_annotations_list.push(annotation);
|
|
2793
|
+
}
|
|
2794
|
+
else {
|
|
2795
|
+
// end case 3: ends after the annotation
|
|
2796
|
+
// clip
|
|
2797
|
+
annotation.data.layout.br.address.column = first;
|
|
2798
|
+
annotation.data.layout.br.offset.x = 0;
|
|
2799
|
+
// size changing
|
|
2800
|
+
resize_annotations_list.push(annotation);
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2803
|
+
else {
|
|
2804
|
+
// start case 3: starts after the annotation
|
|
2805
|
+
// do nothing
|
|
2806
|
+
continue;
|
|
2807
|
+
}
|
|
2808
|
+
update_annotations_list.push(annotation);
|
|
2809
|
+
}
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2812
|
+
for (const annotation of delete_annotations_list) {
|
|
2813
|
+
target_sheet.annotations = target_sheet.annotations.filter(test => test !== annotation);
|
|
2814
|
+
}
|
|
2815
|
+
return {
|
|
2816
|
+
update_annotations_list,
|
|
2817
|
+
resize_annotations_list,
|
|
2818
|
+
delete_annotations_list,
|
|
2819
|
+
};
|
|
2820
|
+
}
|
|
2821
|
+
//////////////////////////////////////////////////////////////////////////////
|
|
2822
|
+
/**
|
|
2823
|
+
* pass all data/style/structure operations through a command mechanism.
|
|
2824
|
+
* this method should optimally act as a dispatcher, so try to minimize
|
|
2825
|
+
* inline code in favor of method calls.
|
|
2826
|
+
*
|
|
2827
|
+
* [NOTE: don't go crazy with that, some simple operations can be inlined]
|
|
2828
|
+
*
|
|
2829
|
+
* NOTE: working on coediting. we will need to handle different sheets.
|
|
2830
|
+
* going to work one command at a time...
|
|
2831
|
+
*
|
|
2832
|
+
* @param queue -- push on the command log. this is default true so it
|
|
2833
|
+
* doesn't change existing behavior, but you can turn it off if the message
|
|
2834
|
+
* comes from a remote queue.
|
|
2835
|
+
*
|
|
2836
|
+
*/
|
|
2837
|
+
ExecCommand(commands, queue = true) {
|
|
2838
|
+
// FIXME: support ephemeral commands (...)
|
|
2839
|
+
// data and style events were triggered by the areas being set.
|
|
2840
|
+
// we are not necessarily setting them for offsheet changes, so
|
|
2841
|
+
// we need an explicit flag. this should be logically OR'ed with
|
|
2842
|
+
// the area existing (for purposes of sending an event).
|
|
2843
|
+
// all flags/areas moved to this struct
|
|
2844
|
+
const flags = {
|
|
2845
|
+
pending: [],
|
|
2846
|
+
};
|
|
2847
|
+
const events = [];
|
|
2848
|
+
// should we normalize always, or only if we're queueing?
|
|
2849
|
+
// it seems like it's useful here, then we can be a little
|
|
2850
|
+
// sloppier in the actual handlers. after normalizing, any
|
|
2851
|
+
// command that has an address/area (or sheet ID parameter)
|
|
2852
|
+
// will have an explicit sheet ID.
|
|
2853
|
+
commands = this.NormalizeCommands(commands);
|
|
2854
|
+
// FIXME: we should queue later, so we can remove any commands
|
|
2855
|
+
// that fail... throw errors, and so on
|
|
2856
|
+
if (queue) {
|
|
2857
|
+
this.command_log.Publish({ command: commands, timestamp: new Date().getTime() });
|
|
2858
|
+
}
|
|
2859
|
+
for (const command of commands) {
|
|
2860
|
+
// console.log(CommandKey[command.key], JSON.stringify(command));
|
|
2861
|
+
switch (command.key) {
|
|
2862
|
+
case CommandKey.Reset:
|
|
2863
|
+
// not sure how well this fits in with the command queue. it
|
|
2864
|
+
// doesn't look like it sends any events, so what's the point?
|
|
2865
|
+
// just to get a command log event?
|
|
2866
|
+
// the problem is that load doesn't run through the queue, so
|
|
2867
|
+
// even if you did a reset -> load we'd just get the reset part.
|
|
2868
|
+
// ...
|
|
2869
|
+
// OK, actually this is used in the CSV import routine. we need
|
|
2870
|
+
// to support it until we get rid of that (it needs to move).
|
|
2871
|
+
this.ResetInternal();
|
|
2872
|
+
break;
|
|
2873
|
+
case CommandKey.Clear:
|
|
2874
|
+
if (command.area) {
|
|
2875
|
+
const area = new Area(command.area.start, command.area.end);
|
|
2876
|
+
this.ClearAreaInternal(area);
|
|
2877
|
+
flags.data_area = Area.Join(area, flags.data_area);
|
|
2878
|
+
flags.formula = true;
|
|
2879
|
+
}
|
|
2880
|
+
break;
|
|
2881
|
+
case CommandKey.Select:
|
|
2882
|
+
// nobody (except one routine) is using commands for selection.
|
|
2883
|
+
// not sure why or why not, or if that's a problem. (it's definitely
|
|
2884
|
+
// a problem if we are recording the log for playback)
|
|
2885
|
+
// ATM the base class is just going to do nothing.
|
|
2886
|
+
this.SelectInternal(command);
|
|
2887
|
+
break;
|
|
2888
|
+
case CommandKey.Freeze:
|
|
2889
|
+
// COEDITING: ok
|
|
2890
|
+
this.FreezeInternal(command);
|
|
2891
|
+
// is the event necessary here? not sure. we were sending it as a
|
|
2892
|
+
// side effect, so it was added here in case there was some reason
|
|
2893
|
+
// it was necessary. at a minimum, it should not require a rebuild
|
|
2894
|
+
// because no addresses change. (although we leave it in case someone
|
|
2895
|
+
// else sets it).)
|
|
2896
|
+
flags.structure_event = true;
|
|
2897
|
+
break;
|
|
2898
|
+
case CommandKey.AddConditionalFormat:
|
|
2899
|
+
{
|
|
2900
|
+
const sheet = this.FindSheet(command.format.area);
|
|
2901
|
+
sheet.conditional_formats.push(command.format);
|
|
2902
|
+
sheet.Invalidate(new Area(command.format.area.start, command.format.area.end));
|
|
2903
|
+
if (sheet === this.active_sheet) {
|
|
2904
|
+
// flags.style_area = Area.Join(command.format.area, flags.style_area);
|
|
2905
|
+
flags.render_area = Area.Join(command.format.area, flags.render_area);
|
|
2906
|
+
}
|
|
2907
|
+
else {
|
|
2908
|
+
// flags.style_event = true;
|
|
2909
|
+
}
|
|
2910
|
+
flags.structure_event = true;
|
|
2911
|
+
flags.conditional_formatting_event = true;
|
|
2912
|
+
}
|
|
2913
|
+
break;
|
|
2914
|
+
case CommandKey.RemoveConditionalFormat:
|
|
2915
|
+
{
|
|
2916
|
+
let sheet;
|
|
2917
|
+
let count = 0;
|
|
2918
|
+
if (command.format) {
|
|
2919
|
+
// we're removing by object equivalence, not strict equality.
|
|
2920
|
+
// this is in case we're switching contexts and the objects
|
|
2921
|
+
// are not strictly equal. may be unecessary. do we need to
|
|
2922
|
+
// normalize in some way? (...)
|
|
2923
|
+
const format = JSON.stringify(command.format);
|
|
2924
|
+
sheet = this.FindSheet(command.format.area);
|
|
2925
|
+
sheet.conditional_formats = sheet.conditional_formats.filter(test => {
|
|
2926
|
+
// if (test === command.format) {
|
|
2927
|
+
if (JSON.stringify(test) === format) {
|
|
2928
|
+
count++;
|
|
2929
|
+
flags.render_area = Area.Join(test.area, flags.render_area);
|
|
2930
|
+
return false;
|
|
2931
|
+
}
|
|
2932
|
+
return true;
|
|
2933
|
+
});
|
|
2934
|
+
}
|
|
2935
|
+
else if (command.area) {
|
|
2936
|
+
const area = new Area(command.area.start, command.area.end);
|
|
2937
|
+
sheet = this.FindSheet(command.area);
|
|
2938
|
+
sheet.conditional_formats = sheet.conditional_formats.filter(test => {
|
|
2939
|
+
const compare = new Area(test.area.start, test.area.end);
|
|
2940
|
+
if (compare.Intersects(area)) {
|
|
2941
|
+
count++;
|
|
2942
|
+
flags.render_area = Area.Join(compare, flags.render_area);
|
|
2943
|
+
return false;
|
|
2944
|
+
}
|
|
2945
|
+
return true;
|
|
2946
|
+
});
|
|
2947
|
+
}
|
|
2948
|
+
if (sheet && count) {
|
|
2949
|
+
sheet.FlushConditionalFormats();
|
|
2950
|
+
flags.structure_event = true;
|
|
2951
|
+
// this will flush leaf vertices. but it's expensive because
|
|
2952
|
+
// it's rebuilding the whole graph. we could maybe reduce a
|
|
2953
|
+
// bit... the question is what's worse: rebuilding the graph
|
|
2954
|
+
// or leaving orphans for a while?
|
|
2955
|
+
// flags.structure_rebuild_required = true;
|
|
2956
|
+
flags.conditional_formatting_event = true;
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2959
|
+
break;
|
|
2960
|
+
case CommandKey.InsertTable:
|
|
2961
|
+
// the most important thing here is validating that we can
|
|
2962
|
+
// create the table in the target area.
|
|
2963
|
+
{
|
|
2964
|
+
const sheet = this.FindSheet(command.area);
|
|
2965
|
+
const area = new Area(command.area.start, command.area.end);
|
|
2966
|
+
// validate first
|
|
2967
|
+
let valid = true;
|
|
2968
|
+
validation_loop: for (let row = area.start.row; row <= area.end.row; row++) {
|
|
2969
|
+
for (let column = area.start.column; column <= area.end.column; column++) {
|
|
2970
|
+
const cell = sheet.cells.GetCell({ row, column }, false);
|
|
2971
|
+
if (cell && (cell.area || cell.merge_area || cell.table)) {
|
|
2972
|
+
valid = false;
|
|
2973
|
+
break validation_loop;
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
}
|
|
2977
|
+
if (valid) {
|
|
2978
|
+
// we need a name for the table. needs to be unique.
|
|
2979
|
+
let index = this.model.tables.size + 1;
|
|
2980
|
+
let name = '';
|
|
2981
|
+
for (;;) {
|
|
2982
|
+
name = `Table${index++}`;
|
|
2983
|
+
if (!this.model.tables.has(name.toLowerCase())) {
|
|
2984
|
+
break;
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
const table = {
|
|
2988
|
+
area: command.area,
|
|
2989
|
+
name,
|
|
2990
|
+
sortable: command.sortable, // defaults to true if !present
|
|
2991
|
+
theme: command.theme,
|
|
2992
|
+
};
|
|
2993
|
+
if (command.totals) {
|
|
2994
|
+
table.totals_row = true;
|
|
2995
|
+
}
|
|
2996
|
+
this.model.tables.set(name.toLowerCase(), table);
|
|
2997
|
+
for (let row = area.start.row; row <= area.end.row; row++) {
|
|
2998
|
+
for (let column = area.start.column; column <= area.end.column; column++) {
|
|
2999
|
+
const cell = sheet.cells.GetCell({ row, column }, true);
|
|
3000
|
+
cell.table = table;
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
this.UpdateTableColumns(table);
|
|
3004
|
+
// force rerendering, we don't need to flush the values
|
|
3005
|
+
sheet.Invalidate(new Area(table.area.start, table.area.end));
|
|
3006
|
+
if (sheet === this.active_sheet) {
|
|
3007
|
+
flags.style_area = Area.Join(area, flags.style_area);
|
|
3008
|
+
flags.render_area = Area.Join(area, flags.render_area);
|
|
3009
|
+
}
|
|
3010
|
+
else {
|
|
3011
|
+
flags.style_event = true;
|
|
3012
|
+
}
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
break;
|
|
3016
|
+
case CommandKey.RemoveTable:
|
|
3017
|
+
// this is pretty easy, we can do it inline
|
|
3018
|
+
{
|
|
3019
|
+
const sheet = this.FindSheet(command.table.area);
|
|
3020
|
+
const area = new Area(command.table.area.start, command.table.area.end);
|
|
3021
|
+
for (let row = area.start.row; row <= area.end.row; row++) {
|
|
3022
|
+
for (let column = area.start.column; column <= area.end.column; column++) {
|
|
3023
|
+
const cell = sheet.cells.GetCell({ row, column }, false);
|
|
3024
|
+
if (cell) {
|
|
3025
|
+
cell.table = undefined;
|
|
3026
|
+
}
|
|
3027
|
+
}
|
|
3028
|
+
}
|
|
3029
|
+
// drop from model
|
|
3030
|
+
// console.info('deleting...', command.table.name);
|
|
3031
|
+
this.model.tables.delete(command.table.name.toLowerCase());
|
|
3032
|
+
// tables use nonstandard styling, we need to invalidate the sheet.
|
|
3033
|
+
// for edges invalidate an extra cell around the table
|
|
3034
|
+
const invalid = sheet.RealArea(area.Clone().Shift(-1, -1).Resize(area.rows + 2, area.columns + 2));
|
|
3035
|
+
sheet.Invalidate(invalid);
|
|
3036
|
+
if (sheet === this.active_sheet) {
|
|
3037
|
+
flags.style_area = Area.Join(area, flags.style_area);
|
|
3038
|
+
flags.render_area = Area.Join(area, flags.render_area);
|
|
3039
|
+
}
|
|
3040
|
+
else {
|
|
3041
|
+
flags.style_event = true;
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
3044
|
+
break;
|
|
3045
|
+
case CommandKey.MergeCells:
|
|
3046
|
+
{
|
|
3047
|
+
// COEDITING: ok
|
|
3048
|
+
const sheet = this.FindSheet(command.area);
|
|
3049
|
+
sheet.MergeCells(new Area(command.area.start, command.area.end));
|
|
3050
|
+
// sheet publishes a data event here, too. probably a good
|
|
3051
|
+
// idea because references to the secondary (non-head) merge
|
|
3052
|
+
// cells will break.
|
|
3053
|
+
flags.structure_event = true;
|
|
3054
|
+
flags.structure_rebuild_required = true;
|
|
3055
|
+
if (sheet === this.active_sheet) {
|
|
3056
|
+
flags.data_area = Area.Join(command.area, flags.data_area);
|
|
3057
|
+
flags.render_area = Area.Join(command.area, flags.render_area);
|
|
3058
|
+
}
|
|
3059
|
+
else {
|
|
3060
|
+
flags.data_event = true;
|
|
3061
|
+
// this.pending_layout_update.add(sheet.id);
|
|
3062
|
+
if (!flags.pending) {
|
|
3063
|
+
flags.pending = [];
|
|
3064
|
+
}
|
|
3065
|
+
flags.pending.push(sheet.id);
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
break;
|
|
3069
|
+
case CommandKey.UnmergeCells:
|
|
3070
|
+
{
|
|
3071
|
+
// COEDITING: ok
|
|
3072
|
+
// the sheet unmerge routine requires a single, contiguous merge area.
|
|
3073
|
+
// we want to support multiple unmerges at the same time, though,
|
|
3074
|
+
// so let's check for multiple. create a list.
|
|
3075
|
+
// FIXME: use a set
|
|
3076
|
+
const sheet = this.FindSheet(command.area);
|
|
3077
|
+
const list = {};
|
|
3078
|
+
const area = new Area(command.area.start, command.area.end);
|
|
3079
|
+
for (const cell of sheet.cells.Iterate(area, false)) {
|
|
3080
|
+
if (cell.merge_area) {
|
|
3081
|
+
const label = Area.CellAddressToLabel(cell.merge_area.start) + ':'
|
|
3082
|
+
+ Area.CellAddressToLabel(cell.merge_area.end);
|
|
3083
|
+
list[label] = cell.merge_area;
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3086
|
+
/*
|
|
3087
|
+
sheet.cells.Apply(area, (cell: Cell) => {
|
|
3088
|
+
if (cell.merge_area) {
|
|
3089
|
+
const label = Area.CellAddressToLabel(cell.merge_area.start) + ':'
|
|
3090
|
+
+ Area.CellAddressToLabel(cell.merge_area.end);
|
|
3091
|
+
list[label] = cell.merge_area;
|
|
3092
|
+
}
|
|
3093
|
+
}, false);
|
|
3094
|
+
*/
|
|
3095
|
+
const keys = Object.keys(list);
|
|
3096
|
+
for (let i = 0; i < keys.length; i++) {
|
|
3097
|
+
sheet.UnmergeCells(list[keys[i]]);
|
|
3098
|
+
}
|
|
3099
|
+
// see above
|
|
3100
|
+
if (sheet === this.active_sheet) {
|
|
3101
|
+
flags.render_area = Area.Join(command.area, flags.render_area);
|
|
3102
|
+
flags.data_area = Area.Join(command.area, flags.data_area);
|
|
3103
|
+
}
|
|
3104
|
+
else {
|
|
3105
|
+
flags.data_event = true;
|
|
3106
|
+
// this.pending_layout_update.add(sheet.id);
|
|
3107
|
+
if (!flags.pending) {
|
|
3108
|
+
flags.pending = [];
|
|
3109
|
+
}
|
|
3110
|
+
flags.pending.push(sheet.id);
|
|
3111
|
+
}
|
|
3112
|
+
flags.structure_event = true;
|
|
3113
|
+
flags.structure_rebuild_required = true;
|
|
3114
|
+
}
|
|
3115
|
+
break;
|
|
3116
|
+
case CommandKey.Indent:
|
|
3117
|
+
{
|
|
3118
|
+
let area;
|
|
3119
|
+
const sheet = this.FindSheet(command.area);
|
|
3120
|
+
if (IsCellAddress(command.area)) {
|
|
3121
|
+
area = new Area(command.area);
|
|
3122
|
+
const style = sheet.GetCellStyle(command.area, true);
|
|
3123
|
+
sheet.UpdateCellStyle(command.area, {
|
|
3124
|
+
indent: Math.max(0, (style.indent || 0) + command.delta),
|
|
3125
|
+
}, true);
|
|
3126
|
+
}
|
|
3127
|
+
else {
|
|
3128
|
+
area = new Area(command.area.start, command.area.end);
|
|
3129
|
+
for (const address of area) {
|
|
3130
|
+
const style = sheet.GetCellStyle(address, true);
|
|
3131
|
+
sheet.UpdateCellStyle(address, {
|
|
3132
|
+
indent: Math.max(0, (style.indent || 0) + command.delta),
|
|
3133
|
+
}, true);
|
|
3134
|
+
}
|
|
3135
|
+
}
|
|
3136
|
+
if (sheet === this.active_sheet) {
|
|
3137
|
+
flags.style_area = Area.Join(area, flags.style_area);
|
|
3138
|
+
flags.render_area = Area.Join(area, flags.render_area);
|
|
3139
|
+
}
|
|
3140
|
+
else {
|
|
3141
|
+
flags.style_event = true;
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
3144
|
+
break;
|
|
3145
|
+
case CommandKey.UpdateStyle:
|
|
3146
|
+
{
|
|
3147
|
+
// COEDITING: handles sheet ID properly
|
|
3148
|
+
// to account for our background bleeding up/left, when applying
|
|
3149
|
+
// style changes we may need to render one additional row/column.
|
|
3150
|
+
let area;
|
|
3151
|
+
const sheet = this.FindSheet(command.area);
|
|
3152
|
+
if (IsCellAddress(command.area)) {
|
|
3153
|
+
area = new Area(command.area);
|
|
3154
|
+
sheet.UpdateCellStyle(command.area, command.style, !!command.delta);
|
|
3155
|
+
}
|
|
3156
|
+
else {
|
|
3157
|
+
area = new Area(command.area.start, command.area.end);
|
|
3158
|
+
sheet.UpdateAreaStyle(area, command.style, !!command.delta);
|
|
3159
|
+
}
|
|
3160
|
+
if (sheet === this.active_sheet) {
|
|
3161
|
+
flags.style_area = Area.Join(area, flags.style_area);
|
|
3162
|
+
// we can limit bleed handling to cases where it's necessary...
|
|
3163
|
+
// if we really wanted to optimize we could call invalidate on .left, .top, &c
|
|
3164
|
+
if (!command.delta
|
|
3165
|
+
|| command.style.fill
|
|
3166
|
+
|| command.style.border_top
|
|
3167
|
+
|| command.style.border_left
|
|
3168
|
+
|| command.style.border_right
|
|
3169
|
+
|| command.style.border_bottom) {
|
|
3170
|
+
area = Area.Bleed(area); // bleed by 1 to account for borders/background
|
|
3171
|
+
this.active_sheet.Invalidate(area);
|
|
3172
|
+
}
|
|
3173
|
+
flags.render_area = Area.Join(area, flags.render_area);
|
|
3174
|
+
}
|
|
3175
|
+
else {
|
|
3176
|
+
flags.style_event = true;
|
|
3177
|
+
}
|
|
3178
|
+
}
|
|
3179
|
+
break;
|
|
3180
|
+
case CommandKey.DataValidation:
|
|
3181
|
+
// COEDITING: ok
|
|
3182
|
+
this.SetValidationInternal(command);
|
|
3183
|
+
if (!command.area.start.sheet_id || command.area.start.sheet_id === this.active_sheet.id) {
|
|
3184
|
+
flags.render_area = Area.Join(new Area(command.area.start, command.area.end), flags.render_area);
|
|
3185
|
+
}
|
|
3186
|
+
break;
|
|
3187
|
+
case CommandKey.SetName:
|
|
3188
|
+
// it seems like we're allowing overwriting names if those
|
|
3189
|
+
// names exist as expressions or named ranges. however we
|
|
3190
|
+
// should not allow overriding a built-in function name (or
|
|
3191
|
+
// a macro function name?)
|
|
3192
|
+
// FOR THE TIME BEING we're going to add that restriction to
|
|
3193
|
+
// the calling function, which (atm) is the only way to get here.
|
|
3194
|
+
{
|
|
3195
|
+
const ac_token = { type: 'token', named: true, name: command.name, scope: command.scope };
|
|
3196
|
+
if (command.area || command.expression) {
|
|
3197
|
+
if (command.area) {
|
|
3198
|
+
this.model.named.SetNamedRange(command.name, new Area(command.area.start, command.area.end), command.scope);
|
|
3199
|
+
}
|
|
3200
|
+
else if (command.expression) {
|
|
3201
|
+
this.model.named.SetNamedExpression(command.name, command.expression, command.scope);
|
|
3202
|
+
}
|
|
3203
|
+
this.autocomplete_matcher.AddFunctions(ac_token);
|
|
3204
|
+
}
|
|
3205
|
+
else {
|
|
3206
|
+
this.model.named.ClearName(command.name, command.scope);
|
|
3207
|
+
this.autocomplete_matcher.RemoveFunctions(ac_token);
|
|
3208
|
+
}
|
|
3209
|
+
flags.structure_event = true;
|
|
3210
|
+
flags.structure_rebuild_required = true;
|
|
3211
|
+
}
|
|
3212
|
+
break;
|
|
3213
|
+
case CommandKey.UpdateBorders:
|
|
3214
|
+
{
|
|
3215
|
+
// COEDITING: ok
|
|
3216
|
+
// UPDATE: actually had a problem with Area.Bleed dropping the
|
|
3217
|
+
// sheet ID. fixed.
|
|
3218
|
+
const area = this.ApplyBordersInternal(command);
|
|
3219
|
+
if (area.start.sheet_id === this.active_sheet.id) {
|
|
3220
|
+
flags.render_area = Area.Join(area, flags.render_area);
|
|
3221
|
+
flags.style_area = Area.Join(area, flags.style_area);
|
|
3222
|
+
}
|
|
3223
|
+
else {
|
|
3224
|
+
flags.style_event = true;
|
|
3225
|
+
}
|
|
3226
|
+
}
|
|
3227
|
+
break;
|
|
3228
|
+
case CommandKey.ShowSheet:
|
|
3229
|
+
// COEDITING: we probably don't want this to pass through
|
|
3230
|
+
// when coediting, but it won't break anything. you can filter.
|
|
3231
|
+
this.ShowSheetInternal(command);
|
|
3232
|
+
flags.sheets = true; // repaint tab bar
|
|
3233
|
+
flags.structure_event = true;
|
|
3234
|
+
break;
|
|
3235
|
+
case CommandKey.TabColor:
|
|
3236
|
+
command.sheet.tab_color = command.color;
|
|
3237
|
+
// NOTE: the flag.sheets originally did not update tab colors,
|
|
3238
|
+
// which were cached. we could create a new flag for that, or
|
|
3239
|
+
// we could just refresh the colors. since there aren't that
|
|
3240
|
+
// many of them, it's probably OK to just refresh the colors any
|
|
3241
|
+
// time this flag is set. if that becomes a perf issue in the
|
|
3242
|
+
// future we could add a new flag.
|
|
3243
|
+
flags.sheets = true; // repaint tab bar
|
|
3244
|
+
flags.structure_event = true;
|
|
3245
|
+
break;
|
|
3246
|
+
case CommandKey.ReorderSheet:
|
|
3247
|
+
{
|
|
3248
|
+
// COEDITING: seems OK, irrespective of active sheet
|
|
3249
|
+
const sheets = [];
|
|
3250
|
+
const target = this.model.sheets.list[command.index];
|
|
3251
|
+
for (let i = 0; i < this.model.sheets.length; i++) {
|
|
3252
|
+
if (i !== command.index) {
|
|
3253
|
+
if (i === command.move_before) {
|
|
3254
|
+
sheets.push(target);
|
|
3255
|
+
}
|
|
3256
|
+
sheets.push(this.model.sheets.list[i]);
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
if (command.move_before >= this.model.sheets.length) {
|
|
3260
|
+
sheets.push(target);
|
|
3261
|
+
}
|
|
3262
|
+
// this.model.sheets = sheets;
|
|
3263
|
+
this.model.sheets.Assign(sheets);
|
|
3264
|
+
flags.sheets = true;
|
|
3265
|
+
flags.structure_event = true;
|
|
3266
|
+
}
|
|
3267
|
+
break;
|
|
3268
|
+
case CommandKey.RenameSheet:
|
|
3269
|
+
{
|
|
3270
|
+
// COEDITING: seems OK, irrespective of active sheet
|
|
3271
|
+
const sheet = this.ResolveSheet(command);
|
|
3272
|
+
if (sheet) {
|
|
3273
|
+
this.RenameSheetInternal(sheet, command.new_name);
|
|
3274
|
+
flags.sheets = true;
|
|
3275
|
+
flags.structure_event = true;
|
|
3276
|
+
}
|
|
3277
|
+
}
|
|
3278
|
+
break;
|
|
3279
|
+
case CommandKey.RemoveAnnotation:
|
|
3280
|
+
this.RemoveAnnotationInternal(command);
|
|
3281
|
+
flags.structure_event = true;
|
|
3282
|
+
flags.structure_rebuild_required = true;
|
|
3283
|
+
flags.annotation_event = true;
|
|
3284
|
+
break;
|
|
3285
|
+
case CommandKey.CreateAnnotation:
|
|
3286
|
+
this.CreateAnnotationInternal(command);
|
|
3287
|
+
flags.structure_event = true;
|
|
3288
|
+
flags.structure_rebuild_required = true;
|
|
3289
|
+
flags.annotation_event = true;
|
|
3290
|
+
break;
|
|
3291
|
+
case CommandKey.ResizeRows:
|
|
3292
|
+
// moving this to a method so we can specialize: non-UI grid
|
|
3293
|
+
// should not support autosize (it can't)
|
|
3294
|
+
// this may impact the SUBTOTAL function. which is dumb, but
|
|
3295
|
+
// there you go. so treat this as a data event for rows that
|
|
3296
|
+
// change visibility one way or the other.
|
|
3297
|
+
// COEDITING: ok
|
|
3298
|
+
{
|
|
3299
|
+
const area = this.ResizeRowsInternal(command);
|
|
3300
|
+
if (area) {
|
|
3301
|
+
if (area.start.sheet_id === this.active_sheet.id) {
|
|
3302
|
+
const real_area = this.active_sheet.RealArea(new Area(area.start, area.end));
|
|
3303
|
+
flags.render_area = Area.Join(real_area, flags.render_area);
|
|
3304
|
+
flags.data_area = Area.Join(real_area, flags.data_area);
|
|
3305
|
+
flags.data_event = true;
|
|
3306
|
+
}
|
|
3307
|
+
else {
|
|
3308
|
+
flags.data_event = true;
|
|
3309
|
+
if (!flags.pending) {
|
|
3310
|
+
flags.pending = [];
|
|
3311
|
+
}
|
|
3312
|
+
if (area.start.sheet_id) {
|
|
3313
|
+
flags.pending.push(area.start.sheet_id);
|
|
3314
|
+
}
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
3317
|
+
flags.structure_event = true;
|
|
3318
|
+
}
|
|
3319
|
+
break;
|
|
3320
|
+
case CommandKey.ResizeColumns:
|
|
3321
|
+
this.ResizeColumnsInternal(command);
|
|
3322
|
+
flags.structure_event = true;
|
|
3323
|
+
break;
|
|
3324
|
+
case CommandKey.ShowHeaders:
|
|
3325
|
+
// FIXME: now that we don't support 2-level headers (or anything
|
|
3326
|
+
// other than 1-level headers), headers should be managed by/move into
|
|
3327
|
+
// the grid class.
|
|
3328
|
+
this.active_sheet.SetHeaderSize(command.show ? undefined : 1, command.show ? undefined : 1);
|
|
3329
|
+
this.flags.layout = true;
|
|
3330
|
+
this.flags.repaint = true;
|
|
3331
|
+
break;
|
|
3332
|
+
case CommandKey.InsertRows:
|
|
3333
|
+
// COEDITING: annotations are broken
|
|
3334
|
+
this.InsertRowsInternal(command);
|
|
3335
|
+
flags.structure_event = true;
|
|
3336
|
+
flags.structure_rebuild_required = true;
|
|
3337
|
+
break;
|
|
3338
|
+
case CommandKey.InsertColumns:
|
|
3339
|
+
// COEDITING: annotations are broken
|
|
3340
|
+
this.InsertColumnsInternal(command);
|
|
3341
|
+
flags.structure_event = true;
|
|
3342
|
+
flags.structure_rebuild_required = true;
|
|
3343
|
+
break;
|
|
3344
|
+
case CommandKey.SetLink:
|
|
3345
|
+
case CommandKey.SetNote:
|
|
3346
|
+
{
|
|
3347
|
+
// COEDITING: ok
|
|
3348
|
+
// note and link are basically the same, although there's a
|
|
3349
|
+
// method for setting note (not sure why)
|
|
3350
|
+
const sheet = this.FindSheet(command.area);
|
|
3351
|
+
let cell = sheet.cells.GetCell(command.area, true);
|
|
3352
|
+
if (cell) {
|
|
3353
|
+
let area;
|
|
3354
|
+
if (cell.merge_area) {
|
|
3355
|
+
area = new Area(cell.merge_area.start);
|
|
3356
|
+
cell = sheet.cells.GetCell(cell.merge_area.start, true);
|
|
3357
|
+
}
|
|
3358
|
+
else {
|
|
3359
|
+
area = new Area(command.area);
|
|
3360
|
+
}
|
|
3361
|
+
if (command.key === CommandKey.SetNote) {
|
|
3362
|
+
cell.SetNote(command.note);
|
|
3363
|
+
}
|
|
3364
|
+
else {
|
|
3365
|
+
cell.hyperlink = command.reference || undefined;
|
|
3366
|
+
cell.render_clean = [];
|
|
3367
|
+
}
|
|
3368
|
+
if (sheet === this.active_sheet) {
|
|
3369
|
+
// this isn't necessary because it's what the render area does
|
|
3370
|
+
// this.DelayedRender(false, area);
|
|
3371
|
+
// treat this as style, because it affects painting but
|
|
3372
|
+
// does not require calculation.
|
|
3373
|
+
flags.style_area = Area.Join(area, flags.style_area);
|
|
3374
|
+
flags.render_area = Area.Join(area, flags.render_area);
|
|
3375
|
+
}
|
|
3376
|
+
else {
|
|
3377
|
+
flags.style_event = true;
|
|
3378
|
+
}
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
break;
|
|
3382
|
+
case CommandKey.SortTable:
|
|
3383
|
+
{
|
|
3384
|
+
// console.info(command.table.area.spreadsheet_label);
|
|
3385
|
+
const area = this.SortTableInternal(command);
|
|
3386
|
+
if (area && area.start.sheet_id === this.active_sheet.id) {
|
|
3387
|
+
flags.data_area = Area.Join(area, flags.data_area);
|
|
3388
|
+
// normally we don't paint, we wait for the calculator to resolve
|
|
3389
|
+
if (this.options.repaint_on_cell_change) {
|
|
3390
|
+
flags.render_area = Area.Join(area, flags.render_area);
|
|
3391
|
+
}
|
|
3392
|
+
}
|
|
3393
|
+
else {
|
|
3394
|
+
flags.data_event = true;
|
|
3395
|
+
}
|
|
3396
|
+
}
|
|
3397
|
+
break;
|
|
3398
|
+
case CommandKey.SetRange:
|
|
3399
|
+
{
|
|
3400
|
+
// COEDITING: handles sheet ID properly
|
|
3401
|
+
// FIXME: areas should check sheet
|
|
3402
|
+
// area could be undefined if there's an error
|
|
3403
|
+
// (try to change part of an array)
|
|
3404
|
+
const area = this.SetRangeInternal(command, flags);
|
|
3405
|
+
if (area) {
|
|
3406
|
+
const sheet = this.model.sheets.Find(area.start.sheet_id || this.active_sheet.id);
|
|
3407
|
+
const tables = sheet?.TablesFromArea(area, true) || [];
|
|
3408
|
+
for (const table of tables) {
|
|
3409
|
+
this.UpdateTableColumns(table);
|
|
3410
|
+
}
|
|
3411
|
+
}
|
|
3412
|
+
if (area && area.start.sheet_id === this.active_sheet.id) {
|
|
3413
|
+
flags.data_area = Area.Join(area, flags.data_area);
|
|
3414
|
+
// normally we don't paint, we wait for the calculator to resolve
|
|
3415
|
+
if (this.options.repaint_on_cell_change) {
|
|
3416
|
+
flags.render_area = Area.Join(area, flags.render_area);
|
|
3417
|
+
}
|
|
3418
|
+
}
|
|
3419
|
+
else {
|
|
3420
|
+
flags.data_event = true;
|
|
3421
|
+
}
|
|
3422
|
+
}
|
|
3423
|
+
break;
|
|
3424
|
+
case CommandKey.DeleteSheet:
|
|
3425
|
+
// COEDITING: looks fine
|
|
3426
|
+
this.DeleteSheetInternal(command);
|
|
3427
|
+
flags.sheets = true;
|
|
3428
|
+
flags.structure_event = true;
|
|
3429
|
+
flags.structure_rebuild_required = true;
|
|
3430
|
+
break;
|
|
3431
|
+
case CommandKey.DuplicateSheet:
|
|
3432
|
+
// FIXME: what happens to named ranges? we don't have sheet-local names...
|
|
3433
|
+
this.DuplicateSheetInternal(command);
|
|
3434
|
+
flags.sheets = true;
|
|
3435
|
+
flags.structure_event = true;
|
|
3436
|
+
flags.structure_rebuild_required = true;
|
|
3437
|
+
break;
|
|
3438
|
+
case CommandKey.AddSheet:
|
|
3439
|
+
// COEDITING: this won't break, but it shouldn't change the
|
|
3440
|
+
// active sheet if this is a remote command. is there a way
|
|
3441
|
+
// to know? we can guess implicitly from the queue parameter,
|
|
3442
|
+
// but it would be better to be explicit.
|
|
3443
|
+
{
|
|
3444
|
+
const id = this.AddSheetInternal(command.name, command.insert_index); // default name
|
|
3445
|
+
if (typeof id === 'number' && command.show) {
|
|
3446
|
+
this.ActivateSheetInternal({
|
|
3447
|
+
key: CommandKey.ActivateSheet,
|
|
3448
|
+
id,
|
|
3449
|
+
});
|
|
3450
|
+
}
|
|
3451
|
+
flags.structure_event = true;
|
|
3452
|
+
flags.sheets = true;
|
|
3453
|
+
flags.structure = true;
|
|
3454
|
+
}
|
|
3455
|
+
break;
|
|
3456
|
+
case CommandKey.ActivateSheet:
|
|
3457
|
+
this.ActivateSheetInternal(command);
|
|
3458
|
+
break;
|
|
3459
|
+
default:
|
|
3460
|
+
console.warn(`unhandled command: ${CommandKey[command.key]} (${command.key})`);
|
|
3461
|
+
}
|
|
3462
|
+
}
|
|
3463
|
+
// consolidate events and merge areas
|
|
3464
|
+
let data_event;
|
|
3465
|
+
let style_event;
|
|
3466
|
+
if (flags.data_area) {
|
|
3467
|
+
if (!flags.data_area.start.sheet_id) {
|
|
3468
|
+
flags.data_area.SetSheetID(this.active_sheet.id);
|
|
3469
|
+
}
|
|
3470
|
+
// events.push({ type: 'data', area: flags.data_area });
|
|
3471
|
+
data_event = { type: 'data', area: flags.data_area };
|
|
3472
|
+
}
|
|
3473
|
+
else if (flags.data_event) {
|
|
3474
|
+
// events.push({ type: 'data' });
|
|
3475
|
+
data_event = { type: 'data' };
|
|
3476
|
+
}
|
|
3477
|
+
if (flags.style_area) {
|
|
3478
|
+
if (!flags.style_area.start.sheet_id) {
|
|
3479
|
+
flags.style_area.SetSheetID(this.active_sheet.id);
|
|
3480
|
+
}
|
|
3481
|
+
// events.push({ type: 'style', area: flags.style_area });
|
|
3482
|
+
style_event = { type: 'style', area: flags.style_area };
|
|
3483
|
+
}
|
|
3484
|
+
else if (flags.style_event) {
|
|
3485
|
+
// events.push({ type: 'style' });
|
|
3486
|
+
style_event = { type: 'style' };
|
|
3487
|
+
}
|
|
3488
|
+
if (data_event && style_event) {
|
|
3489
|
+
events.push({
|
|
3490
|
+
type: 'composite',
|
|
3491
|
+
data_area: data_event.area,
|
|
3492
|
+
style_area: style_event.area,
|
|
3493
|
+
});
|
|
3494
|
+
}
|
|
3495
|
+
else {
|
|
3496
|
+
if (data_event) {
|
|
3497
|
+
events.push(data_event);
|
|
3498
|
+
}
|
|
3499
|
+
if (style_event) {
|
|
3500
|
+
events.push(style_event);
|
|
3501
|
+
}
|
|
3502
|
+
}
|
|
3503
|
+
if (flags.structure_event) {
|
|
3504
|
+
events.push({
|
|
3505
|
+
type: 'structure',
|
|
3506
|
+
rebuild_required: flags.structure_rebuild_required,
|
|
3507
|
+
conditional_format: flags.conditional_formatting_event,
|
|
3508
|
+
update_annotations: flags.annotation_event,
|
|
3509
|
+
});
|
|
3510
|
+
}
|
|
3511
|
+
if (this.batch > 0) {
|
|
3512
|
+
this.batch_events.push(...events);
|
|
3513
|
+
}
|
|
3514
|
+
else {
|
|
3515
|
+
this.grid_events.Publish(events);
|
|
3516
|
+
//if (flags.render_area) {
|
|
3517
|
+
// this.DelayedRender(false, flags.render_area);
|
|
3518
|
+
//}
|
|
3519
|
+
}
|
|
3520
|
+
return flags;
|
|
3521
|
+
}
|
|
3522
|
+
}
|
|
3523
|
+
//# sourceMappingURL=grid_base.js.map
|