@odoo/o-spreadsheet 17.2.9 → 17.2.11

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.
@@ -3,9 +3,9 @@
3
3
  /**
4
4
  * This file is generated by o-spreadsheet build tools. Do not edit it.
5
5
  * @see https://github.com/odoo/o-spreadsheet
6
- * @version 17.2.9
7
- * @date 2024-06-03T14:56:21.684Z
8
- * @hash 086af6d
6
+ * @version 17.2.11
7
+ * @date 2024-06-14T10:03:05.033Z
8
+ * @hash 7704cc4
9
9
  */
10
10
 
11
11
  'use strict';
@@ -295,7 +295,10 @@ function deepCopy(obj) {
295
295
  * Check if the object is a plain old javascript object.
296
296
  */
297
297
  function isPlainObject(obj) {
298
- return typeof obj === "object" && obj?.constructor === Object;
298
+ return (typeof obj === "object" &&
299
+ obj !== null &&
300
+ // obj.constructor can be undefined when there's no prototype (`Object.create(null, {})`)
301
+ (obj?.constructor === Object || obj?.constructor === undefined));
299
302
  }
300
303
  /**
301
304
  * Sanitize the name of a sheet, by eventually removing quotes
@@ -19774,7 +19777,7 @@ function chartToImage(runtime, figure, type) {
19774
19777
  if ("chartJsConfig" in runtime) {
19775
19778
  runtime.chartJsConfig.plugins = [backgroundColorChartJSPlugin];
19776
19779
  // @ts-ignore
19777
- const chart = new window.Chart(canvas, runtime.chartJsConfig);
19780
+ const chart = new window.Chart(canvas, deepCopy(runtime.chartJsConfig));
19778
19781
  const imgContent = chart.toBase64Image();
19779
19782
  chart.destroy();
19780
19783
  div.remove();
@@ -21720,8 +21723,7 @@ function zoneToRect(zone) {
21720
21723
  */
21721
21724
  function useSpreadsheetRect() {
21722
21725
  const position = owl.useState({ x: 0, y: 0, width: 0, height: 0 });
21723
- let spreadsheetElement = document.querySelector(".o-spreadsheet");
21724
- updatePosition();
21726
+ let spreadsheetElement = null;
21725
21727
  function updatePosition() {
21726
21728
  if (!spreadsheetElement) {
21727
21729
  spreadsheetElement = document.querySelector(".o-spreadsheet");
@@ -21843,7 +21845,7 @@ class Popover extends owl.Component {
21843
21845
  if (!anchor)
21844
21846
  return;
21845
21847
  const propsMaxSize = { width: this.props.maxWidth, height: this.props.maxHeight };
21846
- const elDims = {
21848
+ let elDims = {
21847
21849
  width: el.getBoundingClientRect().width,
21848
21850
  height: el.getBoundingClientRect().height,
21849
21851
  };
@@ -21851,7 +21853,14 @@ class Popover extends owl.Component {
21851
21853
  const popoverPositionHelper = this.props.positioning === "BottomLeft"
21852
21854
  ? new BottomLeftPopoverContext(anchor, this.containerRect, propsMaxSize, spreadsheetRect)
21853
21855
  : new TopRightPopoverContext(anchor, this.containerRect, propsMaxSize, spreadsheetRect);
21854
- const style = popoverPositionHelper.getCss(elDims, this.props.verticalOffset);
21856
+ el.style["max-height"] = popoverPositionHelper.getMaxHeight(elDims.height) + "px";
21857
+ el.style["max-width"] = popoverPositionHelper.getMaxWidth(elDims.width) + "px";
21858
+ // Re-compute the dimensions after setting the max-width and max-height
21859
+ elDims = {
21860
+ width: el.getBoundingClientRect().width,
21861
+ height: el.getBoundingClientRect().height,
21862
+ };
21863
+ let style = popoverPositionHelper.getCss(elDims, this.props.verticalOffset);
21855
21864
  for (const property of Object.keys(style)) {
21856
21865
  el.style[property] = style[property];
21857
21866
  }
@@ -21914,8 +21923,6 @@ class PopoverPositionContext {
21914
21923
  const shouldRenderAtRight = this.shouldRenderAtRight(elDims.width);
21915
21924
  verticalOffset = shouldRenderAtBottom ? verticalOffset : -verticalOffset;
21916
21925
  const cssProperties = {
21917
- "max-height": maxHeight + "px",
21918
- "max-width": maxWidth + "px",
21919
21926
  top: this.getTopCoordinate(actualHeight, shouldRenderAtBottom) -
21920
21927
  this.spreadsheetOffset.y -
21921
21928
  verticalOffset +
@@ -29210,13 +29217,17 @@ class SelectMenu extends owl.Component {
29210
29217
  class: { type: String, optional: true },
29211
29218
  };
29212
29219
  static components = { Menu };
29220
+ menuId = new UuidGenerator().uuidv4();
29213
29221
  selectRef = owl.useRef("select");
29214
29222
  selectRect = useAbsoluteBoundingRect(this.selectRef);
29215
29223
  state = owl.useState({
29216
29224
  isMenuOpen: false,
29217
29225
  });
29218
- onClick() {
29219
- this.state.isMenuOpen = true;
29226
+ onClick(ev) {
29227
+ if (ev.closedMenuId === this.menuId) {
29228
+ return;
29229
+ }
29230
+ this.state.isMenuOpen = !this.state.isMenuOpen;
29220
29231
  }
29221
29232
  onMenuClosed() {
29222
29233
  this.state.isMenuOpen = false;
@@ -29224,7 +29235,7 @@ class SelectMenu extends owl.Component {
29224
29235
  get menuPosition() {
29225
29236
  return {
29226
29237
  x: this.selectRect.x,
29227
- y: this.selectRect.y,
29238
+ y: this.selectRect.y + this.selectRect.height,
29228
29239
  };
29229
29240
  }
29230
29241
  }
@@ -30164,9 +30175,9 @@ class FindAndReplacePanel extends owl.Component {
30164
30175
  static props = {
30165
30176
  onCloseSidePanel: Function,
30166
30177
  };
30167
- dataRange = "";
30168
30178
  searchInput = owl.useRef("searchInput");
30169
30179
  store;
30180
+ state;
30170
30181
  get hasSearchResult() {
30171
30182
  return this.store.selectedMatchIndex !== null;
30172
30183
  }
@@ -30196,6 +30207,7 @@ class FindAndReplacePanel extends owl.Component {
30196
30207
  }
30197
30208
  setup() {
30198
30209
  this.store = useLocalStore(FindAndReplaceStore);
30210
+ this.state = owl.useState({ dataRange: "" });
30199
30211
  owl.onMounted(() => this.searchInput.el?.focus());
30200
30212
  }
30201
30213
  onFocusSearch() {
@@ -30232,13 +30244,13 @@ class FindAndReplacePanel extends owl.Component {
30232
30244
  this.store.updateSearchOptions({ searchScope });
30233
30245
  }
30234
30246
  onSearchRangeChanged(ranges) {
30235
- this.dataRange = ranges[0];
30247
+ this.state.dataRange = ranges[0];
30236
30248
  }
30237
30249
  updateDataRange() {
30238
- if (!this.dataRange || this.searchOptions.searchScope !== "specificRange") {
30250
+ if (!this.state.dataRange || this.searchOptions.searchScope !== "specificRange") {
30239
30251
  return;
30240
30252
  }
30241
- const specificRange = this.env.model.getters.getRangeFromSheetXC(this.env.model.getters.getActiveSheetId(), this.dataRange);
30253
+ const specificRange = this.env.model.getters.getRangeFromSheetXC(this.env.model.getters.getActiveSheetId(), this.state.dataRange);
30242
30254
  this.store.updateSearchOptions({ specificRange });
30243
30255
  }
30244
30256
  }
@@ -32432,8 +32444,15 @@ class Composer extends owl.Component {
32432
32444
  }
32433
32445
  onPaste(ev) {
32434
32446
  if (this.composerStore.editionMode !== "inactive") {
32447
+ // let the browser clipboard work
32435
32448
  ev.stopPropagation();
32436
32449
  }
32450
+ else {
32451
+ // the user meant to paste in the sheet, not open the composer with the pasted content
32452
+ // While we're not editing, we still have the focus and should therefore prevent
32453
+ // the native "paste" to occur.
32454
+ ev.preventDefault();
32455
+ }
32437
32456
  }
32438
32457
  /*
32439
32458
  * Triggered automatically by the content-editable between the keydown and key up
@@ -32442,9 +32461,6 @@ class Composer extends owl.Component {
32442
32461
  if (!this.shouldProcessInputEvents) {
32443
32462
  return;
32444
32463
  }
32445
- if (ev.inputType === "insertFromPaste" && this.composerStore.editionMode === "inactive") {
32446
- return;
32447
- }
32448
32464
  ev.stopPropagation();
32449
32465
  let content;
32450
32466
  if (this.composerStore.editionMode === "inactive") {
@@ -32811,10 +32827,7 @@ class GridComposer extends owl.Component {
32811
32827
  }
32812
32828
  get containerStyle() {
32813
32829
  if (this.composerStore.editionMode === "inactive") {
32814
- return `
32815
- position: absolute;
32816
- z-index: -1000;
32817
- `;
32830
+ return `z-index: -1000;`;
32818
32831
  }
32819
32832
  const isFormula = this.composerStore.currentContent.startsWith("=");
32820
32833
  const cell = this.env.model.getters.getActiveCell();
@@ -33525,7 +33538,7 @@ class FiguresContainer extends owl.Component {
33525
33538
  });
33526
33539
  }
33527
33540
  getContainerRect(container) {
33528
- const { width: viewWidth, height: viewHeight } = this.env.model.getters.getMainViewportRect();
33541
+ const { width: viewWidth, height: viewHeight } = this.env.model.getters.getSheetViewDimension();
33529
33542
  const { x: viewportX, y: viewportY } = this.env.model.getters.getMainViewportCoordinates();
33530
33543
  const x = ["bottomRight", "topRight"].includes(container) ? viewportX : 0;
33531
33544
  const width = viewWidth - x;
@@ -50288,7 +50301,10 @@ class Session extends EventBus {
50288
50301
  /**
50289
50302
  * Notify the server that the user client left the collaborative session
50290
50303
  */
50291
- leave() {
50304
+ leave(data) {
50305
+ if (Object.keys(this.clients).length === 1 && this.processedRevisions.size) {
50306
+ this.snapshot(data);
50307
+ }
50292
50308
  delete this.clients[this.clientId];
50293
50309
  this.transportService.leave(this.clientId);
50294
50310
  this.transportService.sendMessage({
@@ -50301,6 +50317,9 @@ class Session extends EventBus {
50301
50317
  * Send a snapshot of the spreadsheet to the collaboration server
50302
50318
  */
50303
50319
  snapshot(data) {
50320
+ if (this.pendingMessages.length !== 0) {
50321
+ return;
50322
+ }
50304
50323
  const snapshotId = this.uuidGenerator.uuidv4();
50305
50324
  this.transportService.sendMessage({
50306
50325
  type: "SNAPSHOT",
@@ -60213,7 +60232,7 @@ class Model extends EventBus {
60213
60232
  this.session.join(this.config.client);
60214
60233
  }
60215
60234
  leaveSession() {
60216
- this.session.leave();
60235
+ this.session.leave(this.exportData());
60217
60236
  }
60218
60237
  setupUiPlugin(Plugin) {
60219
60238
  const plugin = new Plugin(this.uiPluginConfig);
@@ -60767,6 +60786,6 @@ exports.tokenColors = tokenColors;
60767
60786
  exports.tokenize = tokenize;
60768
60787
 
60769
60788
 
60770
- __info__.version = "17.2.9";
60771
- __info__.date = "2024-06-03T14:56:21.684Z";
60772
- __info__.hash = "086af6d";
60789
+ __info__.version = "17.2.11";
60790
+ __info__.date = "2024-06-14T10:03:05.033Z";
60791
+ __info__.hash = "7704cc4";
@@ -3440,7 +3440,7 @@ declare class Session extends EventBus<CollaborativeEvent> {
3440
3440
  /**
3441
3441
  * Notify the server that the user client left the collaborative session
3442
3442
  */
3443
- leave(): void;
3443
+ leave(data: WorkbookData): void;
3444
3444
  /**
3445
3445
  * Send a snapshot of the spreadsheet to the collaboration server
3446
3446
  */
@@ -3,9 +3,9 @@
3
3
  /**
4
4
  * This file is generated by o-spreadsheet build tools. Do not edit it.
5
5
  * @see https://github.com/odoo/o-spreadsheet
6
- * @version 17.2.9
7
- * @date 2024-06-03T14:56:21.684Z
8
- * @hash 086af6d
6
+ * @version 17.2.11
7
+ * @date 2024-06-14T10:03:05.033Z
8
+ * @hash 7704cc4
9
9
  */
10
10
 
11
11
  import { useEnv, useSubEnv, onWillUnmount, useComponent, status, Component, useRef, onMounted, useEffect, useState, onPatched, onWillPatch, onWillUpdateProps, useExternalListener, onWillStart, xml, useChildSubEnv, markRaw } from '@odoo/owl';
@@ -293,7 +293,10 @@ function deepCopy(obj) {
293
293
  * Check if the object is a plain old javascript object.
294
294
  */
295
295
  function isPlainObject(obj) {
296
- return typeof obj === "object" && obj?.constructor === Object;
296
+ return (typeof obj === "object" &&
297
+ obj !== null &&
298
+ // obj.constructor can be undefined when there's no prototype (`Object.create(null, {})`)
299
+ (obj?.constructor === Object || obj?.constructor === undefined));
297
300
  }
298
301
  /**
299
302
  * Sanitize the name of a sheet, by eventually removing quotes
@@ -19772,7 +19775,7 @@ function chartToImage(runtime, figure, type) {
19772
19775
  if ("chartJsConfig" in runtime) {
19773
19776
  runtime.chartJsConfig.plugins = [backgroundColorChartJSPlugin];
19774
19777
  // @ts-ignore
19775
- const chart = new window.Chart(canvas, runtime.chartJsConfig);
19778
+ const chart = new window.Chart(canvas, deepCopy(runtime.chartJsConfig));
19776
19779
  const imgContent = chart.toBase64Image();
19777
19780
  chart.destroy();
19778
19781
  div.remove();
@@ -21718,8 +21721,7 @@ function zoneToRect(zone) {
21718
21721
  */
21719
21722
  function useSpreadsheetRect() {
21720
21723
  const position = useState({ x: 0, y: 0, width: 0, height: 0 });
21721
- let spreadsheetElement = document.querySelector(".o-spreadsheet");
21722
- updatePosition();
21724
+ let spreadsheetElement = null;
21723
21725
  function updatePosition() {
21724
21726
  if (!spreadsheetElement) {
21725
21727
  spreadsheetElement = document.querySelector(".o-spreadsheet");
@@ -21841,7 +21843,7 @@ class Popover extends Component {
21841
21843
  if (!anchor)
21842
21844
  return;
21843
21845
  const propsMaxSize = { width: this.props.maxWidth, height: this.props.maxHeight };
21844
- const elDims = {
21846
+ let elDims = {
21845
21847
  width: el.getBoundingClientRect().width,
21846
21848
  height: el.getBoundingClientRect().height,
21847
21849
  };
@@ -21849,7 +21851,14 @@ class Popover extends Component {
21849
21851
  const popoverPositionHelper = this.props.positioning === "BottomLeft"
21850
21852
  ? new BottomLeftPopoverContext(anchor, this.containerRect, propsMaxSize, spreadsheetRect)
21851
21853
  : new TopRightPopoverContext(anchor, this.containerRect, propsMaxSize, spreadsheetRect);
21852
- const style = popoverPositionHelper.getCss(elDims, this.props.verticalOffset);
21854
+ el.style["max-height"] = popoverPositionHelper.getMaxHeight(elDims.height) + "px";
21855
+ el.style["max-width"] = popoverPositionHelper.getMaxWidth(elDims.width) + "px";
21856
+ // Re-compute the dimensions after setting the max-width and max-height
21857
+ elDims = {
21858
+ width: el.getBoundingClientRect().width,
21859
+ height: el.getBoundingClientRect().height,
21860
+ };
21861
+ let style = popoverPositionHelper.getCss(elDims, this.props.verticalOffset);
21853
21862
  for (const property of Object.keys(style)) {
21854
21863
  el.style[property] = style[property];
21855
21864
  }
@@ -21912,8 +21921,6 @@ class PopoverPositionContext {
21912
21921
  const shouldRenderAtRight = this.shouldRenderAtRight(elDims.width);
21913
21922
  verticalOffset = shouldRenderAtBottom ? verticalOffset : -verticalOffset;
21914
21923
  const cssProperties = {
21915
- "max-height": maxHeight + "px",
21916
- "max-width": maxWidth + "px",
21917
21924
  top: this.getTopCoordinate(actualHeight, shouldRenderAtBottom) -
21918
21925
  this.spreadsheetOffset.y -
21919
21926
  verticalOffset +
@@ -29208,13 +29215,17 @@ class SelectMenu extends Component {
29208
29215
  class: { type: String, optional: true },
29209
29216
  };
29210
29217
  static components = { Menu };
29218
+ menuId = new UuidGenerator().uuidv4();
29211
29219
  selectRef = useRef("select");
29212
29220
  selectRect = useAbsoluteBoundingRect(this.selectRef);
29213
29221
  state = useState({
29214
29222
  isMenuOpen: false,
29215
29223
  });
29216
- onClick() {
29217
- this.state.isMenuOpen = true;
29224
+ onClick(ev) {
29225
+ if (ev.closedMenuId === this.menuId) {
29226
+ return;
29227
+ }
29228
+ this.state.isMenuOpen = !this.state.isMenuOpen;
29218
29229
  }
29219
29230
  onMenuClosed() {
29220
29231
  this.state.isMenuOpen = false;
@@ -29222,7 +29233,7 @@ class SelectMenu extends Component {
29222
29233
  get menuPosition() {
29223
29234
  return {
29224
29235
  x: this.selectRect.x,
29225
- y: this.selectRect.y,
29236
+ y: this.selectRect.y + this.selectRect.height,
29226
29237
  };
29227
29238
  }
29228
29239
  }
@@ -30162,9 +30173,9 @@ class FindAndReplacePanel extends Component {
30162
30173
  static props = {
30163
30174
  onCloseSidePanel: Function,
30164
30175
  };
30165
- dataRange = "";
30166
30176
  searchInput = useRef("searchInput");
30167
30177
  store;
30178
+ state;
30168
30179
  get hasSearchResult() {
30169
30180
  return this.store.selectedMatchIndex !== null;
30170
30181
  }
@@ -30194,6 +30205,7 @@ class FindAndReplacePanel extends Component {
30194
30205
  }
30195
30206
  setup() {
30196
30207
  this.store = useLocalStore(FindAndReplaceStore);
30208
+ this.state = useState({ dataRange: "" });
30197
30209
  onMounted(() => this.searchInput.el?.focus());
30198
30210
  }
30199
30211
  onFocusSearch() {
@@ -30230,13 +30242,13 @@ class FindAndReplacePanel extends Component {
30230
30242
  this.store.updateSearchOptions({ searchScope });
30231
30243
  }
30232
30244
  onSearchRangeChanged(ranges) {
30233
- this.dataRange = ranges[0];
30245
+ this.state.dataRange = ranges[0];
30234
30246
  }
30235
30247
  updateDataRange() {
30236
- if (!this.dataRange || this.searchOptions.searchScope !== "specificRange") {
30248
+ if (!this.state.dataRange || this.searchOptions.searchScope !== "specificRange") {
30237
30249
  return;
30238
30250
  }
30239
- const specificRange = this.env.model.getters.getRangeFromSheetXC(this.env.model.getters.getActiveSheetId(), this.dataRange);
30251
+ const specificRange = this.env.model.getters.getRangeFromSheetXC(this.env.model.getters.getActiveSheetId(), this.state.dataRange);
30240
30252
  this.store.updateSearchOptions({ specificRange });
30241
30253
  }
30242
30254
  }
@@ -32430,8 +32442,15 @@ class Composer extends Component {
32430
32442
  }
32431
32443
  onPaste(ev) {
32432
32444
  if (this.composerStore.editionMode !== "inactive") {
32445
+ // let the browser clipboard work
32433
32446
  ev.stopPropagation();
32434
32447
  }
32448
+ else {
32449
+ // the user meant to paste in the sheet, not open the composer with the pasted content
32450
+ // While we're not editing, we still have the focus and should therefore prevent
32451
+ // the native "paste" to occur.
32452
+ ev.preventDefault();
32453
+ }
32435
32454
  }
32436
32455
  /*
32437
32456
  * Triggered automatically by the content-editable between the keydown and key up
@@ -32440,9 +32459,6 @@ class Composer extends Component {
32440
32459
  if (!this.shouldProcessInputEvents) {
32441
32460
  return;
32442
32461
  }
32443
- if (ev.inputType === "insertFromPaste" && this.composerStore.editionMode === "inactive") {
32444
- return;
32445
- }
32446
32462
  ev.stopPropagation();
32447
32463
  let content;
32448
32464
  if (this.composerStore.editionMode === "inactive") {
@@ -32809,10 +32825,7 @@ class GridComposer extends Component {
32809
32825
  }
32810
32826
  get containerStyle() {
32811
32827
  if (this.composerStore.editionMode === "inactive") {
32812
- return `
32813
- position: absolute;
32814
- z-index: -1000;
32815
- `;
32828
+ return `z-index: -1000;`;
32816
32829
  }
32817
32830
  const isFormula = this.composerStore.currentContent.startsWith("=");
32818
32831
  const cell = this.env.model.getters.getActiveCell();
@@ -33523,7 +33536,7 @@ class FiguresContainer extends Component {
33523
33536
  });
33524
33537
  }
33525
33538
  getContainerRect(container) {
33526
- const { width: viewWidth, height: viewHeight } = this.env.model.getters.getMainViewportRect();
33539
+ const { width: viewWidth, height: viewHeight } = this.env.model.getters.getSheetViewDimension();
33527
33540
  const { x: viewportX, y: viewportY } = this.env.model.getters.getMainViewportCoordinates();
33528
33541
  const x = ["bottomRight", "topRight"].includes(container) ? viewportX : 0;
33529
33542
  const width = viewWidth - x;
@@ -50286,7 +50299,10 @@ class Session extends EventBus {
50286
50299
  /**
50287
50300
  * Notify the server that the user client left the collaborative session
50288
50301
  */
50289
- leave() {
50302
+ leave(data) {
50303
+ if (Object.keys(this.clients).length === 1 && this.processedRevisions.size) {
50304
+ this.snapshot(data);
50305
+ }
50290
50306
  delete this.clients[this.clientId];
50291
50307
  this.transportService.leave(this.clientId);
50292
50308
  this.transportService.sendMessage({
@@ -50299,6 +50315,9 @@ class Session extends EventBus {
50299
50315
  * Send a snapshot of the spreadsheet to the collaboration server
50300
50316
  */
50301
50317
  snapshot(data) {
50318
+ if (this.pendingMessages.length !== 0) {
50319
+ return;
50320
+ }
50302
50321
  const snapshotId = this.uuidGenerator.uuidv4();
50303
50322
  this.transportService.sendMessage({
50304
50323
  type: "SNAPSHOT",
@@ -60211,7 +60230,7 @@ class Model extends EventBus {
60211
60230
  this.session.join(this.config.client);
60212
60231
  }
60213
60232
  leaveSession() {
60214
- this.session.leave();
60233
+ this.session.leave(this.exportData());
60215
60234
  }
60216
60235
  setupUiPlugin(Plugin) {
60217
60236
  const plugin = new Plugin(this.uiPluginConfig);
@@ -60724,6 +60743,6 @@ const constants = {
60724
60743
  export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, CellErrorType, CommandResult, CorePlugin, DispatchResult, EvaluationError, Model, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, UIPlugin, __info__, addFunction, addRenderingLayer, astToFormula, compile, compileTokens, components, constants, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, hooks, invalidateCFEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, iterateAstNodes, links, load, parse, parseTokens, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, stores, tokenColors, tokenize };
60725
60744
 
60726
60745
 
60727
- __info__.version = "17.2.9";
60728
- __info__.date = "2024-06-03T14:56:21.684Z";
60729
- __info__.hash = "086af6d";
60746
+ __info__.version = "17.2.11";
60747
+ __info__.date = "2024-06-14T10:03:05.033Z";
60748
+ __info__.hash = "7704cc4";