[IMP] spreadsheet: update o_spreadsheet to latest version

https://github.com/odoo/o-spreadsheet/commit/fc313d38 [IMP] images: delete images from the file store
https://github.com/odoo/o-spreadsheet/commit/d4e45186 [REF] config: move data external dependencies to config
https://github.com/odoo/o-spreadsheet/commit/3f6cd0f5 [FIX] headers: fix rounding error in header size
https://github.com/odoo/o-spreadsheet/commit/ac07e847 [FIX] functions: fix error message
https://github.com/odoo/o-spreadsheet/commit/e068c651 [IMP] clipboard: copy cell as HTML in OS clipboard

closes odoo/odoo#109497

Related: odoo/enterprise#35607
Signed-off-by: Rémi Rahir (rar) <rar@odoo.com>
This commit is contained in:
Rémi Rahir
2023-01-10 13:39:56 +01:00
committed by Lucas Lefèvre
parent bcf253063f
commit dfb88e4fa2
21 changed files with 266 additions and 106 deletions
@@ -13,7 +13,7 @@ const { Model } = spreadsheet;
async function downloadSpreadsheet(env, action) {
const { orm, name, data, stateUpdateMessages } = action.params;
const dataSources = new DataSources(orm);
const model = new Model(migrate(data), { external: { dataSources } }, stateUpdateMessages);
const model = new Model(migrate(data), { custom: { dataSources } }, stateUpdateMessages);
await dataSources.waitForAllLoaded();
await waitForDataLoaded(model);
const { files } = model.exportXLSX();
@@ -20,7 +20,7 @@ const { CorePlugin } = spreadsheet;
export default class OdooChartCorePlugin extends CorePlugin {
constructor(config) {
super(config);
this.dataSources = config.external.dataSources;
this.dataSources = config.custom.dataSources;
/** @type {Object.<string, Chart>} */
this.charts = {};
@@ -13,7 +13,7 @@ const DATA_SOURCE_ID = "CURRENCIES";
class CurrencyPlugin extends spreadsheet.UIPlugin {
constructor(config) {
super(config);
this.dataSources = config.external.dataSources;
this.dataSources = config.custom.dataSources;
if (this.dataSources) {
this.dataSources.add(DATA_SOURCE_ID, CurrencyDataSource);
}
@@ -45,7 +45,7 @@ const uuidGenerator = new UuidGenerator();
export default class GlobalFiltersUIPlugin extends spreadsheet.UIPlugin {
constructor(config) {
super(config);
this.orm = config.external.env ? config.external.env.services.orm : undefined;
this.orm = config.custom.env ? config.custom.env.services.orm : undefined;
/**
* Cache record display names for relation filters.
* For each filter, contains a promise resolving to
@@ -5,7 +5,7 @@ const { CorePlugin } = spreadsheet;
export default class IrMenuPlugin extends CorePlugin {
constructor(config) {
super(config);
this.env = config.external.env;
this.env = config.custom.env;
}
/**
@@ -34,7 +34,7 @@ const { CorePlugin } = spreadsheet;
export default class ListCorePlugin extends CorePlugin {
constructor(config) {
super(config);
this.dataSources = config.external.dataSources;
this.dataSources = config.custom.dataSources;
this.nextId = 1;
/** @type {Object.<string, List>} */
@@ -15,7 +15,7 @@ export default class ListUIPlugin extends spreadsheet.UIPlugin {
super(config);
/** @type {string} */
this.selectedListId = undefined;
this.env = config.external.env;
this.env = config.custom.env;
}
beforeHandle(cmd) {
@@ -723,6 +723,14 @@
function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
return numberOfLines * (textLineHeight + MIN_CELL_TEXT_MARGIN) - MIN_CELL_TEXT_MARGIN;
}
/**
* Get the default height of the cell given its style.
*/
function getDefaultCellHeight(style) {
// TO DO: take multi text line into account to compute the real cell height in case of wrapping cell
const fontSize = computeTextFontSizeInPixels(style);
return computeTextLinesHeight(fontSize) + 2 * PADDING_AUTORESIZE_VERTICAL;
}
function computeTextWidth(context, text, style) {
context.save();
context.font = computeTextFont(style);
@@ -3568,6 +3576,12 @@
CellValueType["error"] = "error";
})(CellValueType || (CellValueType = {}));
var ClipboardMIMEType;
(function (ClipboardMIMEType) {
ClipboardMIMEType["PlainText"] = "text/plain";
ClipboardMIMEType["Html"] = "text/html";
})(ClipboardMIMEType || (ClipboardMIMEType = {}));
function isSheetDependent(cmd) {
return "sheetId" in cmd;
}
@@ -4216,6 +4230,18 @@
}
return `${strikethrough ? "line-through" : ""} ${underline ? "underline" : ""}`;
}
/**
* Convert the cell style to CSS properties.
*/
function cellStyleToCss(style) {
const attributes = cellTextStyleToCss(style);
if (!style)
return attributes;
if (style.fillColor) {
attributes["background"] = style.fillColor;
}
return attributes;
}
/**
* Convert the cell text style to CSS properties.
*/
@@ -4239,11 +4265,12 @@
}
return attributes;
}
function cssPropertiesToCss(attributes) {
function cssPropertiesToCss(attributes, newLine = true) {
const separator = newLine ? "\n" : "";
const str = Object.entries(attributes)
.map(([attName, attValue]) => `${attName}: ${attValue};`)
.join("\n");
return "\n" + str + "\n";
.join(separator);
return str ? "\n" + str + "\n" : "";
}
const ERROR_TOOLTIP_MAX_HEIGHT = 80;
@@ -7837,17 +7864,6 @@
style,
});
}
async function readOsClipboard(env) {
try {
return await env.clipboard.readText();
}
catch (e) {
// Permission is required to read the clipboard.
console.warn("The OS clipboard could not be read.");
console.error(e);
return undefined;
}
}
//------------------------------------------------------------------------------
// Simple actions
//------------------------------------------------------------------------------
@@ -7855,40 +7871,35 @@
const REDO_ACTION = (env) => env.model.dispatch("REQUEST_REDO");
const COPY_ACTION = async (env) => {
env.model.dispatch("COPY");
await env.clipboard.writeText(env.model.getters.getClipboardContent());
await env.clipboard.write(env.model.getters.getClipboardContent());
};
const CUT_ACTION = async (env) => {
interactiveCut(env);
await env.clipboard.writeText(env.model.getters.getClipboardContent());
await env.clipboard.write(env.model.getters.getClipboardContent());
};
const PASTE_ACTION = async (env) => {
const spreadsheetClipboard = env.model.getters.getClipboardContent();
const osClipboard = await readOsClipboard(env);
const target = env.model.getters.getSelectedZones();
if (osClipboard && osClipboard !== spreadsheetClipboard) {
interactivePasteFromOS(env, target, osClipboard);
const PASTE_ACTION = async (env) => paste(env);
const PASTE_VALUE_ACTION = async (env) => paste(env, "onlyValue");
async function paste(env, pasteOption) {
const spreadsheetClipboard = env.model.getters.getClipboardTextContent();
const osClipboard = await env.clipboard.readText();
switch (osClipboard.status) {
case "ok":
const target = env.model.getters.getSelectedZones();
if (osClipboard && osClipboard.content !== spreadsheetClipboard) {
interactivePasteFromOS(env, target, osClipboard.content);
}
else {
interactivePaste(env, target, pasteOption);
}
break;
case "notImplemented":
env.raiseError(_lt("Pasting from the context menu is not supported in this browser. Use keyboard shortcuts ctrl+c / ctrl+v instead."));
break;
case "permissionDenied":
env.raiseError(_lt("Access to the clipboard denied by the browser. Please enable clipboard permission for this page in your browser settings."));
break;
}
else {
interactivePaste(env, target);
}
};
const PASTE_VALUE_ACTION = async (env) => {
const spreadsheetClipboard = env.model.getters.getClipboardContent();
const osClipboard = await readOsClipboard(env);
const target = env.model.getters.getSelectedZones();
if (osClipboard && osClipboard !== spreadsheetClipboard) {
env.model.dispatch("PASTE_FROM_OS_CLIPBOARD", {
target,
text: osClipboard,
});
}
else {
env.model.dispatch("PASTE", {
target: env.model.getters.getSelectedZones(),
pasteOption: "onlyValue",
});
}
};
}
const PASTE_FORMAT_ACTION = (env) => interactivePaste(env, env.model.getters.getSelectedZones(), "onlyFormat");
const DELETE_CONTENT_ACTION = (env) => env.model.dispatch("DELETE_CONTENT", {
sheetId: env.model.getters.getActiveSheetId(),
@@ -9126,6 +9137,7 @@
name: NumberFormatTerms.CustomCurrency,
sequence: 39,
separator: true,
isVisible: (env) => env.loadCurrencies !== undefined,
action: OPEN_CUSTOM_CURRENCY_SIDEPANEL_ACTION,
})
.addChild("format_number_date", ["format", "format_number"], {
@@ -11639,7 +11651,7 @@
action: async () => {
this.env.model.dispatch("SELECT_FIGURE", { id: this.props.figure.id });
this.env.model.dispatch("COPY");
await this.env.clipboard.writeText(this.env.model.getters.getClipboardContent());
await this.env.clipboard.clear();
},
});
registry.add("cut", {
@@ -11648,7 +11660,7 @@
action: async () => {
this.env.model.dispatch("SELECT_FIGURE", { id: this.props.figure.id });
this.env.model.dispatch("CUT");
await this.env.clipboard.writeText(this.env.model.getters.getClipboardContent());
await this.env.clipboard.clear();
},
});
registry.add("delete", {
@@ -15341,7 +15353,7 @@
assert(() => pv > 0, _lt("The present value (%s) must be strictly positive.", pv.toString()));
}
function assertPeriodSmallerOrEqualToLife(period, life) {
assert(() => period <= life, _lt("The period (%s) must be less than or equal life (%.", period.toString(), life.toString()));
assert(() => period <= life, _lt("The period (%s) must be less than or equal life (%s).", period.toString(), life.toString()));
}
function assertInvestmentStrictlyPositive(investment) {
assert(() => investment > 0, _lt("The investment (%s) must be strictly positive.", investment.toString()));
@@ -23510,6 +23522,11 @@ day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt("A
if (!this.gridEl.contains(document.activeElement)) {
return;
}
const clipboardData = ev.clipboardData;
if (!clipboardData) {
this.displayWarningCopyPasteNotSupported();
return;
}
/* If we are currently editing a cell, let the default behavior */
if (this.env.model.getters.getEditionMode() !== "inactive") {
return;
@@ -23521,7 +23538,9 @@ day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt("A
this.env.model.dispatch("COPY");
}
const content = this.env.model.getters.getClipboardContent();
ev.clipboardData.setData("text/plain", content);
for (const type in content) {
clipboardData.setData(type, content[type]);
}
ev.preventDefault();
}
paste(ev) {
@@ -23529,11 +23548,15 @@ day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt("A
return;
}
const clipboardData = ev.clipboardData;
if (clipboardData.types.indexOf("text/plain") > -1) {
const content = clipboardData.getData("text/plain");
if (!clipboardData) {
this.displayWarningCopyPasteNotSupported();
return;
}
if (clipboardData.types.indexOf(ClipboardMIMEType.PlainText) > -1) {
const content = clipboardData.getData(ClipboardMIMEType.PlainText);
const target = this.env.model.getters.getSelectedZones();
const clipBoardString = this.env.model.getters.getClipboardContent();
if (clipBoardString === content) {
const clipboardString = this.env.model.getters.getClipboardTextContent();
if (clipboardString === content) {
// the paste actually comes from o-spreadsheet itself
interactivePaste(this.env, target);
}
@@ -23542,6 +23565,9 @@ day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt("A
}
}
}
displayWarningCopyPasteNotSupported() {
this.env.raiseError(_lt("Copy/Paste is not supported in this browser."));
}
closeMenu() {
this.menuState.isOpen = false;
this.focus();
@@ -25467,8 +25493,8 @@ day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt("A
function formatAttributes(attrs) {
return new XMLString(attrs.map(([key, val]) => `${key}="${xmlEscape(val)}"`).join(" "));
}
function parseXML(xmlString) {
const document = new DOMParser().parseFromString(xmlString.toString(), "text/xml");
function parseXML(xmlString, mimeType = "text/xml") {
const document = new DOMParser().parseFromString(xmlString.toString(), mimeType);
const parserError = document.querySelector("parsererror");
if (parserError) {
const errorString = parserError.innerHTML;
@@ -27213,6 +27239,11 @@ day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt("A
* @param sheetId an optional sheetId to adapt either range of that sheet specifically, or ranges pointing to that sheet
*/
adaptRanges(applyChange, sheetId) { }
/**
* Implement this method to clean unused external resources, such as images
* stored on a server which have been deleted.
*/
garbageCollectExternalResources() { }
}
/**
@@ -29233,7 +29264,7 @@ day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt("A
}
getHeaderSize(sheetId, dimension, index) {
var _a, _b, _c, _d;
return (((_b = (_a = this.sizes[sheetId]) === null || _a === void 0 ? void 0 : _a[dimension][index]) === null || _b === void 0 ? void 0 : _b.manualSize) ||
return Math.round(((_b = (_a = this.sizes[sheetId]) === null || _a === void 0 ? void 0 : _a[dimension][index]) === null || _b === void 0 ? void 0 : _b.manualSize) ||
((_d = (_c = this.sizes[sheetId]) === null || _c === void 0 ? void 0 : _c[dimension][index]) === null || _d === void 0 ? void 0 : _d.computedSize()) ||
this.getDefaultHeaderSize(dimension));
}
@@ -29266,9 +29297,7 @@ day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt("A
return DEFAULT_CELL_HEIGHT;
}
const cell = this.getters.getCell(position);
// TO DO: take multi text line into account to compute the real cell height in case of wrapping cell
const fontSize = computeTextFontSizeInPixels(cell === null || cell === void 0 ? void 0 : cell.style);
return computeTextLinesHeight(fontSize) + 2 * PADDING_AUTORESIZE_VERTICAL;
return getDefaultCellHeight(cell === null || cell === void 0 ? void 0 : cell.style);
}
/**
* Get the tallest cell of a row and its size.
@@ -29540,7 +29569,7 @@ day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt("A
action: async () => {
this.env.model.dispatch("SELECT_FIGURE", { id: this.figureId });
this.env.model.dispatch("COPY");
await this.env.clipboard.writeText(this.env.model.getters.getClipboardContent());
await this.env.clipboard.clear();
},
});
registry.add("cut", {
@@ -29550,7 +29579,7 @@ day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt("A
action: async () => {
this.env.model.dispatch("SELECT_FIGURE", { id: this.figureId });
this.env.model.dispatch("CUT");
await this.env.clipboard.writeText(this.env.model.getters.getClipboardContent());
await this.env.clipboard.clear();
},
});
registry.add("reset_size", {
@@ -29618,10 +29647,15 @@ day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt("A
};
class ImagePlugin extends CorePlugin {
constructor() {
super(...arguments);
constructor(config) {
super(config);
this.images = {};
/**
* paths of images synced with the file store server.
*/
this.syncedImages = new Set();
this.nextId = 1;
this.fileStore = config.external.fileStore;
}
// ---------------------------------------------------------------------------
// Command Handling
@@ -29642,6 +29676,7 @@ day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt("A
case "CREATE_IMAGE":
this.addFigure(cmd.figureId, cmd.sheetId, cmd.position, cmd.size);
this.history.update("images", cmd.sheetId, cmd.figureId, cmd.definition);
this.syncedImages.add(cmd.definition.path);
break;
case "DUPLICATE_SHEET": {
const sheetFiguresFrom = this.getters.getFigures(cmd.sheetId);
@@ -29672,6 +29707,18 @@ day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt("A
break;
}
}
/**
* Delete unused images from the file store
*/
garbageCollectExternalResources() {
var _a;
const images = new Set(this.getAllImages().map((image) => image.path));
for (const path of this.syncedImages) {
if (!images.has(path)) {
(_a = this.fileStore) === null || _a === void 0 ? void 0 : _a.delete(path);
}
}
}
// ---------------------------------------------------------------------------
// Getters
// ---------------------------------------------------------------------------
@@ -29709,6 +29756,7 @@ day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt("A
for (const image of images) {
this.history.update("nextId", this.nextId + 1);
this.history.update("images", sheet.id, image.id, image.data);
this.syncedImages.add(image.data.path);
}
}
}
@@ -29721,6 +29769,13 @@ day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt("A
}
}
}
getAllImages() {
const images = [];
for (const sheetId in this.images) {
images.push(...Object.values(this.images[sheetId] || {}).filter(isDefined$1));
}
return images;
}
}
ImagePlugin.getters = ["getImage", "getImagePath", "getImageSize"];
figureRegistry.add("image", {
@@ -31532,7 +31587,7 @@ day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt("A
super(config);
this.isUpToDate = false;
this.evaluatedCells = {};
this.evalContext = config.external;
this.evalContext = config.custom;
this.lazyEvaluation = config.lazyEvaluation;
}
// ---------------------------------------------------------------------------
@@ -36712,6 +36767,7 @@ day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt("A
const position = { col, row, sheetId };
cellsInRow.push({
cell: getters.getCell(position),
style: getters.getCellComputedStyle(position),
evaluatedCell: getters.getEvaluatedCell(position),
border: getters.getCellBorder(position) || undefined,
position,
@@ -37062,6 +37118,12 @@ day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt("A
}
}
getClipboardContent() {
return {
[ClipboardMIMEType.PlainText]: this.getPlainTextContent(),
[ClipboardMIMEType.Html]: this.getHTMLContent(),
};
}
getPlainTextContent() {
return (this.cells
.map((cells) => {
return cells
@@ -37070,6 +37132,23 @@ day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt("A
})
.join("\n") || "\t");
}
getHTMLContent() {
if (this.cells.length == 1 && this.cells[0].length == 1) {
return this.getters.getCellText(this.cells[0][0].position);
}
let htmlTable = '<table border="1" style="border-collapse:collapse">';
for (const row of this.cells) {
htmlTable += "<tr>";
for (const cell of row) {
const cssStyle = cssPropertiesToCss(cellStyleToCss(cell.style), false);
const cellText = this.getters.getCellText(cell.position);
htmlTable += `<td style="${cssStyle}">` + xmlEscape(cellText) + "</td>";
}
htmlTable += "</tr>";
}
htmlTable += "</table>";
return htmlTable;
}
isColRowDirtyingClipboard(position, dimension) {
if (!this.zones)
return false;
@@ -37160,7 +37239,7 @@ day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt("A
this.dispatch("SELECT_FIGURE", { id: newId });
}
getClipboardContent() {
return "\t";
return { [ClipboardMIMEType.PlainText]: "\t" };
}
isColRowDirtyingClipboard(position, dimension) {
return false;
@@ -37250,7 +37329,9 @@ day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt("A
this.selection.selectZone({ cell: { col: activeCol, row: activeRow }, zone });
}
getClipboardContent() {
return this.values.map((values) => values.join("\t")).join("\n");
return {
[ClipboardMIMEType.PlainText]: this.values.map((values) => values.join("\t")).join("\n"),
};
}
getPasteZone(target) {
const height = this.values.length;
@@ -37407,7 +37488,11 @@ day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt("A
*/
getClipboardContent() {
var _a;
return ((_a = this.state) === null || _a === void 0 ? void 0 : _a.getClipboardContent()) || "\t";
return ((_a = this.state) === null || _a === void 0 ? void 0 : _a.getClipboardContent()) || { [ClipboardMIMEType.PlainText]: "\t" };
}
getClipboardTextContent() {
var _a;
return ((_a = this.state) === null || _a === void 0 ? void 0 : _a.getClipboardContent()[ClipboardMIMEType.PlainText]) || "\t";
}
isCutOperation() {
return this.state ? this.state.operation === "CUT" : false;
@@ -37485,7 +37570,12 @@ day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt("A
}
}
ClipboardPlugin.layers = [2 /* LAYERS.Clipboard */];
ClipboardPlugin.getters = ["getClipboardContent", "isCutOperation", "isPaintingFormat"];
ClipboardPlugin.getters = [
"getClipboardContent",
"getClipboardTextContent",
"isCutOperation",
"isPaintingFormat",
];
const selectionStatisticFunctions = [
{
@@ -39296,6 +39386,66 @@ day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt("A
onComposerContentFocused: Function,
};
function instantiateClipboard() {
return new WebClipboardWrapper(navigator.clipboard);
}
class WebClipboardWrapper {
// Can be undefined because navigator.clipboard doesn't exist in old browsers
constructor(clipboard) {
this.clipboard = clipboard;
}
async write(clipboardContent) {
var _a;
try {
(_a = this.clipboard) === null || _a === void 0 ? void 0 : _a.write(this.getClipboardItems(clipboardContent));
}
catch (e) { }
}
async writeText(text) {
var _a;
try {
(_a = this.clipboard) === null || _a === void 0 ? void 0 : _a.writeText(text);
}
catch (e) { }
}
async readText() {
let permissionResult = undefined;
try {
//@ts-ignore - clipboard-read is not implemented in all browsers
permissionResult = await navigator.permissions.query({ name: "clipboard-read" });
}
catch (e) { }
try {
const clipboardContent = await this.clipboard.readText();
return { status: "ok", content: clipboardContent };
}
catch (e) {
const status = (permissionResult === null || permissionResult === void 0 ? void 0 : permissionResult.state) === "denied" ? "permissionDenied" : "notImplemented";
return { status };
}
}
async clear() {
var _a;
try {
(_a = this.clipboard) === null || _a === void 0 ? void 0 : _a.write([]);
}
catch (e) { }
}
getClipboardItems(content) {
return [
new ClipboardItem({
[ClipboardMIMEType.PlainText]: this.getBlob(content, ClipboardMIMEType.PlainText),
[ClipboardMIMEType.Html]: this.getBlob(content, ClipboardMIMEType.Html),
}),
];
}
getBlob(clipboardContent, type) {
return new Blob([clipboardContent[type] || ""], {
type,
});
}
}
css /* scss */ `
.o-spreadsheet {
position: relative;
@@ -39392,14 +39542,16 @@ day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt("A
"CTRL+H": () => this.toggleSidePanel("FindAndReplace", {}),
"CTRL+F": () => this.toggleSidePanel("FindAndReplace", {}),
};
const fileStore = this.model.config.external.fileStore;
owl.useSubEnv({
model: this.model,
imageProvider: this.props.fileStore ? new ImageProvider(this.props.fileStore) : undefined,
imageProvider: fileStore ? new ImageProvider(fileStore) : undefined,
loadCurrencies: this.model.config.external.loadCurrencies,
isDashboard: () => this.model.getters.isDashboard(),
openSidePanel: this.openSidePanel.bind(this),
toggleSidePanel: this.toggleSidePanel.bind(this),
_t: Spreadsheet._t,
clipboard: navigator.clipboard,
clipboard: this.env.clipboard || instantiateClipboard(),
});
owl.useExternalListener(window, "resize", () => this.render(true));
owl.useExternalListener(window, "beforeunload", this.unbindModelEvents.bind(this));
@@ -39535,7 +39687,6 @@ day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt("A
Spreadsheet._t = t;
Spreadsheet.props = {
model: Object,
fileStore: { type: Object, optional: true },
};
class LocalTransportService {
@@ -43046,6 +43197,7 @@ day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt("A
this.joinSession();
if (config.snapshotRequested) {
this.session.snapshot(this.exportData());
this.garbageCollectExternalResources();
}
// mark all models as "raw", so they will not be turned into reactive objects
// by owl, since we do not rely on reactivity
@@ -43136,6 +43288,7 @@ day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt("A
return {
...config,
mode: config.mode || "normal",
custom: config.custom || {},
external: config.external || {},
transportService,
client,
@@ -43152,6 +43305,7 @@ day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt("A
range: this.range,
dispatch: this.dispatchFromCorePlugin,
uuidGenerator: this.uuidGenerator,
custom: this.config.custom,
external: this.config.external,
};
}
@@ -43162,7 +43316,7 @@ day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt("A
dispatch: this.dispatch,
selection: this.selection,
moveClient: this.session.move.bind(this.session),
external: this.config.external,
custom: this.config.custom,
uiActions: this.config,
lazyEvaluation: this.config.lazyEvaluation,
};
@@ -43241,6 +43395,7 @@ day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt("A
if (mode !== "normal") {
this.dispatch("STOP_EDITION", { cancel: true });
}
// @ts-ignore For testing purposes only
this.config.mode = mode;
this.trigger("update");
}
@@ -43264,6 +43419,11 @@ day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt("A
data = JSON.parse(JSON.stringify(data));
return getXLSX(data);
}
garbageCollectExternalResources() {
for (const plugin of this.corePlugins) {
plugin.garbageCollectExternalResources();
}
}
}
/**
@@ -43399,8 +43559,8 @@ day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt("A
Object.defineProperty(exports, '__esModule', { value: true });
exports.__info__.version = '2.0.0';
exports.__info__.date = '2023-01-04T17:31:52.940Z';
exports.__info__.hash = 'f88b16b';
exports.__info__.date = '2023-01-10T08:40:22.199Z';
exports.__info__.hash = 'fc313d3';
})(this.o_spreadsheet = this.o_spreadsheet || {}, owl);
//# sourceMappingURL=o_spreadsheet.js.map
@@ -39,7 +39,7 @@ const { CorePlugin } = spreadsheet;
export default class PivotCorePlugin extends CorePlugin {
constructor(config) {
super(config);
this.dataSources = config.external.dataSources;
this.dataSources = config.custom.dataSources;
this.nextId = 1;
/** @type {Object.<string, Pivot>} */
@@ -124,7 +124,7 @@ QUnit.module(
"Links between charts and ir.menus are correctly imported/exported",
async function (assert) {
const env = await makeTestEnv({ serverData: this.serverData });
const model = new Model({}, { external: { env } });
const model = new Model({}, { custom: { env } });
createBasicChart(model, chartId);
model.dispatch("LINK_ODOO_MENU_TO_CHART", {
chartId,
@@ -136,7 +136,7 @@ QUnit.module(
1,
"Link to odoo menu is exported"
);
const importedModel = new Model(exportedData, { external: { env } });
const importedModel = new Model(exportedData, { custom: { env } });
const chartMenu = importedModel.getters.getChartOdooMenu(chartId);
assert.equal(chartMenu.id, 1, "Link to odoo menu is imported");
}
@@ -144,7 +144,7 @@ QUnit.module(
QUnit.test("Can undo-redo a LINK_ODOO_MENU_TO_CHART", async function (assert) {
const env = await makeTestEnv({ serverData: this.serverData });
const model = new Model({}, { external: { env } });
const model = new Model({}, { custom: { env } });
createBasicChart(model, chartId);
model.dispatch("LINK_ODOO_MENU_TO_CHART", {
chartId,
@@ -159,7 +159,7 @@ QUnit.module(
QUnit.test("link is removed when figure is deleted", async function (assert) {
const env = await makeTestEnv({ serverData: this.serverData });
const model = new Model({}, { external: { env } });
const model = new Model({}, { custom: { env } });
createBasicChart(model, chartId);
model.dispatch("LINK_ODOO_MENU_TO_CHART", {
chartId,
@@ -378,7 +378,7 @@ QUnit.module("spreadsheet > Global filters model", {}, () => {
assert.equal(listDomain.length, 7, "it should have updated the list domain");
const newModel = new Model(model.exportData(), {
external: model.config.external,
custom: model.config.custom,
});
assert.equal(newModel.getters.getGlobalFilters().length, 2);
@@ -638,7 +638,7 @@ QUnit.module("spreadsheet > Global filters model", {}, () => {
const model = new Model(
{},
{
external: {
custom: {
dataSources: new DataSources({ ...orm, silent: orm }),
env: { services: { orm } },
},
@@ -26,7 +26,7 @@ function beforeEach() {
QUnit.module("spreadsheet > menu link cells", { beforeEach }, () => {
QUnit.test("ir.menu linked based on xml id", async function (assert) {
const env = await makeTestEnv({ serverData: getMenuServerData() });
const model = new Model({}, { external: { env } });
const model = new Model({}, { custom: { env } });
setCellContent(model, "A1", "[label](odoo://ir_menu_xml_id/test_menu)");
const cell = getCell(model, "A1");
const evaluatedCell = getEvaluatedCell(model, "A1");
@@ -46,7 +46,7 @@ QUnit.module("spreadsheet > menu link cells", { beforeEach }, () => {
QUnit.test("ir.menu linked based on record id", async function (assert) {
const env = await makeTestEnv({ serverData: getMenuServerData() });
const model = new Model({}, { external: { env } });
const model = new Model({}, { custom: { env } });
setCellContent(model, "A1", "[label](odoo://ir_menu_id/2)");
const cell = getCell(model, "A1");
const evaluatedCell = getEvaluatedCell(model, "A1");
@@ -66,7 +66,7 @@ QUnit.module("spreadsheet > menu link cells", { beforeEach }, () => {
QUnit.test("ir.menu linked based on xml id which does not exists", async function (assert) {
const env = await makeTestEnv({ serverData: getMenuServerData() });
const model = new Model({}, { external: { env } });
const model = new Model({}, { custom: { env } });
setCellContent(model, "A1", "[label](odoo://ir_menu_xml_id/does_not_exists)");
assert.equal(
getCell(model, "A1").content,
@@ -81,7 +81,7 @@ QUnit.module("spreadsheet > menu link cells", { beforeEach }, () => {
QUnit.test("ir.menu linked based on record id which does not exists", async function (assert) {
const env = await makeTestEnv({ serverData: getMenuServerData() });
const model = new Model({}, { external: { env } });
const model = new Model({}, { custom: { env } });
setCellContent(model, "A1", "[label](odoo://ir_menu_id/9999)");
assert.equal(getCell(model, "A1").content, "[label](odoo://ir_menu_id/9999)");
assert.equal(getEvaluatedCell(model, "A1").value, "#LINK");
@@ -93,7 +93,7 @@ QUnit.module("spreadsheet > menu link cells", { beforeEach }, () => {
QUnit.test("Odoo link cells can be imported/exported", async function (assert) {
const env = await makeTestEnv({ serverData: getMenuServerData() });
const model = new Model({}, { external: { env } });
const model = new Model({}, { custom: { env } });
setCellContent(model, "A1", "[label](odoo://ir_menu_id/2)");
let cell = getCell(model, "A1");
let evaluatedCell = getEvaluatedCell(model, "A1");
@@ -109,7 +109,7 @@ QUnit.module("spreadsheet > menu link cells", { beforeEach }, () => {
"odoo://ir_menu_id/2",
"The link url should reference the correct menu"
);
const model2 = new Model(model.exportData(), { external: { env } });
const model2 = new Model(model.exportData(), { custom: { env } });
cell = getCell(model2, "A1");
evaluatedCell = getEvaluatedCell(model, "A1");
assert.equal(evaluatedCell.value, "label", "The value should be the menu name");
@@ -41,7 +41,7 @@ QUnit.test("click a web link", async (assert) => {
},
],
};
const model = new Model(data, { external: { env } });
const model = new Model(data, { custom: { env } });
const cell = getEvaluatedCell(model, "A1");
assert.strictEqual(urlRepresentation(cell.link, model.getters), "https://odoo.com");
openLink(cell.link, env);
@@ -68,7 +68,7 @@ QUnit.test("click a menu link", async (assert) => {
},
],
};
const model = new Model(data, { external: { env } });
const model = new Model(data, { custom: { env } });
const cell = getEvaluatedCell(model, "A1");
assert.strictEqual(urlRepresentation(cell.link, model.getters), "menu with xmlid");
openLink(cell.link, env);
@@ -112,7 +112,7 @@ QUnit.test("click a menu link", async (assert) => {
},
],
};
const model = new Model(data, { external: { env } });
const model = new Model(data, { custom: { env } });
const cell = getEvaluatedCell(model, "A1");
assert.strictEqual(urlRepresentation(cell.link, model.getters), "an odoo view");
openLink(cell.link, env);
@@ -38,7 +38,7 @@ export async function createSpreadsheetWithChart(params = {}) {
insertChartInSpreadsheet(model, params.type);
const env = model.config.external.env;
const env = model.config.custom.env;
env.model = model;
await nextTick();
return { model, env };
@@ -62,7 +62,7 @@ export async function createSpreadsheetWithList(params = {}) {
sheetId: params.sheetId,
});
const env = model.config.external.env;
const env = model.config.custom.env;
env.model = model;
await waitForDataSourcesLoaded(model);
return { model, env };
@@ -16,7 +16,7 @@ const { Model } = spreadsheet;
*/
export function setupDataSourceEvaluation(model) {
model.config.external.dataSources.addEventListener("data-source-updated", () => {
model.config.custom.dataSources.addEventListener("data-source-updated", () => {
const sheetId = model.getters.getActiveSheetId();
model.dispatch("EVALUATE_CELLS", { sheetId });
});
@@ -37,7 +37,7 @@ export async function createModelWithDataSource(params = {}) {
mockRPC: params.mockRPC,
});
const model = new Model(params.spreadsheetData, {
external: {
custom: {
env,
dataSources: new DataSources(env.services.orm.silent),
},
@@ -62,7 +62,7 @@ export async function waitForDataSourcesLoaded(model) {
// Read a first time in order to trigger the RPC
readAllCellsValue();
//@ts-ignore
await model.config.external.dataSources.waitForAllLoaded();
await model.config.custom.dataSources.waitForAllLoaded();
await nextTick();
// Read a second time to trigger the compute format (which could trigger a RPC for currency, in list)
readAllCellsValue();
@@ -32,7 +32,7 @@ export async function insertPivotInSpreadsheet(model, params) {
},
name: "Partner Pivot",
};
const dataSource = model.config.external.dataSources.create(PivotDataSource, definition);
const dataSource = model.config.custom.dataSources.create(PivotDataSource, definition);
await dataSource.load();
const { cols, rows, measures } = dataSource.getTableStructure().export();
const table = {
@@ -68,7 +68,7 @@ export async function createSpreadsheetWithPivot(params = {}) {
});
const arch = params.arch || serverData.views["partner,false,pivot"];
await insertPivotInSpreadsheet(model, { arch });
const env = model.config.external.env;
const env = model.config.custom.env;
env.model = model;
await waitForDataSourcesLoaded(model);
return { model, env };
+1 -1
View File
@@ -21,7 +21,7 @@ export async function mountSpreadsheet(model) {
const app = new App(Spreadsheet, {
props: { model },
templates: templates,
env: model.config.external.env,
env: model.config.custom.env,
test: true,
});
registerCleanup(() => app.destroy());
@@ -11,7 +11,7 @@ const DATA_SOURCE_ID = "ACCOUNTING_AGGREGATES";
export default class AccountingPlugin extends spreadsheet.UIPlugin {
constructor(config) {
super(config);
this.dataSources = config.external.dataSources;
this.dataSources = config.custom.dataSources;
if (this.dataSources) {
this.dataSources.add(DATA_SOURCE_ID, AccountingDataSource);
}
@@ -62,7 +62,7 @@ QUnit.module("spreadsheet_account > Accounting Drill down", { beforeEach }, () =
}
},
});
const env = model.config.external.env;
const env = model.config.custom.env;
env.model = model;
setCellContent(model, "A1", `=ODOO.BALANCE("100", 2020)`);
setCellContent(model, "A2", `=ODOO.BALANCE("100", 0)`);
@@ -211,7 +211,7 @@ export class DashboardLoader {
const model = new Model(
migrate(data),
{
external: { env: this.env, orm: this.orm, dataSources },
custom: { env: this.env, orm: this.orm, dataSources },
mode: "dashboard",
},
revisions