@odoo/o-spreadsheet 14.0.1 → 15.0.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/dist/o_spreadsheet.cjs.js +31302 -0
- package/dist/o_spreadsheet.d.ts +6604 -0
- package/dist/o_spreadsheet.esm.js +31257 -0
- package/dist/o_spreadsheet.iife.js +31304 -0
- package/dist/o_spreadsheet.iife.min.js +3071 -0
- package/package.json +57 -15
- package/.eslintrc.json +0 -20
- package/.husky/post-checkout +0 -9
- package/.husky/pre-commit +0 -39
- package/.prettierignore +0 -1
- package/astToFormula.patch +0 -0
- package/demo/data.js +0 -255
- package/demo/index.html +0 -9
- package/demo/libs/Chart.bundle.js +0 -20755
- package/demo/main.js +0 -101
- package/doc/readme.md +0 -46
- package/rollup.config.js +0 -23
- package/src/base_plugin.ts +0 -102
- package/src/components/autofill.ts +0 -146
- package/src/components/bottom_bar.ts +0 -199
- package/src/components/color_picker.ts +0 -150
- package/src/components/composer/autocomplete_dropdown.ts +0 -135
- package/src/components/composer/composer.ts +0 -487
- package/src/components/composer/content_editable_helper.ts +0 -146
- package/src/components/figures/chart.ts +0 -70
- package/src/components/figures/container.ts +0 -268
- package/src/components/figures/text_figure.ts +0 -36
- package/src/components/grid.ts +0 -635
- package/src/components/helpers/dom_helpers.ts +0 -7
- package/src/components/helpers/drag_and_drop.ts +0 -17
- package/src/components/icons.ts +0 -36
- package/src/components/index.ts +0 -1
- package/src/components/menu.ts +0 -288
- package/src/components/overlay.ts +0 -457
- package/src/components/scrollbar.ts +0 -22
- package/src/components/selection_input.ts +0 -168
- package/src/components/side_panel/cell_is_rule_editor.ts +0 -169
- package/src/components/side_panel/chart_panel.ts +0 -104
- package/src/components/side_panel/color_scale_rule_editor.ts +0 -145
- package/src/components/side_panel/conditional_formatting.ts +0 -396
- package/src/components/side_panel/side_panel.ts +0 -127
- package/src/components/side_panel/translations_terms.ts +0 -45
- package/src/components/spreadsheet.ts +0 -198
- package/src/components/top_bar.ts +0 -535
- package/src/constants.ts +0 -26
- package/src/data.ts +0 -149
- package/src/fonts.ts +0 -41
- package/src/formulas/compiler.ts +0 -258
- package/src/formulas/composer_tokenizer.ts +0 -70
- package/src/formulas/index.ts +0 -15
- package/src/formulas/normalize.ts +0 -52
- package/src/formulas/parser.ts +0 -280
- package/src/formulas/range_tokenizer.ts +0 -97
- package/src/formulas/tokenizer.ts +0 -242
- package/src/functions/arguments.ts +0 -114
- package/src/functions/dates.ts +0 -302
- package/src/functions/helpers.ts +0 -592
- package/src/functions/index.ts +0 -52
- package/src/functions/module_database.ts +0 -282
- package/src/functions/module_date.ts +0 -743
- package/src/functions/module_info.ts +0 -69
- package/src/functions/module_logical.ts +0 -208
- package/src/functions/module_lookup.ts +0 -185
- package/src/functions/module_math.ts +0 -1252
- package/src/functions/module_operators.ts +0 -291
- package/src/functions/module_statistical.ts +0 -882
- package/src/functions/module_text.ts +0 -370
- package/src/helpers/color.ts +0 -30
- package/src/helpers/coordinates.ts +0 -71
- package/src/helpers/grid_manipulation.ts +0 -68
- package/src/helpers/index.ts +0 -7
- package/src/helpers/misc.ts +0 -76
- package/src/helpers/numbers.ts +0 -117
- package/src/helpers/uuid.ts +0 -19
- package/src/helpers/zones.ts +0 -263
- package/src/history.ts +0 -186
- package/src/index.ts +0 -84
- package/src/model.ts +0 -261
- package/src/plugins/autofill.ts +0 -412
- package/src/plugins/chart.ts +0 -404
- package/src/plugins/clipboard.ts +0 -461
- package/src/plugins/conditional_format.ts +0 -445
- package/src/plugins/core.ts +0 -1502
- package/src/plugins/edition.ts +0 -157
- package/src/plugins/evaluation.ts +0 -428
- package/src/plugins/figures.ts +0 -123
- package/src/plugins/formatting.ts +0 -630
- package/src/plugins/highlight.ts +0 -187
- package/src/plugins/index.ts +0 -32
- package/src/plugins/merge.ts +0 -524
- package/src/plugins/renderer.ts +0 -571
- package/src/plugins/selection.ts +0 -562
- package/src/plugins/selection_inputs.ts +0 -326
- package/src/registries/autofill_modifiers.ts +0 -76
- package/src/registries/autofill_rules.ts +0 -98
- package/src/registries/figure_registry.ts +0 -30
- package/src/registries/index.ts +0 -7
- package/src/registries/menu_items_registry.ts +0 -110
- package/src/registries/menus/cell_menu_registry.ts +0 -79
- package/src/registries/menus/col_menu_registry.ts +0 -63
- package/src/registries/menus/index.ts +0 -6
- package/src/registries/menus/menu_items_actions.ts +0 -425
- package/src/registries/menus/row_menu_registry.ts +0 -63
- package/src/registries/menus/sheet_menu_registry.ts +0 -75
- package/src/registries/menus/topbar_menu_registry.ts +0 -204
- package/src/registries/side_panel_registry.ts +0 -26
- package/src/registries/topbar_component_registry.ts +0 -12
- package/src/registry.ts +0 -51
- package/src/translation.ts +0 -44
- package/src/types/autofill.ts +0 -48
- package/src/types/chart.ts +0 -26
- package/src/types/commands.ts +0 -716
- package/src/types/conditional_formatting.ts +0 -124
- package/src/types/env.ts +0 -13
- package/src/types/functions.ts +0 -35
- package/src/types/getters.ts +0 -103
- package/src/types/index.ts +0 -20
- package/src/types/misc.ts +0 -143
- package/src/types/rendering.ts +0 -41
- package/src/types/workbook_data.ts +0 -62
- package/tests/__mocks__/uuid.ts +0 -7
- package/tests/canvas.mock.ts +0 -34
- package/tests/components/__mocks__/content_editable_helper.ts +0 -79
- package/tests/components/__mocks__/scrollbar.ts +0 -18
- package/tests/components/__snapshots__/autocomplete_dropdown_test.ts.snap +0 -29
- package/tests/components/__snapshots__/bottom_bar_test.ts.snap +0 -75
- package/tests/components/__snapshots__/context_menu_test.ts.snap +0 -88
- package/tests/components/__snapshots__/grid_test.ts.snap +0 -60
- package/tests/components/__snapshots__/spreadsheet_test.ts.snap +0 -455
- package/tests/components/__snapshots__/top_bar_test.ts.snap +0 -705
- package/tests/components/autocomplete_dropdown_test.ts +0 -315
- package/tests/components/autofill_test.ts +0 -167
- package/tests/components/bottom_bar_test.ts +0 -262
- package/tests/components/chart_side_panel_test.ts +0 -123
- package/tests/components/composer_test.ts +0 -400
- package/tests/components/context_menu_test.ts +0 -825
- package/tests/components/grid_manipulation_test.ts +0 -163
- package/tests/components/grid_test.ts +0 -581
- package/tests/components/overlay_test.ts +0 -541
- package/tests/components/selection_input_test.ts +0 -259
- package/tests/components/side_panel_test.ts +0 -148
- package/tests/components/spreadsheet_test.ts +0 -194
- package/tests/components/top_bar_test.ts +0 -427
- package/tests/data_test.ts +0 -175
- package/tests/dom_helper.ts +0 -42
- package/tests/formulas/__snapshots__/compiler_test.ts.snap +0 -302
- package/tests/formulas/compiler_test.ts +0 -266
- package/tests/formulas/composer_tokenizer_test.ts +0 -137
- package/tests/formulas/expressions_test.ts +0 -57
- package/tests/formulas/formulas_test.ts +0 -80
- package/tests/formulas/normalize_test.ts +0 -82
- package/tests/formulas/parser_test.ts +0 -224
- package/tests/formulas/range_tokenizer_test.ts +0 -187
- package/tests/formulas/tokenizer_test.ts +0 -164
- package/tests/functions/arguments_test.ts +0 -141
- package/tests/functions/dates_test.ts +0 -1081
- package/tests/functions/functions_test.ts +0 -53
- package/tests/functions/helper_test.ts +0 -98
- package/tests/functions/module_database_test.ts +0 -311
- package/tests/functions/module_date_test.ts +0 -941
- package/tests/functions/module_info_test.ts +0 -139
- package/tests/functions/module_logical_test.ts +0 -369
- package/tests/functions/module_lookup_test.ts +0 -541
- package/tests/functions/module_math_test.ts +0 -2482
- package/tests/functions/module_operators_test.ts +0 -955
- package/tests/functions/module_statistical_test.ts +0 -2014
- package/tests/functions/module_text_test.ts +0 -667
- package/tests/helpers/coordinates_test.ts +0 -25
- package/tests/helpers/misc_test.ts +0 -95
- package/tests/helpers/numbers_test.ts +0 -196
- package/tests/helpers/zones_test.ts +0 -97
- package/tests/helpers.ts +0 -384
- package/tests/menu_items_registry_test.ts +0 -540
- package/tests/model/plugins/__snapshots__/chart_test.ts.snap +0 -90
- package/tests/model/plugins/chart_test.ts +0 -562
- package/tests/model_test.ts +0 -82
- package/tests/plugins/__snapshots__/conditional_formatting_test.ts.snap +0 -131
- package/tests/plugins/__snapshots__/renderer_test.ts.snap +0 -371
- package/tests/plugins/autofill_test.ts +0 -457
- package/tests/plugins/borders_test.ts +0 -295
- package/tests/plugins/clipboard_test.ts +0 -1180
- package/tests/plugins/conditional_formatting_test.ts +0 -1253
- package/tests/plugins/core_test.ts +0 -614
- package/tests/plugins/edition_test.ts +0 -100
- package/tests/plugins/evaluation_async_test.ts +0 -332
- package/tests/plugins/evaluation_dates_test.ts +0 -79
- package/tests/plugins/evaluation_test.ts +0 -901
- package/tests/plugins/figures_test.ts +0 -327
- package/tests/plugins/formatting_test.ts +0 -391
- package/tests/plugins/grid_manipulation_test.ts +0 -1366
- package/tests/plugins/highlight_test.ts +0 -413
- package/tests/plugins/history_test.ts +0 -142
- package/tests/plugins/import_export_test.ts +0 -276
- package/tests/plugins/merges_test.ts +0 -490
- package/tests/plugins/navigation_test.ts +0 -181
- package/tests/plugins/plugins/evaluation_test.ts +0 -111
- package/tests/plugins/renderer_test.ts +0 -598
- package/tests/plugins/resizing_test.ts +0 -152
- package/tests/plugins/selection_input_test.ts +0 -409
- package/tests/plugins/selection_test.ts +0 -442
- package/tests/plugins/sheets_test.ts +0 -643
- package/tests/plugins/style_test.ts +0 -90
- package/tsconfig.json +0 -22
package/src/model.ts
DELETED
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
import * as owl from "@odoo/owl";
|
|
2
|
-
import { BasePlugin } from "./base_plugin";
|
|
3
|
-
import { createEmptyWorkbook, createEmptyWorkbookData, load } from "./data";
|
|
4
|
-
import { DEBUG } from "./helpers/index";
|
|
5
|
-
import { WHistory } from "./history";
|
|
6
|
-
import { pluginRegistry } from "./plugins/index";
|
|
7
|
-
import { _lt } from "./translation";
|
|
8
|
-
import {
|
|
9
|
-
Command,
|
|
10
|
-
CommandDispatcher,
|
|
11
|
-
CommandHandler,
|
|
12
|
-
CommandSuccess,
|
|
13
|
-
EvalContext,
|
|
14
|
-
Getters,
|
|
15
|
-
GridRenderingContext,
|
|
16
|
-
LAYERS,
|
|
17
|
-
Workbook,
|
|
18
|
-
WorkbookData,
|
|
19
|
-
} from "./types/index";
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Model
|
|
23
|
-
*
|
|
24
|
-
* The Model class is the owner of the state of the Spreadsheet. However, it
|
|
25
|
-
* has more a coordination role: it defers the actual state manipulation work to
|
|
26
|
-
* plugins.
|
|
27
|
-
*
|
|
28
|
-
* At creation, the Model instantiates all necessary plugins. They each have
|
|
29
|
-
* a private state (for example, the Selection plugin has the current selection),
|
|
30
|
-
* and share a common core state (named workbook).
|
|
31
|
-
*
|
|
32
|
-
* State changes are then performed through commands. Commands are dispatched
|
|
33
|
-
* to the model, which will then relay them to each plugins (and the history
|
|
34
|
-
* handler). Then, the model will trigger an 'update' event to notify whoever
|
|
35
|
-
* is concerned that the command was applied (if it was not cancelled).
|
|
36
|
-
*
|
|
37
|
-
* Also, the model has an unconventional responsibility: it actually renders the
|
|
38
|
-
* visible viewport on a canvas. This is because each plugins actually manage a
|
|
39
|
-
* specific concern about the content of the spreadsheet, and it is more natural
|
|
40
|
-
* if they are able to read data from their internal state to represent it on the
|
|
41
|
-
* screen.
|
|
42
|
-
*
|
|
43
|
-
* Note that the Model can be used in a standalone way to manipulate
|
|
44
|
-
* programmatically a spreadsheet.
|
|
45
|
-
*/
|
|
46
|
-
|
|
47
|
-
export type Mode = "normal" | "headless" | "readonly";
|
|
48
|
-
export interface ModelConfig {
|
|
49
|
-
mode: Mode;
|
|
50
|
-
openSidePanel: (panel: string, panelProps?: any) => void;
|
|
51
|
-
notifyUser: (content: string) => any;
|
|
52
|
-
askConfirmation: (content: string, confirm: () => any, cancel?: () => any) => any;
|
|
53
|
-
editText: (title: string, placeholder: string, callback: (text: string | null) => any) => any;
|
|
54
|
-
evalContext: EvalContext;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const enum Status {
|
|
58
|
-
Ready,
|
|
59
|
-
Running,
|
|
60
|
-
Finalizing,
|
|
61
|
-
Interactive,
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export class Model extends owl.core.EventBus implements CommandDispatcher {
|
|
65
|
-
/**
|
|
66
|
-
* Handlers are classes that can handle a command. In practice, this is
|
|
67
|
-
* basically a list of all plugins and the history manager (not a plugin)
|
|
68
|
-
*/
|
|
69
|
-
private handlers: CommandHandler[];
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* A plugin can draw some contents on the canvas. But even better: it can do
|
|
73
|
-
* so multiple times. The order of the render calls will determine a list of
|
|
74
|
-
* "layers" (i.e., earlier calls will be obviously drawn below later calls).
|
|
75
|
-
* This list simply keeps the renderers+layer information so the drawing code
|
|
76
|
-
* can just iterate on it
|
|
77
|
-
*/
|
|
78
|
-
private renderers: [BasePlugin, LAYERS][] = [];
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Internal status of the model. Important for command handling coordination
|
|
82
|
-
*/
|
|
83
|
-
private status: Status = Status.Ready;
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* The config object contains some configuration flag and callbacks
|
|
87
|
-
*/
|
|
88
|
-
private config: ModelConfig;
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* The workbook is the core state, shared between all plugins.
|
|
92
|
-
*/
|
|
93
|
-
private workbook: Workbook;
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Getters are the main way the rest of the UI read data from the model. Also,
|
|
97
|
-
* it is shared between all plugins, so they can also communicate with each
|
|
98
|
-
* other.
|
|
99
|
-
*/
|
|
100
|
-
getters: Getters;
|
|
101
|
-
|
|
102
|
-
constructor(data: any = {}, config: Partial<ModelConfig> = {}) {
|
|
103
|
-
super();
|
|
104
|
-
DEBUG.model = this;
|
|
105
|
-
|
|
106
|
-
const workbookData = load(data);
|
|
107
|
-
this.workbook = createEmptyWorkbook();
|
|
108
|
-
const history = new WHistory(this.workbook);
|
|
109
|
-
|
|
110
|
-
this.getters = {
|
|
111
|
-
canUndo: history.canUndo.bind(history),
|
|
112
|
-
canRedo: history.canRedo.bind(history),
|
|
113
|
-
} as Getters;
|
|
114
|
-
this.handlers = [history];
|
|
115
|
-
|
|
116
|
-
this.config = {
|
|
117
|
-
mode: config.mode || "normal",
|
|
118
|
-
openSidePanel: config.openSidePanel || (() => {}),
|
|
119
|
-
notifyUser: config.notifyUser || (() => {}),
|
|
120
|
-
askConfirmation: config.askConfirmation || (() => {}),
|
|
121
|
-
editText: config.editText || (() => {}),
|
|
122
|
-
evalContext: config.evalContext || {},
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
// registering plugins
|
|
126
|
-
for (let Plugin of pluginRegistry.getAll()) {
|
|
127
|
-
this.setupPlugin(Plugin, workbookData);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// starting plugins
|
|
131
|
-
this.dispatch("START");
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
destroy() {
|
|
135
|
-
delete DEBUG.model;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Initialise and properly configure a plugin.
|
|
140
|
-
*
|
|
141
|
-
* This method is private for now, but if the need arise, there is no deep
|
|
142
|
-
* reason why the model could not add dynamically a plugin while it is running.
|
|
143
|
-
*/
|
|
144
|
-
private setupPlugin(Plugin: typeof BasePlugin, data: WorkbookData) {
|
|
145
|
-
const dispatch = this.dispatch.bind(this);
|
|
146
|
-
const history = this.handlers.find((p) => p instanceof WHistory)! as WHistory;
|
|
147
|
-
if (Plugin.modes.includes(this.config.mode)) {
|
|
148
|
-
const plugin = new Plugin(this.workbook, this.getters, history, dispatch, this.config);
|
|
149
|
-
plugin.import(data);
|
|
150
|
-
for (let name of Plugin.getters) {
|
|
151
|
-
if (!(name in plugin)) {
|
|
152
|
-
throw new Error(`Invalid getter name: ${name} for plugin ${plugin.constructor}`);
|
|
153
|
-
}
|
|
154
|
-
this.getters[name] = plugin[name].bind(plugin);
|
|
155
|
-
}
|
|
156
|
-
this.handlers.push(plugin);
|
|
157
|
-
const layers = Plugin.layers.map((l) => [plugin, l] as [BasePlugin, LAYERS]);
|
|
158
|
-
this.renderers.push(...layers);
|
|
159
|
-
this.renderers.sort((p1, p2) => p1[1] - p2[1]);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// ---------------------------------------------------------------------------
|
|
164
|
-
// Command Handling
|
|
165
|
-
// ---------------------------------------------------------------------------
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* The dispatch method is the only entry point to manipulate date in the model.
|
|
169
|
-
* This is through this method that commands are dispatched, most of the time
|
|
170
|
-
* recursively until no plugin want to react anymore.
|
|
171
|
-
*
|
|
172
|
-
* Small technical detail: it is defined as an arrow function. There are two
|
|
173
|
-
* reasons for this:
|
|
174
|
-
* 1. this means that the dispatch method can be "detached" from the model,
|
|
175
|
-
* which is done when it is put in the environment (see the Spreadsheet
|
|
176
|
-
* component)
|
|
177
|
-
* 2. This allows us to define its type by using the interface CommandDispatcher
|
|
178
|
-
*/
|
|
179
|
-
dispatch: CommandDispatcher["dispatch"] = (type: string, payload?: any) => {
|
|
180
|
-
const command: Command = Object.assign({ type }, payload);
|
|
181
|
-
let status: Status = command.interactive ? Status.Interactive : this.status;
|
|
182
|
-
switch (status) {
|
|
183
|
-
case Status.Ready:
|
|
184
|
-
for (let handler of this.handlers) {
|
|
185
|
-
const allowDispatch = handler.allowDispatch(command);
|
|
186
|
-
if (allowDispatch.status === "CANCELLED") {
|
|
187
|
-
return allowDispatch;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
this.status = Status.Running;
|
|
191
|
-
for (const h of this.handlers) {
|
|
192
|
-
h.beforeHandle(command);
|
|
193
|
-
}
|
|
194
|
-
for (const h of this.handlers) {
|
|
195
|
-
h.handle(command);
|
|
196
|
-
}
|
|
197
|
-
this.status = Status.Finalizing;
|
|
198
|
-
for (const h of this.handlers) {
|
|
199
|
-
h.finalize(command);
|
|
200
|
-
}
|
|
201
|
-
this.status = Status.Ready;
|
|
202
|
-
if (this.config.mode !== "headless") {
|
|
203
|
-
this.trigger("update");
|
|
204
|
-
}
|
|
205
|
-
break;
|
|
206
|
-
case Status.Running:
|
|
207
|
-
case Status.Interactive:
|
|
208
|
-
for (const h of this.handlers) {
|
|
209
|
-
h.beforeHandle(command);
|
|
210
|
-
}
|
|
211
|
-
for (const h of this.handlers) {
|
|
212
|
-
h.handle(command);
|
|
213
|
-
}
|
|
214
|
-
break;
|
|
215
|
-
case Status.Finalizing:
|
|
216
|
-
throw new Error(_lt("Nope. Don't do that"));
|
|
217
|
-
}
|
|
218
|
-
return { status: "SUCCESS" } as CommandSuccess;
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
// ---------------------------------------------------------------------------
|
|
222
|
-
// Grid Rendering
|
|
223
|
-
// ---------------------------------------------------------------------------
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* When the Grid component is ready (= mounted), it has a reference to its
|
|
227
|
-
* canvas and need to draw the grid on it. This is then done by calling this
|
|
228
|
-
* method, which will dispatch the call to all registered plugins.
|
|
229
|
-
*
|
|
230
|
-
* Note that nothing prevent multiple grid components from calling this method
|
|
231
|
-
* each, or one grid component calling it multiple times with a different
|
|
232
|
-
* context. This is probably the way we should do if we want to be able to
|
|
233
|
-
* freeze a part of the grid (so, we would need to render different zones)
|
|
234
|
-
*/
|
|
235
|
-
drawGrid(context: GridRenderingContext) {
|
|
236
|
-
// we make sure here that the viewport is properly positioned: the offsets
|
|
237
|
-
// correspond exactly to a cell
|
|
238
|
-
context.viewport = this.getters.snapViewportToCell(context.viewport);
|
|
239
|
-
for (let [renderer, layer] of this.renderers) {
|
|
240
|
-
renderer.drawGrid(context, layer);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// ---------------------------------------------------------------------------
|
|
245
|
-
// Data Export
|
|
246
|
-
// ---------------------------------------------------------------------------
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* As the name of this method strongly implies, it is useful when we need to
|
|
250
|
-
* export date out of the model.
|
|
251
|
-
*/
|
|
252
|
-
exportData(): WorkbookData {
|
|
253
|
-
const data = createEmptyWorkbookData();
|
|
254
|
-
for (let handler of this.handlers) {
|
|
255
|
-
if (handler instanceof BasePlugin) {
|
|
256
|
-
handler.export(data);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
return data;
|
|
260
|
-
}
|
|
261
|
-
}
|
package/src/plugins/autofill.ts
DELETED
|
@@ -1,412 +0,0 @@
|
|
|
1
|
-
import { BasePlugin } from "../base_plugin";
|
|
2
|
-
import { clip, isInside, toCartesian, toXC, union } from "../helpers/index";
|
|
3
|
-
import { Mode } from "../model";
|
|
4
|
-
import { autofillModifiersRegistry, autofillRulesRegistry } from "../registries/index";
|
|
5
|
-
import {
|
|
6
|
-
AutofillModifier,
|
|
7
|
-
AutofillResult,
|
|
8
|
-
CancelledReason,
|
|
9
|
-
Cell,
|
|
10
|
-
Command,
|
|
11
|
-
CommandResult,
|
|
12
|
-
DIRECTION,
|
|
13
|
-
GeneratorCell,
|
|
14
|
-
Getters,
|
|
15
|
-
GridRenderingContext,
|
|
16
|
-
LAYERS,
|
|
17
|
-
Tooltip,
|
|
18
|
-
Zone,
|
|
19
|
-
} from "../types/index";
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* This plugin manage the autofill.
|
|
23
|
-
*
|
|
24
|
-
* The way it works is the next one:
|
|
25
|
-
* For each line (row if the direction is left/right, col otherwise), we create
|
|
26
|
-
* a "AutofillGenerator" object which is used to compute the cells to
|
|
27
|
-
* autofill.
|
|
28
|
-
*
|
|
29
|
-
* When we need to autofill a cell, we compute the origin cell in the source.
|
|
30
|
-
* EX: from A1:A2, autofill A3->A6.
|
|
31
|
-
* Target | Origin cell
|
|
32
|
-
* A3 | A1
|
|
33
|
-
* A4 | A2
|
|
34
|
-
* A5 | A1
|
|
35
|
-
* A6 | A2
|
|
36
|
-
* When we have the origin, we take the associated cell in the AutofillGenerator
|
|
37
|
-
* and we apply the modifier (AutofillModifier) associated to the content of the
|
|
38
|
-
* cell.
|
|
39
|
-
*/
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* This class is used to generate the next values to autofill.
|
|
43
|
-
* It's done from a selection (the source) and describe how the next values
|
|
44
|
-
* should be computed.
|
|
45
|
-
*/
|
|
46
|
-
class AutofillGenerator {
|
|
47
|
-
private cells: GeneratorCell[];
|
|
48
|
-
private getters: Getters;
|
|
49
|
-
private index: number = 0;
|
|
50
|
-
private direction: DIRECTION;
|
|
51
|
-
|
|
52
|
-
constructor(cells: GeneratorCell[], getters: Getters, direction: DIRECTION) {
|
|
53
|
-
this.cells = cells;
|
|
54
|
-
this.getters = getters;
|
|
55
|
-
this.direction = direction;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Get the next value to autofill
|
|
60
|
-
*/
|
|
61
|
-
next(): AutofillResult {
|
|
62
|
-
const genCell = this.cells[this.index++ % this.cells.length];
|
|
63
|
-
if (!genCell.rule) {
|
|
64
|
-
return {
|
|
65
|
-
cellData: genCell.data,
|
|
66
|
-
tooltip: genCell.data.content ? { props: genCell.data.content } : undefined,
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
const rule = genCell.rule;
|
|
70
|
-
const { cellData, tooltip } = autofillModifiersRegistry
|
|
71
|
-
.get(rule.type)
|
|
72
|
-
.apply(rule, genCell.data, this.getters, this.direction);
|
|
73
|
-
return {
|
|
74
|
-
cellData: Object.assign({}, genCell.data, cellData),
|
|
75
|
-
tooltip,
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Autofill Plugin
|
|
82
|
-
*
|
|
83
|
-
*/
|
|
84
|
-
export class AutofillPlugin extends BasePlugin {
|
|
85
|
-
static layers = [LAYERS.Autofill];
|
|
86
|
-
static getters = ["getAutofillTooltip"];
|
|
87
|
-
static modes: Mode[] = ["normal", "readonly"];
|
|
88
|
-
|
|
89
|
-
private autofillZone: Zone | undefined;
|
|
90
|
-
private lastCellSelected: { col?: number; row?: number } = {};
|
|
91
|
-
private direction: DIRECTION | undefined;
|
|
92
|
-
private tooltip: Tooltip | undefined;
|
|
93
|
-
|
|
94
|
-
// ---------------------------------------------------------------------------
|
|
95
|
-
// Command Handling
|
|
96
|
-
// ---------------------------------------------------------------------------
|
|
97
|
-
|
|
98
|
-
allowDispatch(cmd: Command): CommandResult {
|
|
99
|
-
switch (cmd.type) {
|
|
100
|
-
case "AUTOFILL_SELECT":
|
|
101
|
-
const sheetId = this.getters.getActiveSheet();
|
|
102
|
-
this.lastCellSelected.col =
|
|
103
|
-
cmd.col === -1
|
|
104
|
-
? this.lastCellSelected.col
|
|
105
|
-
: clip(cmd.col, 0, this.getters.getNumberCols(sheetId));
|
|
106
|
-
this.lastCellSelected.row =
|
|
107
|
-
cmd.row === -1
|
|
108
|
-
? this.lastCellSelected.row
|
|
109
|
-
: clip(cmd.row, 0, this.getters.getNumberRows(sheetId));
|
|
110
|
-
if (this.lastCellSelected.col !== undefined && this.lastCellSelected.row !== undefined) {
|
|
111
|
-
return { status: "SUCCESS" };
|
|
112
|
-
}
|
|
113
|
-
return { status: "CANCELLED", reason: CancelledReason.InvalidAutofillSelection };
|
|
114
|
-
case "AUTOFILL_AUTO":
|
|
115
|
-
const zone = this.getters.getSelectedZone();
|
|
116
|
-
return zone.top === zone.bottom
|
|
117
|
-
? { status: "SUCCESS" }
|
|
118
|
-
: { status: "CANCELLED", reason: CancelledReason.Unknown };
|
|
119
|
-
}
|
|
120
|
-
return { status: "SUCCESS" };
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
handle(cmd: Command) {
|
|
124
|
-
switch (cmd.type) {
|
|
125
|
-
case "AUTOFILL":
|
|
126
|
-
this.autofill(true);
|
|
127
|
-
break;
|
|
128
|
-
case "AUTOFILL_SELECT":
|
|
129
|
-
this.select(cmd.col, cmd.row);
|
|
130
|
-
break;
|
|
131
|
-
case "AUTOFILL_AUTO":
|
|
132
|
-
this.autofillAuto();
|
|
133
|
-
break;
|
|
134
|
-
case "AUTOFILL_CELL":
|
|
135
|
-
const sheet = this.getters.getActiveSheet();
|
|
136
|
-
this.dispatch("UPDATE_CELL", {
|
|
137
|
-
sheet,
|
|
138
|
-
col: cmd.col,
|
|
139
|
-
row: cmd.row,
|
|
140
|
-
style: cmd.style,
|
|
141
|
-
border: cmd.border,
|
|
142
|
-
content: cmd.content,
|
|
143
|
-
format: cmd.format,
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// ---------------------------------------------------------------------------
|
|
149
|
-
// Getters
|
|
150
|
-
// ---------------------------------------------------------------------------
|
|
151
|
-
|
|
152
|
-
getAutofillTooltip(): Tooltip | undefined {
|
|
153
|
-
return this.tooltip;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// ---------------------------------------------------------------------------
|
|
157
|
-
// Private methods
|
|
158
|
-
// ---------------------------------------------------------------------------
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Autofill the autofillZone from the current selection
|
|
162
|
-
* @param apply Flag set to true to apply the autofill in the model. It's
|
|
163
|
-
* usefull to set it to false when we need to fill the tooltip
|
|
164
|
-
*/
|
|
165
|
-
private autofill(apply: boolean) {
|
|
166
|
-
if (!this.autofillZone || this.direction === undefined) {
|
|
167
|
-
this.tooltip = undefined;
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
const source = this.getters.getSelectedZone();
|
|
171
|
-
const target = this.autofillZone;
|
|
172
|
-
|
|
173
|
-
switch (this.direction) {
|
|
174
|
-
case DIRECTION.DOWN:
|
|
175
|
-
for (let col = source.left; col <= source.right; col++) {
|
|
176
|
-
const xcs: string[] = [];
|
|
177
|
-
for (let row = source.top; row <= source.bottom; row++) {
|
|
178
|
-
xcs.push(toXC(col, row));
|
|
179
|
-
}
|
|
180
|
-
const generator = this.createGenerator(xcs);
|
|
181
|
-
for (let row = target.top; row <= target.bottom; row++) {
|
|
182
|
-
this.computeNewCell(generator, col, row, apply);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
break;
|
|
186
|
-
case DIRECTION.UP:
|
|
187
|
-
for (let col = source.left; col <= source.right; col++) {
|
|
188
|
-
const xcs: string[] = [];
|
|
189
|
-
for (let row = source.bottom; row >= source.top; row--) {
|
|
190
|
-
xcs.push(toXC(col, row));
|
|
191
|
-
}
|
|
192
|
-
const generator = this.createGenerator(xcs);
|
|
193
|
-
for (let row = target.bottom; row >= target.top; row--) {
|
|
194
|
-
this.computeNewCell(generator, col, row, apply);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
break;
|
|
198
|
-
case DIRECTION.LEFT:
|
|
199
|
-
for (let row = source.top; row <= source.bottom; row++) {
|
|
200
|
-
const xcs: string[] = [];
|
|
201
|
-
for (let col = source.right; col >= source.left; col--) {
|
|
202
|
-
xcs.push(toXC(col, row));
|
|
203
|
-
}
|
|
204
|
-
const generator = this.createGenerator(xcs);
|
|
205
|
-
for (let col = target.right; col >= target.left; col--) {
|
|
206
|
-
this.computeNewCell(generator, col, row, apply);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
break;
|
|
210
|
-
case DIRECTION.RIGHT:
|
|
211
|
-
for (let row = source.top; row <= source.bottom; row++) {
|
|
212
|
-
const xcs: string[] = [];
|
|
213
|
-
for (let col = source.left; col <= source.right; col++) {
|
|
214
|
-
xcs.push(toXC(col, row));
|
|
215
|
-
}
|
|
216
|
-
const generator = this.createGenerator(xcs);
|
|
217
|
-
for (let col = target.left; col <= target.right; col++) {
|
|
218
|
-
this.computeNewCell(generator, col, row, apply);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
break;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
if (apply) {
|
|
225
|
-
const zone = union(this.getters.getSelectedZone(), this.autofillZone);
|
|
226
|
-
this.autofillZone = undefined;
|
|
227
|
-
this.lastCellSelected = {};
|
|
228
|
-
this.direction = undefined;
|
|
229
|
-
this.tooltip = undefined;
|
|
230
|
-
this.dispatch("SET_SELECTION", {
|
|
231
|
-
zones: [zone],
|
|
232
|
-
anchor: [zone.left, zone.top],
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Select a cell which becomes the last cell of the autofillZone
|
|
239
|
-
*/
|
|
240
|
-
private select(col: number, row: number) {
|
|
241
|
-
const source = this.getters.getSelectedZone();
|
|
242
|
-
if (isInside(col, row, source)) {
|
|
243
|
-
this.autofillZone = undefined;
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
this.direction = this.getDirection(col, row);
|
|
247
|
-
switch (this.direction) {
|
|
248
|
-
case DIRECTION.UP:
|
|
249
|
-
this.saveZone(row, source.top - 1, source.left, source.right);
|
|
250
|
-
break;
|
|
251
|
-
case DIRECTION.DOWN:
|
|
252
|
-
this.saveZone(source.bottom + 1, row, source.left, source.right);
|
|
253
|
-
break;
|
|
254
|
-
case DIRECTION.LEFT:
|
|
255
|
-
this.saveZone(source.top, source.bottom, col, source.left - 1);
|
|
256
|
-
break;
|
|
257
|
-
case DIRECTION.RIGHT:
|
|
258
|
-
this.saveZone(source.top, source.bottom, source.right + 1, col);
|
|
259
|
-
break;
|
|
260
|
-
}
|
|
261
|
-
this.autofill(false);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* Computes the autofillZone to autofill when the user double click on the
|
|
266
|
-
* autofiller
|
|
267
|
-
*/
|
|
268
|
-
private autofillAuto() {
|
|
269
|
-
const zone = this.getters.getSelectedZone();
|
|
270
|
-
let col = zone.left;
|
|
271
|
-
let row = zone.bottom;
|
|
272
|
-
if (col > 0) {
|
|
273
|
-
let left = this.getters.getCell(col - 1, row);
|
|
274
|
-
while (left && left.content) {
|
|
275
|
-
row += 1;
|
|
276
|
-
left = this.getters.getCell(col - 1, row);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
if (row === zone.bottom) {
|
|
280
|
-
col = zone.right;
|
|
281
|
-
if (col <= this.getters.getNumberCols(this.getters.getActiveSheet())) {
|
|
282
|
-
let right = this.getters.getCell(col + 1, row);
|
|
283
|
-
while (right && right.content) {
|
|
284
|
-
row += 1;
|
|
285
|
-
right = this.getters.getCell(col + 1, row);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
if (row !== zone.bottom) {
|
|
290
|
-
this.select(zone.left, row - 1);
|
|
291
|
-
this.autofill(true);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* Generate the next cell
|
|
297
|
-
*/
|
|
298
|
-
private computeNewCell(generator: AutofillGenerator, col: number, row: number, apply: boolean) {
|
|
299
|
-
const { cellData, tooltip } = generator.next();
|
|
300
|
-
const { col: originCol, row: originRow, content, style, border, format } = cellData;
|
|
301
|
-
this.tooltip = tooltip;
|
|
302
|
-
if (apply) {
|
|
303
|
-
this.dispatch("AUTOFILL_CELL", {
|
|
304
|
-
originCol,
|
|
305
|
-
originRow,
|
|
306
|
-
col,
|
|
307
|
-
row,
|
|
308
|
-
content,
|
|
309
|
-
style,
|
|
310
|
-
border,
|
|
311
|
-
format,
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
/**
|
|
317
|
-
* Get the rule associated to the current cell
|
|
318
|
-
*/
|
|
319
|
-
private getRule(cell: Cell, cells: (Cell | null)[]): AutofillModifier | undefined {
|
|
320
|
-
const rules = autofillRulesRegistry.getAll().sort((a, b) => a.sequence - b.sequence);
|
|
321
|
-
const rule = rules.find((rule) => rule.condition(cell, cells));
|
|
322
|
-
return rule && rule.generateRule(cell, cells);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/**
|
|
326
|
-
* Create the generator to be able to autofill the next cells.
|
|
327
|
-
*/
|
|
328
|
-
private createGenerator(source: string[]): AutofillGenerator {
|
|
329
|
-
const nextCells: GeneratorCell[] = [];
|
|
330
|
-
|
|
331
|
-
const cellsData: { col: number; row: number; cell: Cell | null }[] = [];
|
|
332
|
-
for (let xc of source) {
|
|
333
|
-
const [col, row] = toCartesian(xc);
|
|
334
|
-
const cell = this.getters.getCell(col, row);
|
|
335
|
-
let cellData: { col: number; row: number; cell: Cell | null } = {
|
|
336
|
-
col,
|
|
337
|
-
row,
|
|
338
|
-
cell,
|
|
339
|
-
};
|
|
340
|
-
cellsData.push(cellData);
|
|
341
|
-
}
|
|
342
|
-
const cells = cellsData.map((cellData) => cellData.cell);
|
|
343
|
-
for (let cellData of cellsData) {
|
|
344
|
-
let rule: AutofillModifier | undefined;
|
|
345
|
-
if (cellData && cellData.cell && cellData.cell.content) {
|
|
346
|
-
rule = this.getRule(cellData.cell, cells);
|
|
347
|
-
} else {
|
|
348
|
-
rule = { type: "COPY_MODIFIER" };
|
|
349
|
-
}
|
|
350
|
-
const data = {
|
|
351
|
-
row: cellData.row,
|
|
352
|
-
col: cellData.col,
|
|
353
|
-
content: cellData.cell ? cellData.cell.content : undefined,
|
|
354
|
-
style: cellData.cell ? cellData.cell.style : undefined,
|
|
355
|
-
border: cellData.cell ? cellData.cell.border : undefined,
|
|
356
|
-
format: cellData.cell ? cellData.cell.format : undefined,
|
|
357
|
-
};
|
|
358
|
-
nextCells.push({ data, rule });
|
|
359
|
-
}
|
|
360
|
-
return new AutofillGenerator(nextCells, this.getters, this.direction!);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
private saveZone(top: number, bottom: number, left: number, right: number) {
|
|
364
|
-
this.autofillZone = { top, bottom, left, right };
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
/**
|
|
368
|
-
* Compute the direction of the autofill from the last selected zone and
|
|
369
|
-
* a given cell (col, row)
|
|
370
|
-
*/
|
|
371
|
-
private getDirection(col: number, row: number): DIRECTION {
|
|
372
|
-
const source = this.getters.getSelectedZone();
|
|
373
|
-
const position = {
|
|
374
|
-
up: { number: source.top - row, value: DIRECTION.UP },
|
|
375
|
-
down: { number: row - source.bottom, value: DIRECTION.DOWN },
|
|
376
|
-
left: { number: source.left - col, value: DIRECTION.LEFT },
|
|
377
|
-
right: { number: col - source.right, value: DIRECTION.RIGHT },
|
|
378
|
-
};
|
|
379
|
-
if (
|
|
380
|
-
Object.values(position)
|
|
381
|
-
.map((x) => (x.number > 0 ? 1 : 0) as number)
|
|
382
|
-
.reduce((acc, value) => acc + value) === 1
|
|
383
|
-
) {
|
|
384
|
-
return Object.values(position).find((x) => (x.number > 0 ? 1 : 0))!.value;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
const first = position.up.number > 0 ? "up" : "down";
|
|
388
|
-
const second = position.left.number > 0 ? "left" : "right";
|
|
389
|
-
return Math.abs(position[first].number) >= Math.abs(position[second].number)
|
|
390
|
-
? position[first].value
|
|
391
|
-
: position[second].value;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
// ---------------------------------------------------------------------------
|
|
395
|
-
// Grid rendering
|
|
396
|
-
// ---------------------------------------------------------------------------
|
|
397
|
-
|
|
398
|
-
drawGrid(renderingContext: GridRenderingContext) {
|
|
399
|
-
if (!this.autofillZone) {
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
const { viewport, ctx, thinLineWidth } = renderingContext;
|
|
403
|
-
const [x, y, width, height] = this.getters.getRect(this.autofillZone, viewport);
|
|
404
|
-
if (width > 0 && height > 0) {
|
|
405
|
-
ctx.strokeStyle = "black";
|
|
406
|
-
ctx.lineWidth = thinLineWidth;
|
|
407
|
-
ctx.setLineDash([3]);
|
|
408
|
-
ctx.strokeRect(x, y, width, height);
|
|
409
|
-
ctx.setLineDash([]);
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
}
|