@pipedream/google_slides 0.4.0 → 0.6.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/actions/create-image/create-image.mjs +1 -1
- package/actions/create-page-element/create-page-element.mjs +1 -1
- package/actions/create-presentation/create-presentation.mjs +1 -1
- package/actions/create-slide/create-slide.mjs +1 -1
- package/actions/create-table/create-table.mjs +1 -1
- package/actions/delete-page-element/delete-page-element.mjs +1 -1
- package/actions/delete-slide/delete-slide.mjs +1 -1
- package/actions/delete-table-column/delete-table-column.mjs +1 -1
- package/actions/delete-table-row/delete-table-row.mjs +1 -1
- package/actions/find-presentation/find-presentation.mjs +1 -1
- package/actions/format-paragraph/format-paragraph.mjs +236 -0
- package/actions/format-shape/format-shape.mjs +193 -0
- package/actions/format-slide-background/format-slide-background.mjs +74 -0
- package/actions/format-table-cell/format-table-cell.mjs +162 -0
- package/actions/format-text/format-text.mjs +248 -0
- package/actions/get-presentation/get-presentation.mjs +34 -0
- package/actions/insert-table-columns/insert-table-columns.mjs +1 -1
- package/actions/insert-table-rows/insert-table-rows.mjs +1 -1
- package/actions/insert-text/insert-text.mjs +1 -1
- package/actions/insert-text-into-table/insert-text-into-table.mjs +1 -1
- package/actions/merge-data/merge-data.mjs +1 -1
- package/actions/position-element/position-element.mjs +219 -0
- package/actions/refresh-chart/refresh-chart.mjs +1 -1
- package/actions/replace-all-text/replace-all-text.mjs +1 -1
- package/common/constants.mjs +110 -0
- package/common/utils.mjs +115 -0
- package/google_slides.app.mjs +124 -1
- package/package.json +1 -1
- package/sources/new-presentation/new-presentation.mjs +1 -1
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { ConfigurationError } from "@pipedream/platform";
|
|
2
|
+
import googleSlides from "../../google_slides.app.mjs";
|
|
3
|
+
import {
|
|
4
|
+
EMU, EMU_PER_POINT, POINTS, Z_ORDER_OPERATIONS,
|
|
5
|
+
} from "../../common/constants.mjs";
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
key: "google_slides-position-element",
|
|
9
|
+
name: "Position Element",
|
|
10
|
+
description: "Move, scale, rotate, or restack a page element in a Google Slides presentation. Unspecified properties keep their current values. Use **Get Presentation** to find the element's object ID. [See the documentation](https://developers.google.com/workspace/slides/api/reference/rest/v1/presentations/request#UpdatePageElementTransformRequest)",
|
|
11
|
+
version: "0.0.1",
|
|
12
|
+
annotations: {
|
|
13
|
+
destructiveHint: false,
|
|
14
|
+
openWorldHint: true,
|
|
15
|
+
readOnlyHint: false,
|
|
16
|
+
},
|
|
17
|
+
type: "action",
|
|
18
|
+
props: {
|
|
19
|
+
googleSlides,
|
|
20
|
+
presentationId: {
|
|
21
|
+
propDefinition: [
|
|
22
|
+
googleSlides,
|
|
23
|
+
"staticPresentationId",
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
objectId: {
|
|
27
|
+
propDefinition: [
|
|
28
|
+
googleSlides,
|
|
29
|
+
"pageElementId",
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
translateX: {
|
|
33
|
+
type: "integer",
|
|
34
|
+
label: "X Position",
|
|
35
|
+
description: "Horizontal position in points, measured from the top-left of the slide.",
|
|
36
|
+
optional: true,
|
|
37
|
+
},
|
|
38
|
+
translateY: {
|
|
39
|
+
type: "integer",
|
|
40
|
+
label: "Y Position",
|
|
41
|
+
description: "Vertical position in points, measured from the top-left of the slide.",
|
|
42
|
+
optional: true,
|
|
43
|
+
},
|
|
44
|
+
scaleXPercent: {
|
|
45
|
+
type: "integer",
|
|
46
|
+
label: "Horizontal Scale",
|
|
47
|
+
description: "Horizontal scale as a percentage of the element's intrinsic size (100 = unscaled). Range 1-1000.",
|
|
48
|
+
min: 1,
|
|
49
|
+
max: 1000,
|
|
50
|
+
optional: true,
|
|
51
|
+
},
|
|
52
|
+
scaleYPercent: {
|
|
53
|
+
type: "integer",
|
|
54
|
+
label: "Vertical Scale",
|
|
55
|
+
description: "Vertical scale as a percentage of the element's intrinsic size (100 = unscaled). Range 1-1000.",
|
|
56
|
+
min: 1,
|
|
57
|
+
max: 1000,
|
|
58
|
+
optional: true,
|
|
59
|
+
},
|
|
60
|
+
rotation: {
|
|
61
|
+
type: "integer",
|
|
62
|
+
label: "Rotation",
|
|
63
|
+
description: "Clockwise rotation in degrees (-360 to 360).",
|
|
64
|
+
min: -360,
|
|
65
|
+
max: 360,
|
|
66
|
+
optional: true,
|
|
67
|
+
},
|
|
68
|
+
zOrderOperation: {
|
|
69
|
+
type: "string",
|
|
70
|
+
label: "Z-Order",
|
|
71
|
+
description: "Restack the element relative to the other elements on its slide.",
|
|
72
|
+
options: Z_ORDER_OPERATIONS,
|
|
73
|
+
optional: true,
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
async run({ $ }) {
|
|
77
|
+
const {
|
|
78
|
+
googleSlides,
|
|
79
|
+
presentationId,
|
|
80
|
+
objectId,
|
|
81
|
+
translateX,
|
|
82
|
+
translateY,
|
|
83
|
+
scaleXPercent,
|
|
84
|
+
scaleYPercent,
|
|
85
|
+
rotation,
|
|
86
|
+
zOrderOperation,
|
|
87
|
+
} = this;
|
|
88
|
+
|
|
89
|
+
const movesElement = [
|
|
90
|
+
translateX,
|
|
91
|
+
translateY,
|
|
92
|
+
scaleXPercent,
|
|
93
|
+
scaleYPercent,
|
|
94
|
+
rotation,
|
|
95
|
+
].some((value) => value != null);
|
|
96
|
+
|
|
97
|
+
if (!movesElement && !zOrderOperation) {
|
|
98
|
+
throw new ConfigurationError("Provide at least one of X Position, Y Position, Horizontal Scale, Vertical Scale, Rotation, or Z-Order.");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const id = googleSlides.getPresentationId(presentationId);
|
|
102
|
+
const requests = [];
|
|
103
|
+
|
|
104
|
+
const {
|
|
105
|
+
element, groupId,
|
|
106
|
+
} = await googleSlides.getPageElement(id, objectId);
|
|
107
|
+
|
|
108
|
+
// A grouped child's transform is relative to its group, so the slide-space
|
|
109
|
+
// X/Y props would not mean what they say, and the API rejects grouped IDs
|
|
110
|
+
// for z-order outright.
|
|
111
|
+
if (groupId) {
|
|
112
|
+
throw new ConfigurationError(`Page element "${objectId}" is inside group "${groupId}". Its position is relative to that group, and z-order cannot target a grouped element. Target the group itself, or ungroup it first.`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (movesElement) {
|
|
116
|
+
const current = element.transform || {};
|
|
117
|
+
const unit = current.unit || POINTS;
|
|
118
|
+
const toUnit = (points) => (unit === EMU
|
|
119
|
+
? points * EMU_PER_POINT
|
|
120
|
+
: points);
|
|
121
|
+
|
|
122
|
+
// A move alone leaves the matrix untouched. When scale or rotation is
|
|
123
|
+
// set, the matrix is decomposed so that reflection (carried by the signed
|
|
124
|
+
// scaleY) and shear survive the rebuild.
|
|
125
|
+
let matrix = {
|
|
126
|
+
scaleX: current.scaleX ?? 1,
|
|
127
|
+
scaleY: current.scaleY ?? 1,
|
|
128
|
+
shearX: current.shearX ?? 0,
|
|
129
|
+
shearY: current.shearY ?? 0,
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
if (scaleXPercent != null || scaleYPercent != null || rotation != null) {
|
|
133
|
+
// Factor any reflection onto the axis that already carries it before
|
|
134
|
+
// decomposing, so changing the rotation cannot move a horizontal flip
|
|
135
|
+
// onto the vertical axis and leave the element 180 degrees out.
|
|
136
|
+
const determinant = (matrix.scaleX * matrix.scaleY) - (matrix.shearY * matrix.shearX);
|
|
137
|
+
const flipX = determinant < 0 && matrix.scaleX < 0
|
|
138
|
+
? -1
|
|
139
|
+
: 1;
|
|
140
|
+
const flipY = determinant < 0 && flipX === 1
|
|
141
|
+
? -1
|
|
142
|
+
: 1;
|
|
143
|
+
|
|
144
|
+
const columnX = matrix.scaleX * flipX;
|
|
145
|
+
const columnXShear = matrix.shearY * flipX;
|
|
146
|
+
const columnY = matrix.shearX * flipY;
|
|
147
|
+
const columnYScale = matrix.scaleY * flipY;
|
|
148
|
+
|
|
149
|
+
const currentAngle = Math.atan2(columnXShear, columnX);
|
|
150
|
+
const cos = Math.cos(currentAngle);
|
|
151
|
+
const sin = Math.sin(currentAngle);
|
|
152
|
+
|
|
153
|
+
const currentScaleX = Math.hypot(columnX, columnXShear) || 1;
|
|
154
|
+
const shear = (columnY * cos) + (columnYScale * sin);
|
|
155
|
+
const currentScaleY = (columnYScale * cos) - (columnY * sin);
|
|
156
|
+
|
|
157
|
+
const scaleX = scaleXPercent != null
|
|
158
|
+
? scaleXPercent / 100
|
|
159
|
+
: currentScaleX;
|
|
160
|
+
const scaleY = scaleYPercent != null
|
|
161
|
+
? (scaleYPercent / 100) * (currentScaleY < 0
|
|
162
|
+
? -1
|
|
163
|
+
: 1)
|
|
164
|
+
: currentScaleY;
|
|
165
|
+
const angle = rotation != null
|
|
166
|
+
? rotation * Math.PI / 180
|
|
167
|
+
: currentAngle;
|
|
168
|
+
|
|
169
|
+
const newCos = Math.cos(angle);
|
|
170
|
+
const newSin = Math.sin(angle);
|
|
171
|
+
|
|
172
|
+
matrix = {
|
|
173
|
+
scaleX: scaleX * newCos * flipX,
|
|
174
|
+
shearY: scaleX * newSin * flipX,
|
|
175
|
+
shearX: ((shear * newCos) - (scaleY * newSin)) * flipY,
|
|
176
|
+
scaleY: ((shear * newSin) + (scaleY * newCos)) * flipY,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
requests.push({
|
|
181
|
+
updatePageElementTransform: {
|
|
182
|
+
objectId,
|
|
183
|
+
applyMode: "ABSOLUTE",
|
|
184
|
+
transform: {
|
|
185
|
+
...matrix,
|
|
186
|
+
translateX: translateX != null
|
|
187
|
+
? toUnit(translateX)
|
|
188
|
+
: current.translateX ?? 0,
|
|
189
|
+
translateY: translateY != null
|
|
190
|
+
? toUnit(translateY)
|
|
191
|
+
: current.translateY ?? 0,
|
|
192
|
+
unit,
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (zOrderOperation) {
|
|
199
|
+
requests.push({
|
|
200
|
+
updatePageElementsZOrder: {
|
|
201
|
+
pageElementObjectIds: [
|
|
202
|
+
objectId,
|
|
203
|
+
],
|
|
204
|
+
operation: zOrderOperation,
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
await googleSlides.batchUpdate(id, requests);
|
|
210
|
+
|
|
211
|
+
$.export("$summary", `Repositioned element ${objectId}`);
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
presentationId,
|
|
215
|
+
objectId,
|
|
216
|
+
requestsApplied: requests.map((request) => Object.keys(request)[0]),
|
|
217
|
+
};
|
|
218
|
+
},
|
|
219
|
+
};
|
|
@@ -4,7 +4,7 @@ export default {
|
|
|
4
4
|
key: "google_slides-refresh-chart",
|
|
5
5
|
name: "Refresh a chart",
|
|
6
6
|
description: "Refresh a chart from Sheets. [See the docs here](https://developers.google.com/slides/api/samples/elements#refresh_a_chart_from_sheets)",
|
|
7
|
-
version: "0.0.
|
|
7
|
+
version: "0.0.9",
|
|
8
8
|
annotations: {
|
|
9
9
|
destructiveHint: false,
|
|
10
10
|
openWorldHint: true,
|
|
@@ -4,7 +4,7 @@ export default {
|
|
|
4
4
|
key: "google_slides-replace-all-text",
|
|
5
5
|
name: "Replace All Text",
|
|
6
6
|
description: "Replace all text in a presentation. [See the documentation](https://developers.google.com/workspace/slides/api/reference/rest/v1/presentations/request#ReplaceAllTextRequest)",
|
|
7
|
-
version: "0.0.
|
|
7
|
+
version: "0.0.5",
|
|
8
8
|
annotations: {
|
|
9
9
|
destructiveHint: true,
|
|
10
10
|
openWorldHint: true,
|
package/common/constants.mjs
CHANGED
|
@@ -141,3 +141,113 @@ export const SHAPE_TYPES = [
|
|
|
141
141
|
"ELLIPSE_RIBBON_2",
|
|
142
142
|
"CLOUD_CALLOUT",
|
|
143
143
|
];
|
|
144
|
+
|
|
145
|
+
// Enum values used by the styling requests, checked against the current API
|
|
146
|
+
// reference. https://developers.google.com/workspace/slides/api/reference/rest/v1/presentations/request
|
|
147
|
+
|
|
148
|
+
export const ALIGNMENTS = [
|
|
149
|
+
"START",
|
|
150
|
+
"CENTER",
|
|
151
|
+
"END",
|
|
152
|
+
"JUSTIFIED",
|
|
153
|
+
];
|
|
154
|
+
|
|
155
|
+
export const CONTENT_ALIGNMENTS = [
|
|
156
|
+
"TOP",
|
|
157
|
+
"MIDDLE",
|
|
158
|
+
"BOTTOM",
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
export const DASH_STYLES = [
|
|
162
|
+
"SOLID",
|
|
163
|
+
"DOT",
|
|
164
|
+
"DASH",
|
|
165
|
+
"DASH_DOT",
|
|
166
|
+
"LONG_DASH",
|
|
167
|
+
"LONG_DASH_DOT",
|
|
168
|
+
];
|
|
169
|
+
|
|
170
|
+
export const Z_ORDER_OPERATIONS = [
|
|
171
|
+
"BRING_TO_FRONT",
|
|
172
|
+
"BRING_FORWARD",
|
|
173
|
+
"SEND_BACKWARD",
|
|
174
|
+
"SEND_TO_BACK",
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
// Theme colors resolve against the deck's own ColorScheme, so a caller can style
|
|
178
|
+
// with the presentation's palette instead of hardcoding hex values.
|
|
179
|
+
export const THEME_COLORS = [
|
|
180
|
+
"DARK1",
|
|
181
|
+
"LIGHT1",
|
|
182
|
+
"DARK2",
|
|
183
|
+
"LIGHT2",
|
|
184
|
+
"ACCENT1",
|
|
185
|
+
"ACCENT2",
|
|
186
|
+
"ACCENT3",
|
|
187
|
+
"ACCENT4",
|
|
188
|
+
"ACCENT5",
|
|
189
|
+
"ACCENT6",
|
|
190
|
+
"HYPERLINK",
|
|
191
|
+
"FOLLOWED_HYPERLINK",
|
|
192
|
+
"TEXT1",
|
|
193
|
+
"BACKGROUND1",
|
|
194
|
+
"TEXT2",
|
|
195
|
+
"BACKGROUND2",
|
|
196
|
+
];
|
|
197
|
+
|
|
198
|
+
// Dimension.unit - points, rather than the API's other unit (EMU).
|
|
199
|
+
export const POINTS = "PT";
|
|
200
|
+
export const EMU = "EMU";
|
|
201
|
+
|
|
202
|
+
// 1 inch = 72 pt = 914400 EMU. A transform carries a single unit for all six
|
|
203
|
+
// matrix elements, so caller-supplied points must be converted into whatever
|
|
204
|
+
// unit the element already uses rather than mixed in.
|
|
205
|
+
export const EMU_PER_POINT = 12700;
|
|
206
|
+
|
|
207
|
+
// Range.type. The API expresses the three range shapes as distinct types rather
|
|
208
|
+
// than as optional bounds, and rejects FIXED_RANGE that carries no end index.
|
|
209
|
+
export const TEXT_RANGE_ALL = "ALL";
|
|
210
|
+
export const TEXT_RANGE_FROM_START_INDEX = "FROM_START_INDEX";
|
|
211
|
+
export const TEXT_RANGE_FIXED_RANGE = "FIXED_RANGE";
|
|
212
|
+
|
|
213
|
+
// BulletGlyphPreset. NOTE: these are NOT the same spellings the Google Docs API
|
|
214
|
+
// uses - Slides says DIGIT where Docs says DECIMAL - so they cannot be shared
|
|
215
|
+
// with the google_docs connector.
|
|
216
|
+
export const BULLET_PRESETS = [
|
|
217
|
+
"BULLET_DISC_CIRCLE_SQUARE",
|
|
218
|
+
"BULLET_DIAMONDX_ARROW3D_SQUARE",
|
|
219
|
+
"BULLET_CHECKBOX",
|
|
220
|
+
"BULLET_ARROW_DIAMOND_DISC",
|
|
221
|
+
"BULLET_STAR_CIRCLE_SQUARE",
|
|
222
|
+
"BULLET_ARROW3D_CIRCLE_SQUARE",
|
|
223
|
+
"BULLET_LEFTTRIANGLE_DIAMOND_DISC",
|
|
224
|
+
"BULLET_DIAMONDX_HOLLOWDIAMOND_SQUARE",
|
|
225
|
+
"BULLET_DIAMOND_CIRCLE_SQUARE",
|
|
226
|
+
"NUMBERED_DIGIT_ALPHA_ROMAN",
|
|
227
|
+
"NUMBERED_DIGIT_ALPHA_ROMAN_PARENS",
|
|
228
|
+
"NUMBERED_DIGIT_NESTED",
|
|
229
|
+
"NUMBERED_UPPERALPHA_ALPHA_ROMAN",
|
|
230
|
+
"NUMBERED_UPPERROMAN_UPPERALPHA_DIGIT",
|
|
231
|
+
"NUMBERED_ZERODIGIT_ALPHA_ROMAN",
|
|
232
|
+
];
|
|
233
|
+
|
|
234
|
+
// Sentinel for the Bullets prop meaning "strip existing bullets".
|
|
235
|
+
export const BULLETS_NONE = "NONE";
|
|
236
|
+
|
|
237
|
+
export const BASELINE_OFFSETS = [
|
|
238
|
+
"NONE",
|
|
239
|
+
"SUPERSCRIPT",
|
|
240
|
+
"SUBSCRIPT",
|
|
241
|
+
];
|
|
242
|
+
|
|
243
|
+
export const TEXT_DIRECTIONS = [
|
|
244
|
+
"LEFT_TO_RIGHT",
|
|
245
|
+
"RIGHT_TO_LEFT",
|
|
246
|
+
];
|
|
247
|
+
|
|
248
|
+
export const SPACING_MODES = [
|
|
249
|
+
"NEVER_COLLAPSE",
|
|
250
|
+
"COLLAPSE_LISTS",
|
|
251
|
+
];
|
|
252
|
+
|
|
253
|
+
export const OPACITY_SCALE = 100;
|
package/common/utils.mjs
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { ConfigurationError } from "@pipedream/platform";
|
|
2
|
+
import {
|
|
3
|
+
TEXT_RANGE_ALL,
|
|
4
|
+
TEXT_RANGE_FIXED_RANGE,
|
|
5
|
+
OPACITY_SCALE,
|
|
6
|
+
POINTS,
|
|
7
|
+
TEXT_RANGE_FROM_START_INDEX,
|
|
8
|
+
THEME_COLORS,
|
|
9
|
+
} from "./constants.mjs";
|
|
10
|
+
|
|
11
|
+
function toOpaqueColor(value) {
|
|
12
|
+
const trimmed = String(value).trim();
|
|
13
|
+
const themeColor = trimmed.toUpperCase();
|
|
14
|
+
if (THEME_COLORS.includes(themeColor)) {
|
|
15
|
+
return {
|
|
16
|
+
themeColor,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const hex = trimmed.replace(/^#/, "");
|
|
21
|
+
if (!/^[0-9a-fA-F]{6}$/.test(hex)) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const channel = (start) => parseInt(hex.slice(start, start + 2), 16) / 255;
|
|
25
|
+
return {
|
|
26
|
+
rgbColor: {
|
|
27
|
+
red: channel(0),
|
|
28
|
+
green: channel(2),
|
|
29
|
+
blue: channel(4),
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function toSolidFill(value, opacityPercent) {
|
|
35
|
+
const color = value == null || value === ""
|
|
36
|
+
? null
|
|
37
|
+
: toOpaqueColor(value);
|
|
38
|
+
if (value && !color) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
const fill = {};
|
|
42
|
+
if (color) {
|
|
43
|
+
fill.color = color;
|
|
44
|
+
}
|
|
45
|
+
if (opacityPercent != null) {
|
|
46
|
+
fill.alpha = opacityPercent / OPACITY_SCALE;
|
|
47
|
+
}
|
|
48
|
+
return Object.keys(fill).length
|
|
49
|
+
? fill
|
|
50
|
+
: null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function buildTextRange(startIndex, endIndex) {
|
|
54
|
+
if (startIndex == null && endIndex == null) {
|
|
55
|
+
return {
|
|
56
|
+
type: TEXT_RANGE_ALL,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
if (startIndex == null) {
|
|
60
|
+
throw new ConfigurationError("End Index needs a Start Index. Provide both to style a fixed span, Start Index on its own to style from there to the end, or neither to style all of the text.");
|
|
61
|
+
}
|
|
62
|
+
if (endIndex == null) {
|
|
63
|
+
return {
|
|
64
|
+
type: TEXT_RANGE_FROM_START_INDEX,
|
|
65
|
+
startIndex,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (endIndex <= startIndex) {
|
|
69
|
+
throw new ConfigurationError(`End Index (${endIndex}) must be greater than Start Index (${startIndex}).`);
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
type: TEXT_RANGE_FIXED_RANGE,
|
|
73
|
+
startIndex,
|
|
74
|
+
endIndex,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function styleBuilder() {
|
|
79
|
+
const style = {};
|
|
80
|
+
const fields = [];
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
style,
|
|
84
|
+
fields,
|
|
85
|
+
set(name, value) {
|
|
86
|
+
if (value == null) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
style[name] = value;
|
|
90
|
+
fields.push(name);
|
|
91
|
+
},
|
|
92
|
+
setDimension(name, magnitude, unit = POINTS) {
|
|
93
|
+
if (magnitude == null) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
this.set(name, {
|
|
97
|
+
magnitude,
|
|
98
|
+
unit,
|
|
99
|
+
});
|
|
100
|
+
},
|
|
101
|
+
get isEmpty() {
|
|
102
|
+
return !fields.length;
|
|
103
|
+
},
|
|
104
|
+
get mask() {
|
|
105
|
+
return fields.join(",");
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export default {
|
|
111
|
+
styleBuilder,
|
|
112
|
+
toOpaqueColor,
|
|
113
|
+
toSolidFill,
|
|
114
|
+
buildTextRange,
|
|
115
|
+
};
|
package/google_slides.app.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { ConfigurationError } from "@pipedream/platform";
|
|
1
2
|
import slides from "@googleapis/slides";
|
|
2
3
|
import googleDrive from "@pipedream/google_drive";
|
|
4
|
+
import { CONTENT_ALIGNMENTS } from "./common/constants.mjs";
|
|
3
5
|
|
|
4
6
|
export default {
|
|
5
7
|
...googleDrive,
|
|
@@ -19,6 +21,72 @@ export default {
|
|
|
19
21
|
return this.listPresentationsOptions(driveId, nextPageToken);
|
|
20
22
|
},
|
|
21
23
|
},
|
|
24
|
+
staticPresentationId: {
|
|
25
|
+
type: "string",
|
|
26
|
+
label: "Presentation ID",
|
|
27
|
+
description: "The ID of the presentation. This is the long string in the URL: `https://docs.google.com/presentation/d/{PRESENTATION_ID}/edit`. A full presentation URL is also accepted. Use **Find Presentation** to resolve a name to its ID.",
|
|
28
|
+
},
|
|
29
|
+
staticSlideId: {
|
|
30
|
+
type: "string",
|
|
31
|
+
label: "Slide ID",
|
|
32
|
+
description: "The object ID of the slide (e.g. `p1` or `g1a2b3c4d5`). Use **Get Presentation** and read `slides[].objectId`.",
|
|
33
|
+
},
|
|
34
|
+
pageElementId: {
|
|
35
|
+
type: "string",
|
|
36
|
+
label: "Page Element ID",
|
|
37
|
+
description: "The object ID of the shape, image, table, or other page element to act on. Use **Get Presentation** and read `slides[].pageElements[].objectId`.",
|
|
38
|
+
},
|
|
39
|
+
// Shared by the styling actions; individual actions override the label or
|
|
40
|
+
// description where their wording differs.
|
|
41
|
+
rowIndex: {
|
|
42
|
+
type: "integer",
|
|
43
|
+
label: "Row Index",
|
|
44
|
+
description: "The 0-based row of the target table cell.",
|
|
45
|
+
min: 0,
|
|
46
|
+
optional: true,
|
|
47
|
+
},
|
|
48
|
+
columnIndex: {
|
|
49
|
+
type: "integer",
|
|
50
|
+
label: "Column Index",
|
|
51
|
+
description: "The 0-based column of the target table cell.",
|
|
52
|
+
min: 0,
|
|
53
|
+
optional: true,
|
|
54
|
+
},
|
|
55
|
+
startIndex: {
|
|
56
|
+
type: "integer",
|
|
57
|
+
label: "Start Index",
|
|
58
|
+
description: "Character index to style from, inclusive. On its own, styles from here to the end of the text. Omit both indices to style all of it.",
|
|
59
|
+
min: 0,
|
|
60
|
+
optional: true,
|
|
61
|
+
},
|
|
62
|
+
endIndex: {
|
|
63
|
+
type: "integer",
|
|
64
|
+
label: "End Index",
|
|
65
|
+
description: "Character index to style up to, exclusive. Requires **Start Index**, and must be greater than it.",
|
|
66
|
+
min: 0,
|
|
67
|
+
optional: true,
|
|
68
|
+
},
|
|
69
|
+
backgroundColor: {
|
|
70
|
+
type: "string",
|
|
71
|
+
label: "Background Color",
|
|
72
|
+
description: "A hex code (e.g. `#EEEEEE`) or one of the deck's theme colors (e.g. `ACCENT1`).",
|
|
73
|
+
optional: true,
|
|
74
|
+
},
|
|
75
|
+
backgroundOpacity: {
|
|
76
|
+
type: "integer",
|
|
77
|
+
label: "Background Opacity",
|
|
78
|
+
description: "Opacity as a whole percentage, from `0` (fully transparent) to `100` (fully opaque). Can be set on its own to change an existing fill's opacity without restating its color.",
|
|
79
|
+
min: 0,
|
|
80
|
+
max: 100,
|
|
81
|
+
optional: true,
|
|
82
|
+
},
|
|
83
|
+
contentAlignment: {
|
|
84
|
+
type: "string",
|
|
85
|
+
label: "Content Alignment",
|
|
86
|
+
description: "Vertical alignment of the text within the element. One of `TOP`, `MIDDLE` or `BOTTOM` - e.g. `MIDDLE` to centre the text vertically.",
|
|
87
|
+
options: CONTENT_ALIGNMENTS,
|
|
88
|
+
optional: true,
|
|
89
|
+
},
|
|
22
90
|
layoutId: {
|
|
23
91
|
type: "string",
|
|
24
92
|
label: "Layout ID",
|
|
@@ -251,13 +319,68 @@ export default {
|
|
|
251
319
|
}
|
|
252
320
|
return this.listFilesOptions(pageToken, request);
|
|
253
321
|
},
|
|
254
|
-
|
|
322
|
+
getPresentationId(idOrUrl) {
|
|
323
|
+
const input = String(idOrUrl).trim();
|
|
324
|
+
if (!input) {
|
|
325
|
+
throw new ConfigurationError("Presentation ID is required.");
|
|
326
|
+
}
|
|
327
|
+
// Published presentations use /presentation/d/e/{token}/pub and have no
|
|
328
|
+
// editable ID that presentations.get accepts, so reject them explicitly
|
|
329
|
+
// rather than extracting the literal "e" segment as the ID.
|
|
330
|
+
if (/\/presentation\/d\/e\//.test(input)) {
|
|
331
|
+
throw new ConfigurationError("Published presentation URLs (`/presentation/d/e/...`) are not supported. Provide the presentation's ID or its editable URL (`https://docs.google.com/presentation/d/{ID}/edit`).");
|
|
332
|
+
}
|
|
333
|
+
// Accept a plain presentation ID or a full Slides URL
|
|
334
|
+
// (e.g. https://docs.google.com/presentation/d/{ID}/edit).
|
|
335
|
+
const match = input.match(/\/presentation\/d\/([a-zA-Z0-9-_]+)/);
|
|
336
|
+
return match
|
|
337
|
+
? match[1]
|
|
338
|
+
: input;
|
|
339
|
+
},
|
|
340
|
+
async getPresentation(presentationId, fields) {
|
|
255
341
|
const slides = this.slides();
|
|
256
342
|
const request = {
|
|
257
343
|
presentationId,
|
|
344
|
+
...fields && {
|
|
345
|
+
fields,
|
|
346
|
+
},
|
|
258
347
|
};
|
|
259
348
|
return (await slides.presentations.get(request)).data;
|
|
260
349
|
},
|
|
350
|
+
_findPageElement(presentation, objectId) {
|
|
351
|
+
const walk = (elements, slideId, groupId) => {
|
|
352
|
+
for (const element of elements || []) {
|
|
353
|
+
if (element.objectId === objectId) {
|
|
354
|
+
return {
|
|
355
|
+
element,
|
|
356
|
+
slideId,
|
|
357
|
+
groupId,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
const found = walk(element.elementGroup?.children, slideId, element.objectId);
|
|
361
|
+
if (found) {
|
|
362
|
+
return found;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return null;
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
for (const slide of presentation.slides || []) {
|
|
369
|
+
const found = walk(slide.pageElements, slide.objectId);
|
|
370
|
+
if (found) {
|
|
371
|
+
return found;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return null;
|
|
375
|
+
},
|
|
376
|
+
async getPageElement(presentationId, objectId) {
|
|
377
|
+
const presentation = await this.getPresentation(presentationId);
|
|
378
|
+
const found = this._findPageElement(presentation, objectId);
|
|
379
|
+
if (!found) {
|
|
380
|
+
throw new ConfigurationError(`No page element with ID "${objectId}" was found in presentation ${presentationId}. Use Get Presentation to list slides[].pageElements[].objectId.`);
|
|
381
|
+
}
|
|
382
|
+
return found;
|
|
383
|
+
},
|
|
261
384
|
async getSlide(presentationId, slideId) {
|
|
262
385
|
const slides = this.slides();
|
|
263
386
|
const request = {
|
package/package.json
CHANGED