@trebco/treb 23.6.5 → 25.0.0-rc1
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/.eslintignore +8 -0
- package/.eslintrc.js +164 -0
- package/README-shadow-DOM.md +88 -0
- package/README.md +37 -130
- package/api-config.json +29 -0
- package/api-generator/api-generator-types.ts +82 -0
- package/api-generator/api-generator.ts +1172 -0
- package/api-generator/package.json +3 -0
- package/build/treb-spreadsheet.mjs +14 -0
- package/{treb.d.ts → build/treb.d.ts} +285 -269
- package/esbuild-custom-element.mjs +336 -0
- package/esbuild.js +305 -0
- package/package.json +43 -14
- package/treb-base-types/package.json +5 -0
- package/treb-base-types/src/api_types.ts +36 -0
- package/treb-base-types/src/area.ts +583 -0
- package/treb-base-types/src/basic_types.ts +45 -0
- package/treb-base-types/src/cell.ts +612 -0
- package/treb-base-types/src/cells.ts +1066 -0
- package/treb-base-types/src/color.ts +124 -0
- package/treb-base-types/src/import.ts +71 -0
- package/treb-base-types/src/index-standalone.ts +29 -0
- package/treb-base-types/src/index.ts +42 -0
- package/treb-base-types/src/layout.ts +47 -0
- package/treb-base-types/src/localization.ts +187 -0
- package/treb-base-types/src/rectangle.ts +145 -0
- package/treb-base-types/src/render_text.ts +72 -0
- package/treb-base-types/src/style.ts +545 -0
- package/treb-base-types/src/table.ts +109 -0
- package/treb-base-types/src/text_part.ts +54 -0
- package/treb-base-types/src/theme.ts +608 -0
- package/treb-base-types/src/union.ts +152 -0
- package/treb-base-types/src/value-type.ts +164 -0
- package/treb-base-types/style/resizable.css +59 -0
- package/treb-calculator/modern.tsconfig.json +11 -0
- package/treb-calculator/package.json +5 -0
- package/treb-calculator/src/calculator.ts +2546 -0
- package/treb-calculator/src/complex-math.ts +558 -0
- package/treb-calculator/src/dag/array-vertex.ts +198 -0
- package/treb-calculator/src/dag/graph.ts +951 -0
- package/treb-calculator/src/dag/leaf_vertex.ts +118 -0
- package/treb-calculator/src/dag/spreadsheet_vertex.ts +327 -0
- package/treb-calculator/src/dag/spreadsheet_vertex_base.ts +44 -0
- package/treb-calculator/src/dag/vertex.ts +352 -0
- package/treb-calculator/src/descriptors.ts +162 -0
- package/treb-calculator/src/expression-calculator.ts +1069 -0
- package/treb-calculator/src/function-error.ts +103 -0
- package/treb-calculator/src/function-library.ts +103 -0
- package/treb-calculator/src/functions/base-functions.ts +1214 -0
- package/treb-calculator/src/functions/checkbox.ts +164 -0
- package/treb-calculator/src/functions/complex-functions.ts +253 -0
- package/treb-calculator/src/functions/finance-functions.ts +399 -0
- package/treb-calculator/src/functions/information-functions.ts +102 -0
- package/treb-calculator/src/functions/matrix-functions.ts +182 -0
- package/treb-calculator/src/functions/sparkline.ts +335 -0
- package/treb-calculator/src/functions/statistics-functions.ts +350 -0
- package/treb-calculator/src/functions/text-functions.ts +298 -0
- package/treb-calculator/src/index.ts +27 -0
- package/treb-calculator/src/notifier-types.ts +59 -0
- package/treb-calculator/src/primitives.ts +428 -0
- package/treb-calculator/src/utilities.ts +305 -0
- package/treb-charts/package.json +5 -0
- package/treb-charts/src/chart-functions.ts +156 -0
- package/treb-charts/src/chart-types.ts +230 -0
- package/treb-charts/src/chart.ts +1288 -0
- package/treb-charts/src/index.ts +24 -0
- package/treb-charts/src/main.ts +37 -0
- package/treb-charts/src/rectangle.ts +52 -0
- package/treb-charts/src/renderer.ts +1841 -0
- package/treb-charts/src/util.ts +122 -0
- package/treb-charts/style/charts.scss +221 -0
- package/treb-charts/style/old-charts.scss +250 -0
- package/treb-embed/markup/layout.html +137 -0
- package/treb-embed/markup/toolbar.html +175 -0
- package/treb-embed/modern.tsconfig.json +25 -0
- package/treb-embed/src/custom-element/content-types.d.ts +18 -0
- package/treb-embed/src/custom-element/global.d.ts +11 -0
- package/treb-embed/src/custom-element/spreadsheet-constructor.ts +1227 -0
- package/treb-embed/src/custom-element/treb-global.ts +44 -0
- package/treb-embed/src/custom-element/treb-spreadsheet-element.ts +52 -0
- package/treb-embed/src/embedded-spreadsheet.ts +5362 -0
- package/treb-embed/src/index.ts +16 -0
- package/treb-embed/src/language-model.ts +41 -0
- package/treb-embed/src/options.ts +320 -0
- package/treb-embed/src/progress-dialog.ts +228 -0
- package/treb-embed/src/selection-state.ts +16 -0
- package/treb-embed/src/spinner.ts +42 -0
- package/treb-embed/src/toolbar-message.ts +96 -0
- package/treb-embed/src/types.ts +167 -0
- package/treb-embed/style/autocomplete.scss +103 -0
- package/treb-embed/style/dark-theme.scss +114 -0
- package/treb-embed/style/defaults.scss +36 -0
- package/treb-embed/style/dialog.scss +181 -0
- package/treb-embed/style/dropdown-select.scss +101 -0
- package/treb-embed/style/formula-bar.scss +193 -0
- package/treb-embed/style/grid.scss +374 -0
- package/treb-embed/style/layout.scss +424 -0
- package/treb-embed/style/mouse-mask.scss +67 -0
- package/treb-embed/style/note.scss +92 -0
- package/treb-embed/style/overlay-editor.scss +102 -0
- package/treb-embed/style/spinner.scss +92 -0
- package/treb-embed/style/tab-bar.scss +228 -0
- package/treb-embed/style/table.scss +80 -0
- package/treb-embed/style/theme-defaults.scss +444 -0
- package/treb-embed/style/toolbar.scss +416 -0
- package/treb-embed/style/tooltip.scss +68 -0
- package/treb-embed/style/treb-icons.scss +130 -0
- package/treb-embed/style/treb-spreadsheet-element.scss +20 -0
- package/treb-embed/style/z-index.scss +43 -0
- package/treb-export/docs/charts.md +68 -0
- package/treb-export/modern.tsconfig.json +19 -0
- package/treb-export/package.json +4 -0
- package/treb-export/src/address-type.ts +77 -0
- package/treb-export/src/base-template.ts +22 -0
- package/treb-export/src/column-width.ts +85 -0
- package/treb-export/src/drawing2/chart-template-components2.ts +389 -0
- package/treb-export/src/drawing2/chart2.ts +282 -0
- package/treb-export/src/drawing2/column-chart-template2.ts +521 -0
- package/treb-export/src/drawing2/donut-chart-template2.ts +296 -0
- package/treb-export/src/drawing2/drawing2.ts +355 -0
- package/treb-export/src/drawing2/embedded-image.ts +71 -0
- package/treb-export/src/drawing2/scatter-chart-template2.ts +555 -0
- package/treb-export/src/export-worker/export-worker.ts +99 -0
- package/treb-export/src/export-worker/index-modern.ts +22 -0
- package/treb-export/src/export2.ts +2204 -0
- package/treb-export/src/import2.ts +882 -0
- package/treb-export/src/relationship.ts +36 -0
- package/treb-export/src/shared-strings2.ts +128 -0
- package/treb-export/src/template-2.ts +22 -0
- package/treb-export/src/unescape_xml.ts +47 -0
- package/treb-export/src/workbook-sheet2.ts +182 -0
- package/treb-export/src/workbook-style2.ts +1285 -0
- package/treb-export/src/workbook-theme2.ts +88 -0
- package/treb-export/src/workbook2.ts +491 -0
- package/treb-export/src/xml-utils.ts +201 -0
- package/treb-export/template/base/[Content_Types].xml +2 -0
- package/treb-export/template/base/_rels/.rels +2 -0
- package/treb-export/template/base/docProps/app.xml +2 -0
- package/treb-export/template/base/docProps/core.xml +12 -0
- package/treb-export/template/base/xl/_rels/workbook.xml.rels +2 -0
- package/treb-export/template/base/xl/sharedStrings.xml +2 -0
- package/treb-export/template/base/xl/styles.xml +2 -0
- package/treb-export/template/base/xl/theme/theme1.xml +2 -0
- package/treb-export/template/base/xl/workbook.xml +2 -0
- package/treb-export/template/base/xl/worksheets/sheet1.xml +2 -0
- package/treb-export/template/base.xlsx +0 -0
- package/treb-format/package.json +8 -0
- package/treb-format/src/format.test.ts +213 -0
- package/treb-format/src/format.ts +942 -0
- package/treb-format/src/format_cache.ts +199 -0
- package/treb-format/src/format_parser.ts +723 -0
- package/treb-format/src/index.ts +25 -0
- package/treb-format/src/number_format_section.ts +100 -0
- package/treb-format/src/value_parser.ts +337 -0
- package/treb-grid/package.json +5 -0
- package/treb-grid/src/editors/autocomplete.ts +394 -0
- package/treb-grid/src/editors/autocomplete_matcher.ts +260 -0
- package/treb-grid/src/editors/formula_bar.ts +473 -0
- package/treb-grid/src/editors/formula_editor_base.ts +910 -0
- package/treb-grid/src/editors/overlay_editor.ts +511 -0
- package/treb-grid/src/index.ts +37 -0
- package/treb-grid/src/layout/base_layout.ts +2618 -0
- package/treb-grid/src/layout/grid_layout.ts +299 -0
- package/treb-grid/src/layout/rectangle_cache.ts +86 -0
- package/treb-grid/src/render/selection-renderer.ts +414 -0
- package/treb-grid/src/render/svg_header_overlay.ts +93 -0
- package/treb-grid/src/render/svg_selection_block.ts +187 -0
- package/treb-grid/src/render/tile_renderer.ts +2122 -0
- package/treb-grid/src/types/annotation.ts +216 -0
- package/treb-grid/src/types/border_constants.ts +34 -0
- package/treb-grid/src/types/clipboard_data.ts +31 -0
- package/treb-grid/src/types/data_model.ts +334 -0
- package/treb-grid/src/types/drag_mask.ts +81 -0
- package/treb-grid/src/types/grid.ts +7743 -0
- package/treb-grid/src/types/grid_base.ts +3644 -0
- package/treb-grid/src/types/grid_command.ts +470 -0
- package/treb-grid/src/types/grid_events.ts +124 -0
- package/treb-grid/src/types/grid_options.ts +97 -0
- package/treb-grid/src/types/grid_selection.ts +60 -0
- package/treb-grid/src/types/named_range.ts +369 -0
- package/treb-grid/src/types/scale-control.ts +202 -0
- package/treb-grid/src/types/serialize_options.ts +72 -0
- package/treb-grid/src/types/set_range_options.ts +52 -0
- package/treb-grid/src/types/sheet.ts +3099 -0
- package/treb-grid/src/types/sheet_types.ts +95 -0
- package/treb-grid/src/types/tab_bar.ts +464 -0
- package/treb-grid/src/types/tile.ts +59 -0
- package/treb-grid/src/types/update_flags.ts +75 -0
- package/treb-grid/src/util/dom_utilities.ts +44 -0
- package/treb-grid/src/util/fontmetrics2.ts +179 -0
- package/treb-grid/src/util/ua.ts +104 -0
- package/treb-logo.svg +18 -0
- package/treb-parser/package.json +5 -0
- package/treb-parser/src/csv-parser.ts +122 -0
- package/treb-parser/src/index.ts +25 -0
- package/treb-parser/src/md-parser.ts +526 -0
- package/treb-parser/src/parser-types.ts +397 -0
- package/treb-parser/src/parser.test.ts +298 -0
- package/treb-parser/src/parser.ts +2673 -0
- package/treb-utils/package.json +5 -0
- package/treb-utils/src/dispatch.ts +57 -0
- package/treb-utils/src/event_source.ts +147 -0
- package/treb-utils/src/ievent_source.ts +33 -0
- package/treb-utils/src/index.ts +31 -0
- package/treb-utils/src/measurement.ts +174 -0
- package/treb-utils/src/resizable.ts +160 -0
- package/treb-utils/src/scale.ts +137 -0
- package/treb-utils/src/serialize_html.ts +124 -0
- package/treb-utils/src/template.ts +70 -0
- package/treb-utils/src/validate_uri.ts +61 -0
- package/tsconfig.json +10 -0
- package/tsproject.json +30 -0
- package/util/license-plugin-esbuild.js +86 -0
- package/util/list-css-vars.sh +46 -0
- package/README-esm.md +0 -37
- package/treb-bundle.css +0 -2
- package/treb-bundle.mjs +0 -15
|
@@ -0,0 +1,414 @@
|
|
|
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-2023 trebco, llc.
|
|
18
|
+
* info@treb.app
|
|
19
|
+
*
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { Theme, Rectangle, ICellAddress } from 'treb-base-types';
|
|
23
|
+
import type { BaseLayout } from '../layout/base_layout';
|
|
24
|
+
import { SVGSelectionBlock, SelectionOffset } from './svg_selection_block';
|
|
25
|
+
import type { GridSelection } from '../types/grid_selection';
|
|
26
|
+
import { HeaderOverlay, Orientation } from './svg_header_overlay';
|
|
27
|
+
import type { DataModel, ViewModel } from '../types/data_model';
|
|
28
|
+
|
|
29
|
+
// const SVGNS = 'http://www.w3.org/2000/svg';
|
|
30
|
+
|
|
31
|
+
export class SelectionRenderer {
|
|
32
|
+
|
|
33
|
+
public nub_rectangle: Rectangle = new Rectangle(-1, -1, 0, 0);
|
|
34
|
+
|
|
35
|
+
// tmp
|
|
36
|
+
public cached_additional_selections = '';
|
|
37
|
+
|
|
38
|
+
private grid_selections: SVGSelectionBlock[] = [];
|
|
39
|
+
private row_header_selections: SVGSelectionBlock[] = [];
|
|
40
|
+
private column_header_selections: SVGSelectionBlock[] = [];
|
|
41
|
+
private corner_selections: SVGSelectionBlock[] = [];
|
|
42
|
+
|
|
43
|
+
private row_overlay!: HeaderOverlay;
|
|
44
|
+
private column_overlay!: HeaderOverlay;
|
|
45
|
+
private corner_row_overlay!: HeaderOverlay;
|
|
46
|
+
private corner_column_overlay!: HeaderOverlay;
|
|
47
|
+
|
|
48
|
+
constructor(
|
|
49
|
+
private theme: Theme,
|
|
50
|
+
private layout: BaseLayout,
|
|
51
|
+
private model: DataModel,
|
|
52
|
+
private view: ViewModel,
|
|
53
|
+
private primary_selection: GridSelection,
|
|
54
|
+
// private highlight_selection: GridSelection,
|
|
55
|
+
private additional_selections: GridSelection[]) {
|
|
56
|
+
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public Initialize(): void {
|
|
60
|
+
|
|
61
|
+
// create header overlays
|
|
62
|
+
|
|
63
|
+
this.row_overlay =
|
|
64
|
+
new HeaderOverlay(this.theme, this.layout.row_header_selection, Orientation.Horizontal);
|
|
65
|
+
this.column_overlay =
|
|
66
|
+
new HeaderOverlay(this.theme, this.layout.column_header_selection, Orientation.Vertical);
|
|
67
|
+
this.corner_row_overlay =
|
|
68
|
+
new HeaderOverlay(this.theme, this.layout.corner_selection, Orientation.Horizontal);
|
|
69
|
+
this.corner_column_overlay =
|
|
70
|
+
new HeaderOverlay(this.theme, this.layout.corner_selection, Orientation.Vertical);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* we cache blocks that have inline style information. if style
|
|
75
|
+
* information updates we will have to flush the cache and rebuild.
|
|
76
|
+
* /
|
|
77
|
+
public Flush() {
|
|
78
|
+
|
|
79
|
+
// clean up, then call initialize to reconstruct
|
|
80
|
+
|
|
81
|
+
for (const overlay of [
|
|
82
|
+
this.row_overlay,
|
|
83
|
+
this.column_overlay,
|
|
84
|
+
this.corner_row_overlay,
|
|
85
|
+
this.corner_column_overlay,
|
|
86
|
+
]) {
|
|
87
|
+
overlay.Remove();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
this.Initialize();
|
|
91
|
+
|
|
92
|
+
// selections: remove nodes from DOM, if connected, before cleaning up
|
|
93
|
+
|
|
94
|
+
for (const group of [
|
|
95
|
+
this.grid_selections,
|
|
96
|
+
this.row_header_selections,
|
|
97
|
+
this.column_header_selections,
|
|
98
|
+
this.corner_selections,
|
|
99
|
+
]) {
|
|
100
|
+
for (const block of group) {
|
|
101
|
+
|
|
102
|
+
// IE11 requires parentNode; seems to work in chrome/ffx,
|
|
103
|
+
// so unify (was originally using parentElement)
|
|
104
|
+
|
|
105
|
+
if (block.g.parentNode) {
|
|
106
|
+
block.g.parentNode.removeChild(block.g);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
this.grid_selections = [];
|
|
112
|
+
this.row_header_selections = [];
|
|
113
|
+
this.column_header_selections = [];
|
|
114
|
+
this.corner_selections = [];
|
|
115
|
+
|
|
116
|
+
}
|
|
117
|
+
*/
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* renders all (primary and additional) selections. selections are painted
|
|
121
|
+
* on a separate canvas which overlays the grid. unlike grid/header layers,
|
|
122
|
+
* the selection canvas is transparent (alpha = true, which is default, so
|
|
123
|
+
* omitted).
|
|
124
|
+
*
|
|
125
|
+
* updated for svg selections. erase is now required, so parameter is removed.
|
|
126
|
+
* update: add an optional (default true) parameter to re-render additional
|
|
127
|
+
* selections; this will support cache for selections that don't change.
|
|
128
|
+
*/
|
|
129
|
+
public RenderSelections(show_primary_selection = true, rerender = true): void {
|
|
130
|
+
|
|
131
|
+
// this is a dumb way of doing this... it's also error prone,
|
|
132
|
+
// because it needs to track all the function exits (there are
|
|
133
|
+
// two, atm)
|
|
134
|
+
|
|
135
|
+
const cache_primary_empty = this.primary_selection.empty;
|
|
136
|
+
if (!show_primary_selection) {
|
|
137
|
+
this.primary_selection.empty = true;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// temp (we could change the signature and just take an array)
|
|
141
|
+
const aggregate = [this.primary_selection].concat(this.additional_selections);
|
|
142
|
+
|
|
143
|
+
this.RenderSelectionGroup(aggregate, this.layout.grid_selection, undefined, undefined, this.grid_selections, undefined, rerender);
|
|
144
|
+
|
|
145
|
+
// this is the layout rect for row/column header highlights (primary selection only)
|
|
146
|
+
|
|
147
|
+
let header_selection_rect = new Rectangle(-1, -1, 0, 0);
|
|
148
|
+
if (!this.primary_selection.empty) {
|
|
149
|
+
const area = this.view.active_sheet.RealArea(this.primary_selection.area);
|
|
150
|
+
header_selection_rect =
|
|
151
|
+
this.layout.CellAddressToRectangle(area.start).Combine(
|
|
152
|
+
this.layout.CellAddressToRectangle(area.end));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// highlight row header (if visible)
|
|
156
|
+
|
|
157
|
+
if (!this.primary_selection.empty && this.layout.header_offset.y > 2) {
|
|
158
|
+
this.row_overlay.Show(header_selection_rect.left, 0,
|
|
159
|
+
header_selection_rect.width, this.layout.header_offset.y);
|
|
160
|
+
this.corner_row_overlay.Show(header_selection_rect.left + this.layout.header_offset.x, 0,
|
|
161
|
+
header_selection_rect.width, this.layout.header_offset.y);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
this.row_overlay.Hide();
|
|
165
|
+
this.corner_row_overlay.Hide();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// highlight column header (if visible)
|
|
169
|
+
|
|
170
|
+
if (!this.primary_selection.empty && this.layout.header_offset.x > 2) {
|
|
171
|
+
this.column_overlay.Show(0, header_selection_rect.top,
|
|
172
|
+
this.layout.header_offset.x, header_selection_rect.height);
|
|
173
|
+
this.corner_column_overlay.Show(0, header_selection_rect.top + this.layout.header_offset.y,
|
|
174
|
+
this.layout.header_offset.x, header_selection_rect.height);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
this.column_overlay.Hide();
|
|
178
|
+
this.corner_column_overlay.Hide();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (!this.view.active_sheet.freeze.columns && !this.view.active_sheet.freeze.rows) {
|
|
182
|
+
this.primary_selection.empty = cache_primary_empty;
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// check visibility for selections in frozen rows, columns
|
|
187
|
+
|
|
188
|
+
const visible_row: boolean[] = [];
|
|
189
|
+
const visible_column: boolean[] = [];
|
|
190
|
+
|
|
191
|
+
if (this.primary_selection.empty) {
|
|
192
|
+
visible_row.push(false);
|
|
193
|
+
visible_column.push(false);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
const start = this.primary_selection.area.start;
|
|
197
|
+
visible_row.push(
|
|
198
|
+
(start.row <= this.view.active_sheet.freeze.rows) ||
|
|
199
|
+
(start.row === Infinity));
|
|
200
|
+
|
|
201
|
+
visible_column.push(
|
|
202
|
+
(start.column <= this.view.active_sheet.freeze.columns) ||
|
|
203
|
+
(start.column === Infinity));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
for (const {area} of this.additional_selections) {
|
|
207
|
+
visible_row.push(
|
|
208
|
+
(area.start.row <= this.view.active_sheet.freeze.rows) ||
|
|
209
|
+
(area.start.row === Infinity));
|
|
210
|
+
|
|
211
|
+
visible_column.push(
|
|
212
|
+
(area.start.column <= this.view.active_sheet.freeze.columns) ||
|
|
213
|
+
(area.start.column === Infinity));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// selections...
|
|
217
|
+
|
|
218
|
+
if (this.view.active_sheet.freeze.rows) {
|
|
219
|
+
this.RenderSelectionGroup(aggregate, this.layout.row_header_selection,
|
|
220
|
+
visible_row, undefined, this.row_header_selections,
|
|
221
|
+
{x: 0, y: this.layout.header_offset.y});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (this.view.active_sheet.freeze.columns) {
|
|
225
|
+
this.RenderSelectionGroup(aggregate, this.layout.column_header_selection,
|
|
226
|
+
visible_column, undefined, this.column_header_selections,
|
|
227
|
+
{x: this.layout.header_offset.x, y: 0});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (this.view.active_sheet.freeze.rows && this.view.active_sheet.freeze.columns) {
|
|
231
|
+
this.RenderSelectionGroup(aggregate, this.layout.corner_selection,
|
|
232
|
+
visible_column, visible_row, this.corner_selections, {...this.layout.header_offset});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
this.primary_selection.empty = cache_primary_empty;
|
|
236
|
+
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* render a group of selections, optionally gated on one or two boolean
|
|
241
|
+
* arrays (used to check if the selection is within some bounds)
|
|
242
|
+
*/
|
|
243
|
+
private RenderSelectionGroup(
|
|
244
|
+
aggregate: GridSelection[],
|
|
245
|
+
node: SVGElement,
|
|
246
|
+
visible_a: boolean[]|undefined,
|
|
247
|
+
visible_b: boolean[]|undefined,
|
|
248
|
+
group: SVGSelectionBlock[],
|
|
249
|
+
offset?: SelectionOffset,
|
|
250
|
+
rerender = true) {
|
|
251
|
+
|
|
252
|
+
for (let i = 0; i < aggregate.length; i++ ){
|
|
253
|
+
|
|
254
|
+
const sheet_match = (!aggregate[i].area.start.sheet_id) ||
|
|
255
|
+
(aggregate[i].area.start.sheet_id === this.view.active_sheet.id);
|
|
256
|
+
|
|
257
|
+
if (sheet_match && !aggregate[i].empty && (!visible_a || visible_a[i]) && (!visible_b || visible_b[i])) {
|
|
258
|
+
if (rerender || !aggregate[i].rendered) {
|
|
259
|
+
const block = this.EnsureGridSelectionBlock(node, group, i, offset);
|
|
260
|
+
this.RenderSVGSelection(aggregate[i], block, i);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
if (group[i]) group[i].Show(false);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
for (let i = aggregate.length; i < group.length; i++) {
|
|
269
|
+
if (group[i]) group[i].Show(false);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* create or return existing node. supports changing the offset,
|
|
276
|
+
* as that may be variable.
|
|
277
|
+
*
|
|
278
|
+
* FIXME: now that this is in a single method, could inline?
|
|
279
|
+
*/
|
|
280
|
+
private EnsureGridSelectionBlock(
|
|
281
|
+
node: SVGElement,
|
|
282
|
+
node_set: SVGSelectionBlock[],
|
|
283
|
+
index: number,
|
|
284
|
+
offset?: SelectionOffset){
|
|
285
|
+
|
|
286
|
+
// ensure the selection
|
|
287
|
+
|
|
288
|
+
let selection_block: SVGSelectionBlock = node_set[index];
|
|
289
|
+
if (!selection_block) {
|
|
290
|
+
selection_block = new SVGSelectionBlock(!index, this.theme);
|
|
291
|
+
node_set[index] = selection_block;
|
|
292
|
+
|
|
293
|
+
if (index) {
|
|
294
|
+
|
|
295
|
+
// alternate, should indicate a different way
|
|
296
|
+
|
|
297
|
+
// we're adding a node to contain alternate selections just so that
|
|
298
|
+
// we can use 1n, 2n, 3n indexing in CSS... although nth-of-type looks
|
|
299
|
+
// like it might help, it won't. it doesn't mean nth-instance.
|
|
300
|
+
|
|
301
|
+
let group: SVGElement = node.querySelector('.alternate-selections') as SVGElement;
|
|
302
|
+
if (!group) {
|
|
303
|
+
group = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
304
|
+
group.setAttribute('class', 'alternate-selections');
|
|
305
|
+
node.appendChild(group);
|
|
306
|
+
}
|
|
307
|
+
group?.appendChild(selection_block.g);
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
// primary
|
|
311
|
+
node.appendChild(selection_block.g);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (offset) selection_block.Offset(offset);
|
|
317
|
+
|
|
318
|
+
return selection_block;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
private ClampEnd(address: ICellAddress) {
|
|
322
|
+
|
|
323
|
+
// NOTE: column/row can be infinity, but min handles that properly
|
|
324
|
+
|
|
325
|
+
return {
|
|
326
|
+
row: Math.min(address.row, this.view.active_sheet.rows - 1),
|
|
327
|
+
column: Math.min(address.column, this.view.active_sheet.columns - 1),
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* testing an SVG selection. index replaces primary; primary is always index 0.
|
|
334
|
+
*/
|
|
335
|
+
private RenderSVGSelection(selection: GridSelection, block: SVGSelectionBlock, index = 0) {
|
|
336
|
+
|
|
337
|
+
const area = this.view.active_sheet.RealArea(selection.area, true);
|
|
338
|
+
|
|
339
|
+
let rect = this.layout.CellAddressToRectangle(area.start);
|
|
340
|
+
if (area.count > 1) {
|
|
341
|
+
rect = rect.Combine(this.layout.CellAddressToRectangle(area.end));
|
|
342
|
+
}
|
|
343
|
+
else if (index) {
|
|
344
|
+
|
|
345
|
+
// update: select merge areas for alternate selections when single
|
|
346
|
+
|
|
347
|
+
const data = this.view.active_sheet.CellData(selection.target);
|
|
348
|
+
if (data.merge_area) {
|
|
349
|
+
rect = this.layout.CellAddressToRectangle(data.merge_area.start);
|
|
350
|
+
rect = rect.Combine(this.layout.CellAddressToRectangle(data.merge_area.end));
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// nub select target wants the base rectangle (not offset for tiles)
|
|
355
|
+
// FIXME: parameterize size
|
|
356
|
+
|
|
357
|
+
if (!index) {
|
|
358
|
+
this.nub_rectangle = new Rectangle(
|
|
359
|
+
rect.left + rect.width - 6,
|
|
360
|
+
rect.top + rect.height - 6,
|
|
361
|
+
11, 11);
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
// block.SetThemeColor(index - 1);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// FIXME: with giant selection svg, we should clip the selection rect
|
|
368
|
+
// to visible to prevent giant rects/paths
|
|
369
|
+
|
|
370
|
+
// when not showing headers...
|
|
371
|
+
|
|
372
|
+
if (rect.top === 0 && this.layout.header_offset.y <= 1) {
|
|
373
|
+
rect.top = 1;
|
|
374
|
+
rect.height -= 1;
|
|
375
|
+
}
|
|
376
|
+
if (rect.left === 0 && this.layout.header_offset.x <= 1) {
|
|
377
|
+
rect.left = 1;
|
|
378
|
+
rect.width -= 1;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// don't render if the rect is <= 0 height or width (cells are hidden)
|
|
382
|
+
|
|
383
|
+
if (rect.height <= 0 || rect.height <= 0) {
|
|
384
|
+
block.Show(false);
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// FIXME: this could be wrapped up in one call
|
|
389
|
+
|
|
390
|
+
block.SetOutline(rect, !!index);
|
|
391
|
+
|
|
392
|
+
if (!index) {
|
|
393
|
+
|
|
394
|
+
// get the target rect (primary only)
|
|
395
|
+
|
|
396
|
+
let target_rect = this.layout.CellAddressToRectangle(selection.target);
|
|
397
|
+
const data = this.view.active_sheet.CellData(selection.target);
|
|
398
|
+
if (data.merge_area) {
|
|
399
|
+
target_rect = this.layout.CellAddressToRectangle(data.merge_area.start);
|
|
400
|
+
target_rect = target_rect.Combine(this.layout.CellAddressToRectangle(data.merge_area.end));
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
block.SetFill(target_rect, rect);
|
|
404
|
+
block.SetNub(rect);
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
selection.rendered = true;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
block.Show();
|
|
411
|
+
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
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-2023 trebco, llc.
|
|
18
|
+
* info@treb.app
|
|
19
|
+
*
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import type { Theme } from 'treb-base-types';
|
|
23
|
+
|
|
24
|
+
const SVGNS = 'http://www.w3.org/2000/svg';
|
|
25
|
+
|
|
26
|
+
export enum Orientation {
|
|
27
|
+
Horizontal,
|
|
28
|
+
Vertical,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class HeaderOverlay {
|
|
32
|
+
|
|
33
|
+
private g: SVGGElement;
|
|
34
|
+
private overlay: SVGRectElement;
|
|
35
|
+
private highlight: SVGRectElement;
|
|
36
|
+
|
|
37
|
+
constructor(
|
|
38
|
+
private theme: Theme,
|
|
39
|
+
private container: SVGElement,
|
|
40
|
+
private orientation: Orientation) {
|
|
41
|
+
|
|
42
|
+
this.g = document.createElementNS(SVGNS, 'g');
|
|
43
|
+
this.g.setAttribute('class', 'treb-header-overlay');
|
|
44
|
+
|
|
45
|
+
this.overlay = document.createElementNS(SVGNS, 'rect');
|
|
46
|
+
this.overlay.setAttribute('class', 'treb-overlay');
|
|
47
|
+
|
|
48
|
+
this.highlight = document.createElementNS(SVGNS, 'rect');
|
|
49
|
+
this.highlight.setAttribute('class', 'treb-highlight');
|
|
50
|
+
|
|
51
|
+
this.g.style.display = 'none';
|
|
52
|
+
this.g.appendChild(this.highlight);
|
|
53
|
+
this.g.appendChild(this.overlay);
|
|
54
|
+
|
|
55
|
+
container.appendChild(this.g);
|
|
56
|
+
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* remove from DOM, prior to cleanup
|
|
61
|
+
*/
|
|
62
|
+
public Remove() {
|
|
63
|
+
this.container.removeChild(this.g);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public Hide() {
|
|
67
|
+
this.g.style.display = 'none';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public Show(x: number, y: number, width: number, height: number){
|
|
71
|
+
|
|
72
|
+
this.overlay.setAttribute('x', x.toString());
|
|
73
|
+
this.overlay.setAttribute('y', y.toString());
|
|
74
|
+
this.overlay.setAttribute('width', width.toString());
|
|
75
|
+
this.overlay.setAttribute('height', height.toString());
|
|
76
|
+
|
|
77
|
+
if (this.orientation === Orientation.Horizontal) {
|
|
78
|
+
this.highlight.setAttribute('x', x.toString());
|
|
79
|
+
this.highlight.setAttribute('y', (y + height - 2).toString());
|
|
80
|
+
this.highlight.setAttribute('width', width.toString());
|
|
81
|
+
this.highlight.setAttribute('height', '2');
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
this.highlight.setAttribute('x', (x + width - 2).toString());
|
|
85
|
+
this.highlight.setAttribute('y', y.toString());
|
|
86
|
+
this.highlight.setAttribute('width', '2');
|
|
87
|
+
this.highlight.setAttribute('height', height.toString());
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
this.g.style.display = 'block';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
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-2023 trebco, llc.
|
|
18
|
+
* info@treb.app
|
|
19
|
+
*
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import type { Theme, Rectangle } from 'treb-base-types';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* the original selections -- a canvas overlaid over the tile canvases --
|
|
26
|
+
* broke android chrome, hard. so we are switching to an svg overlay (seems
|
|
27
|
+
* ok on android chrome, at least for now).
|
|
28
|
+
*
|
|
29
|
+
* this class wraps up some of the svg-specific stuff, particularly setting
|
|
30
|
+
* attributes.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
const SVGNS = 'http://www.w3.org/2000/svg';
|
|
34
|
+
|
|
35
|
+
export interface SelectionOffset {
|
|
36
|
+
x: number;
|
|
37
|
+
y: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class SVGSelectionBlock {
|
|
41
|
+
|
|
42
|
+
public g: SVGGElement;
|
|
43
|
+
public outline: SVGRectElement;
|
|
44
|
+
public fill?: SVGElement; // SVGPathElement;
|
|
45
|
+
public nub?: SVGRectElement;
|
|
46
|
+
|
|
47
|
+
constructor( primary: boolean,
|
|
48
|
+
private theme: Theme,
|
|
49
|
+
private offset: SelectionOffset = {x: 0, y: 0}) {
|
|
50
|
+
|
|
51
|
+
this.g = document.createElementNS(SVGNS, 'g');
|
|
52
|
+
this.g.setAttribute('transform', `translate(${offset.x}, ${offset.y})`);
|
|
53
|
+
|
|
54
|
+
this.outline = document.createElementNS(SVGNS, 'rect');
|
|
55
|
+
this.outline.setAttribute('class', 'outline');
|
|
56
|
+
|
|
57
|
+
if (primary) {
|
|
58
|
+
|
|
59
|
+
this.g.setAttribute('class', 'selection primary-selection');
|
|
60
|
+
|
|
61
|
+
// primary selections have a separate fill, plus the nub. separate
|
|
62
|
+
// fill because the "target" is unfilled.
|
|
63
|
+
|
|
64
|
+
this.fill = document.createElementNS(SVGNS, 'path');
|
|
65
|
+
this.fill.setAttribute('class', 'fill');
|
|
66
|
+
|
|
67
|
+
this.nub = document.createElementNS(SVGNS, 'rect');
|
|
68
|
+
this.nub.setAttribute('class', 'nub');
|
|
69
|
+
|
|
70
|
+
this.g.appendChild(this.fill);
|
|
71
|
+
this.g.appendChild(this.outline);
|
|
72
|
+
this.g.appendChild(this.nub);
|
|
73
|
+
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
this.g.setAttribute('class', 'selection alternate-selection');
|
|
77
|
+
|
|
78
|
+
// secondary selections. fill is not used, we just fill the rect
|
|
79
|
+
|
|
80
|
+
// UPDATE: adding the fill, for styling purposes; we can set color,
|
|
81
|
+
// and use currentColor, but we can't set opacity separately so we
|
|
82
|
+
// need another node. which is a waste, but ergonomics ftw!
|
|
83
|
+
|
|
84
|
+
this.fill = document.createElementNS(SVGNS, 'rect');
|
|
85
|
+
this.fill.setAttribute('class', 'fill');
|
|
86
|
+
|
|
87
|
+
// this.SetThemeColor(0);
|
|
88
|
+
// if (theme.additional_selection_line_dash_array) {
|
|
89
|
+
// this.outline.setAttribute('stroke-dasharray', theme.additional_selection_line_dash_array);
|
|
90
|
+
// }
|
|
91
|
+
|
|
92
|
+
this.g.appendChild(this.fill);
|
|
93
|
+
this.g.appendChild(this.outline);
|
|
94
|
+
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
public Offset(offset: SelectionOffset): void {
|
|
99
|
+
this.g.setAttribute('transform', `translate(${offset.x}, ${offset.y})`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/*
|
|
103
|
+
public SetThemeColor(index = 0) {
|
|
104
|
+
|
|
105
|
+
if (Array.isArray(this.theme.additional_selection_color)) {
|
|
106
|
+
if (index >= this.theme.additional_selection_color.length) {
|
|
107
|
+
index = index % this.theme.additional_selection_color.length;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (this.theme.additional_selection_overlay_color) {
|
|
112
|
+
if (typeof this.theme.additional_selection_overlay_color === 'string') {
|
|
113
|
+
this.outline.setAttribute('fill', this.theme.additional_selection_overlay_color);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
this.outline.setAttribute('fill', this.theme.additional_selection_overlay_color[index] || '');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
this.outline.setAttribute('fill', '');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (this.theme.additional_selection_color) {
|
|
124
|
+
if (typeof this.theme.additional_selection_color === 'string') {
|
|
125
|
+
this.outline.setAttribute('stroke', this.theme.additional_selection_color);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
this.outline.setAttribute('stroke', this.theme.additional_selection_color[index] || '');
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
this.outline.setAttribute('stroke', '');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
}
|
|
136
|
+
*/
|
|
137
|
+
|
|
138
|
+
public Show(show = true) {
|
|
139
|
+
this.g.style.display = show ? 'block' : 'none';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
public SetOutline(rect: Rectangle, fill = false): void {
|
|
143
|
+
this.outline.setAttribute('x', (rect.left - 1).toString());
|
|
144
|
+
this.outline.setAttribute('y', (rect.top - 1).toString());
|
|
145
|
+
this.outline.setAttribute('width', (rect.width + 1).toString());
|
|
146
|
+
this.outline.setAttribute('height', (rect.height + 1).toString());
|
|
147
|
+
|
|
148
|
+
if (fill && this.fill) {
|
|
149
|
+
this.fill.setAttribute('x', (rect.left).toString());
|
|
150
|
+
this.fill.setAttribute('y', (rect.top).toString());
|
|
151
|
+
this.fill.setAttribute('width', (rect.width).toString());
|
|
152
|
+
this.fill.setAttribute('height', (rect.height).toString());
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
public SetFill(inside: Rectangle, outside: Rectangle) {
|
|
158
|
+
if (!this.fill) return;
|
|
159
|
+
|
|
160
|
+
const d: string[] = [];
|
|
161
|
+
|
|
162
|
+
// inner
|
|
163
|
+
d.push('M' + inside.left + ' ' + inside.top);
|
|
164
|
+
d.push('L' + inside.left + ' ' + inside.bottom);
|
|
165
|
+
d.push('L' + inside.right + ' ' + inside.bottom);
|
|
166
|
+
d.push('L' + inside.right + ' ' + inside.top);
|
|
167
|
+
d.push('Z');
|
|
168
|
+
|
|
169
|
+
// outer, reverse direction
|
|
170
|
+
d.push('M' + outside.left + ' ' + outside.top);
|
|
171
|
+
d.push('L' + outside.right + ' ' + outside.top);
|
|
172
|
+
d.push('L' + outside.right + ' ' + outside.bottom);
|
|
173
|
+
d.push('L' + outside.left + ' ' + outside.bottom);
|
|
174
|
+
d.push('Z');
|
|
175
|
+
|
|
176
|
+
this.fill.setAttribute('d', d.join(' '));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
public SetNub(rect: Rectangle) {
|
|
180
|
+
if (!this.nub) return;
|
|
181
|
+
this.nub.setAttribute('x', (rect.left + rect.width - 4).toString());
|
|
182
|
+
this.nub.setAttribute('y', (rect.top + rect.height - 4).toString());
|
|
183
|
+
this.nub.setAttribute('width', '7');
|
|
184
|
+
this.nub.setAttribute('height', '7');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
}
|