@mcpher/gas-fakes 2.3.11 → 2.3.13
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/README.md +14 -14
- package/gas-fakes.js +1 -0
- package/gf_agent/scripts/builder.js +26 -1
- package/package.json +1 -1
- package/src/cli/lib-manager.js +14 -4
- package/src/cli/setup.js +17 -4
- package/src/services/chartsapp/fakechartsapp.js +6 -1
- package/src/services/driveapp/driveiterators.js +53 -8
- package/src/services/driveapp/fakedriveapp.js +49 -15
- package/src/services/driveapp/fakedrivefile.js +105 -0
- package/src/services/driveapp/fakedrivefolder.js +8 -0
- package/src/services/driveapp/fakedrivemeta.js +68 -16
- package/src/services/driveapp/fakefolderapp.js +19 -6
- package/src/services/enums/chartsenums.js +53 -20
- package/src/services/enums/driveenums.js +1 -0
- package/src/services/slidesapp/fakeautofit.js +23 -15
- package/src/services/slidesapp/fakecolorscheme.js +160 -0
- package/src/services/slidesapp/fakelayout.js +11 -1
- package/src/services/slidesapp/fakemaster.js +10 -0
- package/src/services/slidesapp/fakepresentation.js +27 -0
- package/src/services/slidesapp/fakeslide.js +9 -0
- package/src/services/slidesapp/faketextrange.js +6 -11
- package/src/services/spreadsheetapp/chartenummapping.js +15 -0
- package/src/services/spreadsheetapp/fakebooleancondition.js +119 -0
- package/src/services/spreadsheetapp/fakecellimage.js +42 -0
- package/src/services/spreadsheetapp/fakecellimagebuilder.js +59 -0
- package/src/services/spreadsheetapp/fakeconditionalformatrule.js +55 -0
- package/src/services/spreadsheetapp/fakeconditionalformatrulebuilder.js +330 -0
- package/src/services/spreadsheetapp/fakedevelopermetadata.js +32 -4
- package/src/services/spreadsheetapp/fakedevelopermetadatalocation.js +27 -5
- package/src/services/spreadsheetapp/fakeembeddedchartbuilder.js +155 -21
- package/src/services/spreadsheetapp/fakegradientcondition.js +71 -0
- package/src/services/spreadsheetapp/fakesheet.js +63 -0
- package/src/services/spreadsheetapp/fakesheetrange.js +12 -1
- package/src/services/spreadsheetapp/fakespreadsheet.js +30 -11
- package/src/services/spreadsheetapp/fakespreadsheetapp.js +21 -3
- package/src/services/urlfetchapp/app.js +33 -1
- package/src/support/fileiterators.js +3 -1
- package/src/support/filesharers.js +7 -3
- package/src/support/peeker.js +8 -2
- package/src/support/sheetutils.js +1 -0
- package/src/support/sxdrive.js +26 -15
- package/src/support/syncit.js +4 -6
- package/src/support/workersync/synchronizer.js +24 -4
- package/src/support/workersync/worker.js +13 -2
- package/summarize_advanced.js +0 -69
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Proxies } from '../../support/proxies.js';
|
|
2
2
|
import { Utils } from '../../support/utils.js';
|
|
3
|
+
import { SheetUtils } from '../../support/sheetutils.js';
|
|
3
4
|
import { newFakeSheetRange } from './fakesheetrange.js';
|
|
4
5
|
import { makeGridRange } from './sheetrangehelpers.js';
|
|
5
6
|
|
|
@@ -25,12 +26,27 @@ class FakeDeveloperMetadataLocation {
|
|
|
25
26
|
return null;
|
|
26
27
|
}
|
|
27
28
|
const dr = this.__location.dimensionRange;
|
|
28
|
-
const
|
|
29
|
-
|
|
29
|
+
const startA1 = SheetUtils.columnToLetter(dr.startIndex + 1);
|
|
30
|
+
const endA1 = SheetUtils.columnToLetter(dr.endIndex);
|
|
31
|
+
return this.getSheet().getRange(`${startA1}:${endA1}`);
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
getLocationType() {
|
|
33
|
-
|
|
35
|
+
if (this.__location.locationType) {
|
|
36
|
+
return SpreadsheetApp.DeveloperMetadataLocationType[this.__location.locationType];
|
|
37
|
+
}
|
|
38
|
+
if (this.__location.spreadsheet) {
|
|
39
|
+
return SpreadsheetApp.DeveloperMetadataLocationType.SPREADSHEET;
|
|
40
|
+
}
|
|
41
|
+
if (this.__location.dimensionRange) {
|
|
42
|
+
return this.__location.dimensionRange.dimension === 'COLUMNS' ?
|
|
43
|
+
SpreadsheetApp.DeveloperMetadataLocationType.COLUMN :
|
|
44
|
+
SpreadsheetApp.DeveloperMetadataLocationType.ROW;
|
|
45
|
+
}
|
|
46
|
+
if (this.__location.sheetId !== undefined) {
|
|
47
|
+
return SpreadsheetApp.DeveloperMetadataLocationType.SHEET;
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
34
50
|
}
|
|
35
51
|
|
|
36
52
|
getRow() {
|
|
@@ -38,8 +54,7 @@ class FakeDeveloperMetadataLocation {
|
|
|
38
54
|
return null;
|
|
39
55
|
}
|
|
40
56
|
const dr = this.__location.dimensionRange;
|
|
41
|
-
|
|
42
|
-
return this.getSheet().getRange(dr.startIndex + 1, 1, numRows, this.getSheet().getMaxColumns());
|
|
57
|
+
return this.getSheet().getRange(`${dr.startIndex + 1}:${dr.endIndex}`);
|
|
43
58
|
}
|
|
44
59
|
|
|
45
60
|
getSheet() {
|
|
@@ -48,6 +63,13 @@ class FakeDeveloperMetadataLocation {
|
|
|
48
63
|
return this.__spreadsheet.getSheetById(sheetId);
|
|
49
64
|
}
|
|
50
65
|
|
|
66
|
+
getSpreadsheet() {
|
|
67
|
+
if (this.getLocationType().toString() !== 'SPREADSHEET' || !this.__location.spreadsheet) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
return this.__spreadsheet;
|
|
71
|
+
}
|
|
72
|
+
|
|
51
73
|
toString() {
|
|
52
74
|
return 'DeveloperMetadataLocation';
|
|
53
75
|
}
|
|
@@ -3,6 +3,8 @@ import { signatureArgs } from "../../support/helpers.js";
|
|
|
3
3
|
import { Utils } from "../../support/utils.js";
|
|
4
4
|
import { makeSheetsGridRange } from "./sheetrangehelpers.js";
|
|
5
5
|
import { newFakeEmbeddedChart } from "./fakeembeddedchart.js";
|
|
6
|
+
import { newFakeContainerInfo } from "./fakecontainerinfo.js";
|
|
7
|
+
import { ChartEnumMapping } from "./chartenummapping.js";
|
|
6
8
|
|
|
7
9
|
const { is } = Utils;
|
|
8
10
|
|
|
@@ -23,8 +25,6 @@ export class FakeEmbeddedChartBuilder {
|
|
|
23
25
|
spec: {
|
|
24
26
|
title: "",
|
|
25
27
|
basicChart: {
|
|
26
|
-
chartType: "COLUMN", // Default
|
|
27
|
-
legendPosition: "RIGHT_LEGEND",
|
|
28
28
|
axis: [],
|
|
29
29
|
domains: [],
|
|
30
30
|
series: [],
|
|
@@ -60,17 +60,11 @@ export class FakeEmbeddedChartBuilder {
|
|
|
60
60
|
|
|
61
61
|
const gridRange = makeSheetsGridRange(range);
|
|
62
62
|
|
|
63
|
-
// In Sheets API, ranges are usually mapped to domains or series.
|
|
64
|
-
// Simplifying: if the range has multiple columns, split it into separate sources if it's a series.
|
|
65
|
-
// But for this fake, we'll just encourage the user to use single-column ranges or handle it simply.
|
|
66
|
-
|
|
67
63
|
if (this.__apiChart.spec.basicChart.domains.length === 0) {
|
|
68
64
|
this.__apiChart.spec.basicChart.domains.push({
|
|
69
65
|
domain: { sourceRange: { sources: [gridRange] } },
|
|
70
66
|
});
|
|
71
67
|
} else {
|
|
72
|
-
// Check if gridRange has multiple columns/rows and split if necessary?
|
|
73
|
-
// For now, just add it. The test will be updated to use single column ranges.
|
|
74
68
|
this.__apiChart.spec.basicChart.series.push({
|
|
75
69
|
series: { sourceRange: { sources: [gridRange] } },
|
|
76
70
|
});
|
|
@@ -82,15 +76,10 @@ export class FakeEmbeddedChartBuilder {
|
|
|
82
76
|
const { nargs, matchThrow } = signatureArgs(arguments, "EmbeddedChartBuilder.setChartType");
|
|
83
77
|
if (nargs !== 1) matchThrow();
|
|
84
78
|
|
|
85
|
-
// type is Charts.ChartType enum (which should match API strings in our fakes)
|
|
86
79
|
this.__apiChart.spec.basicChart.chartType = type.toString();
|
|
87
80
|
return this;
|
|
88
81
|
}
|
|
89
82
|
|
|
90
|
-
/**
|
|
91
|
-
* Returns the chart type.
|
|
92
|
-
* @returns {ChartType}
|
|
93
|
-
*/
|
|
94
83
|
getChartType() {
|
|
95
84
|
const spec = this.__apiChart.spec;
|
|
96
85
|
const chartType = Charts.ChartType;
|
|
@@ -125,9 +114,6 @@ export class FakeEmbeddedChartBuilder {
|
|
|
125
114
|
if (option === "title") {
|
|
126
115
|
this.__apiChart.spec.title = value;
|
|
127
116
|
} else {
|
|
128
|
-
// For other options, we might want to store them in a general options object if needed,
|
|
129
|
-
// but for now, we'll just focus on "title" as requested.
|
|
130
|
-
// SpreadsheetApp.EmbeddedChartBuilder.setOption(option, value)
|
|
131
117
|
if (!this.__apiChart.spec.basicChart.options) {
|
|
132
118
|
this.__apiChart.spec.basicChart.options = {};
|
|
133
119
|
}
|
|
@@ -140,14 +126,162 @@ export class FakeEmbeddedChartBuilder {
|
|
|
140
126
|
const { nargs, matchThrow } = signatureArgs(arguments, "EmbeddedChartBuilder.build");
|
|
141
127
|
if (nargs !== 0) matchThrow();
|
|
142
128
|
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
129
|
+
// Perform translation from GAS internal state to Sheets REST API Spec
|
|
130
|
+
const apiChart = JSON.parse(JSON.stringify(this.__apiChart));
|
|
131
|
+
const spec = apiChart.spec;
|
|
132
|
+
const basic = spec.basicChart;
|
|
133
|
+
|
|
134
|
+
// 1. Translate Enums
|
|
135
|
+
if (basic && basic.legendPosition) {
|
|
136
|
+
basic.legendPosition = ChartEnumMapping.Position[basic.legendPosition] || basic.legendPosition;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (spec.hiddenDimensionStrategy) {
|
|
140
|
+
spec.hiddenDimensionStrategy = ChartEnumMapping.ChartHiddenDimensionStrategy[spec.hiddenDimensionStrategy] || spec.hiddenDimensionStrategy;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Translate background color from Hex to RGB object if present
|
|
144
|
+
if (basic && basic.options?.backgroundColor) {
|
|
145
|
+
const hex = basic.options.backgroundColor;
|
|
146
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
147
|
+
if (result) {
|
|
148
|
+
spec.backgroundColorStyle = {
|
|
149
|
+
rgbColor: {
|
|
150
|
+
red: parseInt(result[1], 16) / 255,
|
|
151
|
+
green: parseInt(result[2], 16) / 255,
|
|
152
|
+
blue: parseInt(result[3], 16) / 255
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// 2. Handle Specialized Spec Blocks
|
|
159
|
+
if (basic && basic.chartType === "PIE") {
|
|
160
|
+
spec.pieChart = {
|
|
161
|
+
domain: basic.domains?.[0]?.domain,
|
|
162
|
+
series: basic.series?.[0]?.series,
|
|
163
|
+
threeDimensional: basic.options?.is3D || false,
|
|
164
|
+
pieHole: basic.options?.pieHole || 0,
|
|
165
|
+
};
|
|
166
|
+
if (basic.legendPosition) spec.pieChart.legendPosition = basic.legendPosition;
|
|
167
|
+
delete spec.basicChart;
|
|
168
|
+
} else if (basic && basic.chartType === "HISTOGRAM") {
|
|
169
|
+
spec.histogramChart = {
|
|
170
|
+
series: [
|
|
171
|
+
{
|
|
172
|
+
data: basic.series?.[0]?.series || basic.domains?.[0]?.domain,
|
|
173
|
+
}
|
|
174
|
+
],
|
|
175
|
+
};
|
|
176
|
+
if (basic.legendPosition) spec.histogramChart.legendPosition = basic.legendPosition;
|
|
177
|
+
delete spec.basicChart;
|
|
178
|
+
} else if (basic) {
|
|
179
|
+
// Sheets API quirk: even if basicChart.chartType is set, it often defaults to rendering as a LINE
|
|
180
|
+
// chart unless the series array explicitly mirrors the type.
|
|
181
|
+
basic.series = basic.series || [];
|
|
182
|
+
if (basic.chartType === "COMBO") {
|
|
183
|
+
if (basic.series.length === 0) basic.series.push({ type: "COLUMN" });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
basic.series.forEach(s => {
|
|
187
|
+
if (!s.type) s.type = basic.chartType === "COMBO" ? "COLUMN" : basic.chartType;
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Clean up internal-only 'options' field that the API doesn't recognize
|
|
191
|
+
delete basic.options;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return newFakeEmbeddedChart(apiChart, this.__sheet);
|
|
148
195
|
}
|
|
149
196
|
|
|
197
|
+
// --- Sub-builder coercion methods ---
|
|
198
|
+
asAreaChart() { return this.setChartType("AREA"); }
|
|
199
|
+
asBarChart() { return this.setChartType("BAR"); }
|
|
200
|
+
asColumnChart() { return this.setChartType("COLUMN"); }
|
|
201
|
+
asComboChart() { return this.setChartType("COMBO"); }
|
|
202
|
+
asHistogramChart() { return this.setChartType("HISTOGRAM"); }
|
|
203
|
+
asLineChart() { return this.setChartType("LINE"); }
|
|
204
|
+
asPieChart() { return this.setChartType("PIE"); }
|
|
205
|
+
asScatterChart() { return this.setChartType("SCATTER"); }
|
|
206
|
+
asTableChart() { return this.setChartType("TABLE"); }
|
|
207
|
+
|
|
208
|
+
// --- Common Builder Methods ---
|
|
209
|
+
clearRanges() {
|
|
210
|
+
this.__apiChart.spec.basicChart.domains = [];
|
|
211
|
+
this.__apiChart.spec.basicChart.series = [];
|
|
212
|
+
return this;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
getContainer() {
|
|
216
|
+
return newFakeContainerInfo(this.__apiChart.position.overlayPosition || {});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
getRanges() {
|
|
220
|
+
// In gas-fakes, we don't deeply parse and unwrap ranges back from the API spec for this fake, returning empty array.
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
removeRange(range) { return this; }
|
|
225
|
+
|
|
226
|
+
setHiddenDimensionStrategy(strategy) {
|
|
227
|
+
this.__apiChart.spec.hiddenDimensionStrategy = strategy ? strategy.toString() : "IGNORE_ROWS";
|
|
228
|
+
return this;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
setMergeStrategy(strategy) { return this; }
|
|
232
|
+
setNumHeaders(headers) { return this; }
|
|
233
|
+
setTransposeRowsAndColumns(transpose) { return this; }
|
|
234
|
+
|
|
235
|
+
// --- Chart Specific Builder Methods ---
|
|
236
|
+
reverseCategories() { return this; }
|
|
237
|
+
setBackgroundColor(color) { return this.setOption("backgroundColor", color); }
|
|
238
|
+
setColors(colors) { return this; }
|
|
239
|
+
setLegendPosition(position) {
|
|
240
|
+
this.__apiChart.spec.basicChart.legendPosition = position ? position.toString() : "RIGHT_LEGEND";
|
|
241
|
+
return this;
|
|
242
|
+
}
|
|
243
|
+
setLegendTextStyle(textStyle) { return this; }
|
|
244
|
+
setPointStyle(pointStyle) { return this; }
|
|
245
|
+
setRange(min, max) { return this; }
|
|
246
|
+
setStacked() { return this; }
|
|
247
|
+
setTitle(title) {
|
|
248
|
+
this.__apiChart.spec.title = title;
|
|
249
|
+
return this;
|
|
250
|
+
}
|
|
251
|
+
setTitleTextStyle(textStyle) { return this; }
|
|
252
|
+
setXAxisTextStyle(textStyle) { return this; }
|
|
253
|
+
setXAxisTitle(title) { return this; }
|
|
254
|
+
setXAxisTitleTextStyle(textStyle) { return this; }
|
|
255
|
+
setYAxisTextStyle(textStyle) { return this; }
|
|
256
|
+
setYAxisTitle(title) { return this; }
|
|
257
|
+
setYAxisTitleTextStyle(textStyle) { return this; }
|
|
258
|
+
useLogScale() { return this; }
|
|
259
|
+
set3D() { return this.setOption("is3D", true); }
|
|
260
|
+
enablePaging(enable, pageSize) { return this; }
|
|
261
|
+
enableRtlTable(enable) { return this; }
|
|
262
|
+
enableSorting(enable) { return this; }
|
|
263
|
+
setFirstRowNumber(number) { return this; }
|
|
264
|
+
setInitialSortingAscending(column) { return this; }
|
|
265
|
+
setInitialSortingDescending(column) { return this; }
|
|
266
|
+
showRowNumberColumn(show) { return this; }
|
|
267
|
+
useAlternatingRowStyle(use) { return this; }
|
|
268
|
+
setXAxisLogScale() { return this; }
|
|
269
|
+
setXAxisRange(min, max) { return this; }
|
|
270
|
+
setYAxisLogScale() { return this; }
|
|
271
|
+
setYAxisRange(min, max) { return this; }
|
|
272
|
+
reverseDirection() { return this; }
|
|
273
|
+
|
|
150
274
|
toString() {
|
|
275
|
+
const type = this.__apiChart.spec.basicChart?.chartType;
|
|
276
|
+
if (type) {
|
|
277
|
+
// The API uses string constants like "COLUMN", "PIE", "SCATTER".
|
|
278
|
+
// We convert them to CamelCase format like "EmbeddedPieChartBuilder".
|
|
279
|
+
const formattedType = type.charAt(0).toUpperCase() + type.slice(1).toLowerCase();
|
|
280
|
+
// Handle the underscore in STEPPED_AREA
|
|
281
|
+
const finalType = formattedType.replace(/_([a-z])/g, function (g) { return g[1].toUpperCase(); });
|
|
282
|
+
return `Embedded${finalType}ChartBuilder`;
|
|
283
|
+
}
|
|
284
|
+
|
|
151
285
|
const hex = Math.floor(Math.random() * 0xffffffff).toString(16).padStart(8, "0");
|
|
152
286
|
return `com.google.apps.maestro.server.beans.trix.impl.ChartPropertyApiEmbeddedChartBuilder@${hex}`;
|
|
153
287
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Proxies } from '../../support/proxies.js';
|
|
2
|
+
import { makeColorFromApi } from '../common/fakecolorbuilder.js';
|
|
3
|
+
import { InterpolationType } from '../enums/sheetsenums.js';
|
|
4
|
+
|
|
5
|
+
export const newFakeGradientCondition = (...args) => {
|
|
6
|
+
return Proxies.guard(new FakeGradientCondition(...args));
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export class FakeGradientCondition {
|
|
10
|
+
constructor(apiGradient) {
|
|
11
|
+
this.__apiGradient = apiGradient || {};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
__getColor(point) {
|
|
15
|
+
if (!point) return null;
|
|
16
|
+
if (point.colorStyle) return makeColorFromApi(point.colorStyle);
|
|
17
|
+
if (point.color) return makeColorFromApi({ rgbColor: point.color });
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
__getType(point) {
|
|
22
|
+
if (!point || !point.type) return null;
|
|
23
|
+
return InterpolationType[point.type] || null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
__getValue(point) {
|
|
27
|
+
if (!point) return "";
|
|
28
|
+
if (point.type === 'MIN' || point.type === 'MAX') return "";
|
|
29
|
+
return point.value !== undefined ? point.value : "";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
getMaxColorObject() {
|
|
33
|
+
return this.__getColor(this.__apiGradient.maxpoint);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
getMaxType() {
|
|
37
|
+
return this.__getType(this.__apiGradient.maxpoint);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
getMaxValue() {
|
|
41
|
+
return this.__getValue(this.__apiGradient.maxpoint);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
getMidColorObject() {
|
|
45
|
+
return this.__getColor(this.__apiGradient.midpoint);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
getMidType() {
|
|
49
|
+
return this.__getType(this.__apiGradient.midpoint);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
getMidValue() {
|
|
53
|
+
return this.__getValue(this.__apiGradient.midpoint);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
getMinColorObject() {
|
|
57
|
+
return this.__getColor(this.__apiGradient.minpoint);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
getMinType() {
|
|
61
|
+
return this.__getType(this.__apiGradient.minpoint);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
getMinValue() {
|
|
65
|
+
return this.__getValue(this.__apiGradient.minpoint);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
toString() {
|
|
69
|
+
return 'GradientCondition';
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -5,6 +5,7 @@ import { Utils } from "../../support/utils.js";
|
|
|
5
5
|
import { SheetUtils } from "../../support/sheetutils.js";
|
|
6
6
|
import { batchUpdate, makeSheetsGridRange } from "./sheetrangehelpers.js";
|
|
7
7
|
import { newFakeFilter } from "./fakefilter.js";
|
|
8
|
+
import { newFakeConditionalFormatRule } from "./fakeconditionalformatrule.js";
|
|
8
9
|
import { newFakePivotTable } from "./fakepivottable.js";
|
|
9
10
|
import { newFakeBanding } from "./fakebanding.js";
|
|
10
11
|
import { newFakeDeveloperMetadataFinder } from "./fakedevelopermetadatafinder.js";
|
|
@@ -378,6 +379,68 @@ export class FakeSheet {
|
|
|
378
379
|
: null;
|
|
379
380
|
}
|
|
380
381
|
|
|
382
|
+
/**
|
|
383
|
+
* clearConditionalFormatRules() https://developers.google.com/apps-script/reference/spreadsheet/sheet#clearconditionalformatrules
|
|
384
|
+
* Removes all conditional format rules from the sheet. Equivalent to calling setConditionalFormatRules(rules) with an empty array as input.
|
|
385
|
+
*/
|
|
386
|
+
clearConditionalFormatRules() {
|
|
387
|
+
this.setConditionalFormatRules([]);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* getConditionalFormatRules() https://developers.google.com/apps-script/reference/spreadsheet/sheet#getconditionalformatrules
|
|
392
|
+
* Get all conditional format rules in this sheet.
|
|
393
|
+
* @returns {FakeConditionalFormatRule[]} An array of all rules in the sheet.
|
|
394
|
+
*/
|
|
395
|
+
getConditionalFormatRules() {
|
|
396
|
+
const meta = this.getParent().__getMetaProps(
|
|
397
|
+
`sheets(conditionalFormats,properties.sheetId)`
|
|
398
|
+
);
|
|
399
|
+
const sheetMeta = meta.sheets.find(
|
|
400
|
+
(s) => s.properties.sheetId === this.getSheetId()
|
|
401
|
+
);
|
|
402
|
+
const rules = sheetMeta?.conditionalFormats || [];
|
|
403
|
+
return rules.map(rule => newFakeConditionalFormatRule(rule, this));
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* setConditionalFormatRules(rules) https://developers.google.com/apps-script/reference/spreadsheet/sheet#setconditionalformatrulesrules
|
|
408
|
+
* Replaces all currently existing conditional format rules in the sheet with the input rules. Rules are evaluated in their input order.
|
|
409
|
+
* @param {FakeConditionalFormatRule[]} rules The array of conditional format rules to set.
|
|
410
|
+
*/
|
|
411
|
+
setConditionalFormatRules(rules) {
|
|
412
|
+
const { nargs, matchThrow } = signatureArgs(arguments, "Sheet.setConditionalFormatRules");
|
|
413
|
+
if (nargs !== 1 || !Array.isArray(rules)) matchThrow();
|
|
414
|
+
|
|
415
|
+
const requests = [];
|
|
416
|
+
|
|
417
|
+
// Delete existing rules
|
|
418
|
+
const existingRules = this.getConditionalFormatRules();
|
|
419
|
+
const existingCount = existingRules.length;
|
|
420
|
+
for (let i = existingCount - 1; i >= 0; i--) {
|
|
421
|
+
requests.push({
|
|
422
|
+
deleteConditionalFormatRule: {
|
|
423
|
+
sheetId: this.getSheetId(),
|
|
424
|
+
index: i
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Add new rules
|
|
430
|
+
rules.forEach((rule, index) => {
|
|
431
|
+
requests.push({
|
|
432
|
+
addConditionalFormatRule: {
|
|
433
|
+
rule: rule.__apiRule,
|
|
434
|
+
index: index
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
if (requests.length > 0) {
|
|
440
|
+
Sheets.Spreadsheets.batchUpdate({ requests: requests }, this.__parent.getId());
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
381
444
|
getPivotTables() {
|
|
382
445
|
const meta = this.getParent().__getMetaProps(
|
|
383
446
|
`sheets(data(rowData(values(pivotTable))),properties.sheetId)`
|
|
@@ -2611,6 +2611,17 @@ skipFilteredRows Boolean Whether to avoid clearing filtered rows.
|
|
|
2611
2611
|
// options = { valueInputOption: "RAW" },
|
|
2612
2612
|
options = { valueInputOption: "USER_ENTERED" },
|
|
2613
2613
|
}) {
|
|
2614
|
+
// Intercept FakeCellImage objects and convert to =IMAGE(url) formula
|
|
2615
|
+
const processedValues = values.map(row =>
|
|
2616
|
+
row.map(cell => {
|
|
2617
|
+
if (cell && typeof cell === 'object' && cell.toString() === 'CellImage') {
|
|
2618
|
+
const url = cell._properties?.sourceUrl || null;
|
|
2619
|
+
return url ? `=IMAGE("${url}")` : "";
|
|
2620
|
+
}
|
|
2621
|
+
return cell;
|
|
2622
|
+
})
|
|
2623
|
+
);
|
|
2624
|
+
|
|
2614
2625
|
const range = single
|
|
2615
2626
|
? this.__getRangeWithSheet(this.__getTopLeft())
|
|
2616
2627
|
: this.__getWithSheet();
|
|
@@ -2620,7 +2631,7 @@ skipFilteredRows Boolean Whether to avoid clearing filtered rows.
|
|
|
2620
2631
|
{
|
|
2621
2632
|
majorDimension: "ROWS",
|
|
2622
2633
|
range,
|
|
2623
|
-
values,
|
|
2634
|
+
values: processedValues,
|
|
2624
2635
|
},
|
|
2625
2636
|
],
|
|
2626
2637
|
};
|
|
@@ -152,17 +152,6 @@ export class FakeSpreadsheet {
|
|
|
152
152
|
"deleteColumn",
|
|
153
153
|
"getImages",
|
|
154
154
|
"find",
|
|
155
|
-
"sort",
|
|
156
|
-
"addEditor",
|
|
157
|
-
"addEditors",
|
|
158
|
-
"addViewers",
|
|
159
|
-
"addViewer",
|
|
160
|
-
"removeViewer",
|
|
161
|
-
"removeEditor",
|
|
162
|
-
|
|
163
|
-
// these convert to a pdf so we'll need to figure out how to do that
|
|
164
|
-
// probably using the Drive export
|
|
165
|
-
"getAs",
|
|
166
155
|
"getBlob",
|
|
167
156
|
];
|
|
168
157
|
|
|
@@ -181,6 +170,36 @@ export class FakeSpreadsheet {
|
|
|
181
170
|
return this.__getFirstSheet();
|
|
182
171
|
}
|
|
183
172
|
|
|
173
|
+
addViewer(emailAddress) {
|
|
174
|
+
this.__file.addViewer(emailAddress);
|
|
175
|
+
return this;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
addEditor(emailAddress) {
|
|
179
|
+
this.__file.addEditor(emailAddress);
|
|
180
|
+
return this;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
addViewers(emailAddresses) {
|
|
184
|
+
this.__file.addViewers(emailAddresses);
|
|
185
|
+
return this;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
addEditors(emailAddresses) {
|
|
189
|
+
this.__file.addEditors(emailAddresses);
|
|
190
|
+
return this;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
removeViewer(emailAddress) {
|
|
194
|
+
this.__file.removeViewer(emailAddress);
|
|
195
|
+
return this;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
removeEditor(emailAddress) {
|
|
199
|
+
this.__file.removeEditor(emailAddress);
|
|
200
|
+
return this;
|
|
201
|
+
}
|
|
202
|
+
|
|
184
203
|
|
|
185
204
|
addDeveloperMetadata(key, value, visibility) {
|
|
186
205
|
const { nargs, matchThrow } = signatureArgs(
|
|
@@ -12,8 +12,10 @@ import { newFakeRichTextValueBuilder } from "../common/fakerichtextvalue.js";
|
|
|
12
12
|
import { newFakeTextStyleBuilder } from "../common/faketextstylebuilder.js";
|
|
13
13
|
import { newFakeFilterCriteriaBuilder } from "./fakefiltercriteriabuilder.js";
|
|
14
14
|
import { newFakeDataValidationBuilder } from "./fakedatavalidationbuilder.js";
|
|
15
|
+
import { newFakeConditionalFormatRuleBuilder } from "./fakeconditionalformatrulebuilder.js";
|
|
15
16
|
import { newFakeDataSourceSpecBuilder } from "./fakedatasourcespecbuilder.js";
|
|
16
|
-
import {
|
|
17
|
+
import { newFakeTextFinder } from "./faketextfinder.js";
|
|
18
|
+
import { newFakeCellImageBuilder } from "./fakecellimagebuilder.js";
|
|
17
19
|
|
|
18
20
|
import * as Enums from "../enums/sheetsenums.js";
|
|
19
21
|
|
|
@@ -79,7 +81,6 @@ export class FakeSpreadsheetApp {
|
|
|
79
81
|
|
|
80
82
|
const props = [
|
|
81
83
|
"getActive",
|
|
82
|
-
"newConditionalFormatRule",
|
|
83
84
|
"getActiveSheet",
|
|
84
85
|
"getCurrentCell",
|
|
85
86
|
"getActiveRange",
|
|
@@ -89,7 +90,6 @@ export class FakeSpreadsheetApp {
|
|
|
89
90
|
"setCurrentCell",
|
|
90
91
|
"setActiveRange",
|
|
91
92
|
"setActiveRangeList",
|
|
92
|
-
"newCellImage",
|
|
93
93
|
"getUi",
|
|
94
94
|
"open",
|
|
95
95
|
|
|
@@ -228,6 +228,15 @@ export class FakeSpreadsheetApp {
|
|
|
228
228
|
return this.openById(match[1]);
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
/**
|
|
232
|
+
* newCellImage() https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet-app#newcellimage
|
|
233
|
+
* Creates a builder for a CellImage.
|
|
234
|
+
* @returns {FakeCellImageBuilder}
|
|
235
|
+
*/
|
|
236
|
+
newCellImage() {
|
|
237
|
+
return newFakeCellImageBuilder();
|
|
238
|
+
}
|
|
239
|
+
|
|
231
240
|
/**
|
|
232
241
|
* newColor() https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet-app#newcolor
|
|
233
242
|
* Creates a builder for a Color.
|
|
@@ -237,6 +246,15 @@ export class FakeSpreadsheetApp {
|
|
|
237
246
|
return newFakeColorBuilder();
|
|
238
247
|
}
|
|
239
248
|
|
|
249
|
+
/**
|
|
250
|
+
* newConditionalFormatRule() https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet-app#newconditionalformatrule
|
|
251
|
+
* Creates a builder for a conditional formatting rule.
|
|
252
|
+
* @returns {FakeConditionalFormatRuleBuilder}
|
|
253
|
+
*/
|
|
254
|
+
newConditionalFormatRule() {
|
|
255
|
+
return newFakeConditionalFormatRuleBuilder();
|
|
256
|
+
}
|
|
257
|
+
|
|
240
258
|
/**
|
|
241
259
|
* newTextStyle() https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet-app#newTextStyle()
|
|
242
260
|
* Creates a builder for a TextStyle.
|
|
@@ -116,6 +116,37 @@ const fetchAll = (requests) => {
|
|
|
116
116
|
return responses.map(({ data }) => responsify(data))
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
const getRequest = (url, options = {}) => {
|
|
120
|
+
const defaultMethod = 'get';
|
|
121
|
+
const request = {
|
|
122
|
+
url: url,
|
|
123
|
+
method: (options.method || defaultMethod).toLowerCase(),
|
|
124
|
+
headers: options.headers || {},
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// Apps Script defaults
|
|
128
|
+
if (options.contentType) {
|
|
129
|
+
request.contentType = options.contentType;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (options.payload) {
|
|
133
|
+
request.payload = options.payload;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (options.useIntranet !== undefined) {
|
|
137
|
+
request.useIntranet = options.useIntranet;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Add standard fetch behavior
|
|
141
|
+
if (request.method === 'post' || request.method === 'put' || request.method === 'patch') {
|
|
142
|
+
if (!request.contentType) {
|
|
143
|
+
request.contentType = 'application/x-www-form-urlencoded';
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return request;
|
|
148
|
+
}
|
|
149
|
+
|
|
119
150
|
|
|
120
151
|
// This will eventually hold a proxy for DriveApp
|
|
121
152
|
let _app = null
|
|
@@ -131,7 +162,8 @@ if (typeof globalThis[name] === typeof undefined) {
|
|
|
131
162
|
if (!_app) {
|
|
132
163
|
_app = {
|
|
133
164
|
fetch,
|
|
134
|
-
fetchAll
|
|
165
|
+
fetchAll,
|
|
166
|
+
getRequest
|
|
135
167
|
}
|
|
136
168
|
}
|
|
137
169
|
// this is the actual driveApp we'll return from the proxy
|
|
@@ -31,7 +31,9 @@ export const getPermissionIterator = ({
|
|
|
31
31
|
pageToken = nextPageToken
|
|
32
32
|
|
|
33
33
|
// format the results into the folder or file object
|
|
34
|
-
|
|
34
|
+
// we must copy the array because the cache returns a reference
|
|
35
|
+
// and splicing it would empty the cache for subsequent calls
|
|
36
|
+
tank = [...permissions]
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
// if we've got anything in the tank send back the oldest one
|
|
@@ -4,14 +4,18 @@ import { newFakeUser } from '../services/common/fakeuser.js';
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* get the file sharers
|
|
7
|
-
* @
|
|
7
|
+
* @param {string} id the file id
|
|
8
|
+
* @param {string|string[]} roles the role or roles to filter by
|
|
9
|
+
* @returns {FakeUser[]} the file viewers
|
|
8
10
|
*/
|
|
9
|
-
export const getSharers = (id,
|
|
11
|
+
export const getSharers = (id, roles) => {
|
|
12
|
+
const roleList = Array.isArray(roles) ? roles : [roles]
|
|
10
13
|
const pit = getPermissionIterator({ id })
|
|
11
14
|
const viewers = []
|
|
12
15
|
while (pit.hasNext()) {
|
|
13
16
|
const permission = pit.next()
|
|
14
|
-
|
|
17
|
+
// console.log(`...DEBUG: permission for ${id}:`, JSON.stringify(permission))
|
|
18
|
+
if (roleList.includes(permission.role) && permission.type === "user") viewers.push(makeUserFromPermission(permission))
|
|
15
19
|
}
|
|
16
20
|
return viewers
|
|
17
21
|
}
|