canva-pptx 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1147 -0
- package/package.json +63 -0
- package/src/AdvancedComponents/accordionList.js +39 -0
- package/src/AdvancedComponents/calendarGrid.js +50 -0
- package/src/AdvancedComponents/certificateFrame.js +35 -0
- package/src/AdvancedComponents/deviceMockup.js +59 -0
- package/src/AdvancedComponents/featureGrid.js +70 -0
- package/src/AdvancedComponents/funnelDiagram.js +49 -0
- package/src/AdvancedComponents/ganttChart.js +35 -0
- package/src/AdvancedComponents/invoiceTable.js +32 -0
- package/src/AdvancedComponents/matrixGrid.js +43 -0
- package/src/AdvancedComponents/mindMap.js +43 -0
- package/src/AdvancedComponents/orgChart.js +76 -0
- package/src/AdvancedComponents/personaCard.js +44 -0
- package/src/AdvancedComponents/processCycle.js +47 -0
- package/src/AdvancedComponents/pyramidHierarchy.js +38 -0
- package/src/AdvancedComponents/saasFeatureBlock.js +38 -0
- package/src/AdvancedComponents/swotMatrix.js +55 -0
- package/src/AdvancedComponents/teamMemberProfile.js +75 -0
- package/src/AdvancedComponents/trafficLight.js +35 -0
- package/src/AdvancedComponents/vennDiagram.js +35 -0
- package/src/AdvancedComponents/winLossChart.js +43 -0
- package/src/components/StatMetric.js +43 -0
- package/src/components/alertBox.js +46 -0
- package/src/components/avatarGroup.js +56 -0
- package/src/components/badge.js +40 -0
- package/src/components/breadcrumbNav.js +31 -0
- package/src/components/browserWindow.js +86 -0
- package/src/components/brushStroke.js +23 -0
- package/src/components/callToAction.js +27 -0
- package/src/components/card.js +64 -0
- package/src/components/chart.js +19 -0
- package/src/components/codeBlock.js +40 -0
- package/src/components/codeDiff.js +51 -0
- package/src/components/comparisonTable.js +43 -0
- package/src/components/cornerAccent.js +32 -0
- package/src/components/dotPattern.js +30 -0
- package/src/components/geometricConfetti.js +34 -0
- package/src/components/gradientMesh.js +32 -0
- package/src/components/iconList.js +43 -0
- package/src/components/image.js +11 -0
- package/src/components/kanbanColumn.js +38 -0
- package/src/components/link.js +23 -0
- package/src/components/organicBlob.js +45 -0
- package/src/components/pricingColumn.js +53 -0
- package/src/components/progressBar.js +55 -0
- package/src/components/ratingStars.js +25 -0
- package/src/components/shape.js +12 -0
- package/src/components/slide.js +5 -0
- package/src/components/socialBar.js +59 -0
- package/src/components/squiggleLine.js +26 -0
- package/src/components/stepProcess.js +39 -0
- package/src/components/table.js +23 -0
- package/src/components/tagCloud.js +39 -0
- package/src/components/testimonialCard.js +54 -0
- package/src/components/text.js +14 -0
- package/src/components/theme.js +7 -0
- package/src/components/timeline.js +73 -0
- package/src/components/waveDecoration.js +35 -0
- package/src/core/PPTManager.js +17 -0
- package/src/index.js +225 -0
- package/src/layout/bento.js +47 -0
- package/src/layout/checkerboard.js +36 -0
- package/src/layout/filmStrip.js +29 -0
- package/src/layout/gallery.js +50 -0
- package/src/layout/grid.js +37 -0
- package/src/layout/hero.js +30 -0
- package/src/layout/magazine.js +39 -0
- package/src/layout/radial.js +34 -0
- package/src/layout/sidebar.js +26 -0
- package/src/layout/splitScreen.js +36 -0
- package/src/layout/zPattern.js +29 -0
- package/src/system/animation.js +36 -0
- package/src/system/contrastChecker.js +35 -0
- package/src/system/dataAdapter.js +36 -0
- package/src/system/layoutDebugger.js +40 -0
- package/src/system/markdownEngine.js +45 -0
- package/src/system/masterOverlay.js +50 -0
- package/src/system/sectionDivider.js +41 -0
- package/src/system/smartIcon.js +33 -0
- package/src/system/smartText.js +44 -0
- package/src/system/speakerNotes.js +12 -0
- package/src/system/syntaxHighlighter.js +71 -0
- package/src/system/themeGenerator.js +25 -0
- package/src/system/tocGenerator.js +68 -0
- package/src/system/watermark.js +26 -0
- package/src/themes/index.js +93 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
function addPyramidHierarchy(slide, levels = [], options = {}) {
|
|
2
|
+
const {
|
|
3
|
+
x = 3, y = 1, w = 4, h = 4,
|
|
4
|
+
colors = ["FFCDD2", "EF9A9A", "E57373", "EF5350", "F44336"] // Reds
|
|
5
|
+
} = options;
|
|
6
|
+
|
|
7
|
+
const count = levels.length;
|
|
8
|
+
const layerH = h / count;
|
|
9
|
+
|
|
10
|
+
// Draw from bottom to top so the widest is at the bottom?
|
|
11
|
+
// Standard Pyramids usually have Level 1 at top or bottom depending on context.
|
|
12
|
+
// We'll render Top-Down (Index 0 at top).
|
|
13
|
+
|
|
14
|
+
levels.forEach((level, i) => {
|
|
15
|
+
const curY = y + i * layerH;
|
|
16
|
+
|
|
17
|
+
// Width increases as we go down
|
|
18
|
+
const factor = (i + 1) / count;
|
|
19
|
+
const curW = w * factor;
|
|
20
|
+
const curX = x + (w - curW) / 2;
|
|
21
|
+
|
|
22
|
+
slide.addShape("rect", {
|
|
23
|
+
x: curX,
|
|
24
|
+
y: curY,
|
|
25
|
+
w: curW,
|
|
26
|
+
h: layerH,
|
|
27
|
+
fill: colors[i % colors.length],
|
|
28
|
+
line: { color: "FFFFFF", width: 1 } // Separator
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
slide.addText(level, {
|
|
32
|
+
x: curX, y: curY, w: curW, h: layerH,
|
|
33
|
+
align: "center", valign: "middle", fontSize: 10, bold: true, color: "333333"
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = { addPyramidHierarchy };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
function addSaaSFeatureBlock(slide, title, desc, imagePath, options = {}) {
|
|
2
|
+
const {
|
|
3
|
+
x = 1, y = 1, w = 8, h = 3,
|
|
4
|
+
imagePosition = "left" // or 'right'
|
|
5
|
+
} = options;
|
|
6
|
+
|
|
7
|
+
const halfW = w / 2;
|
|
8
|
+
const imgX = imagePosition === "left" ? x : x + halfW;
|
|
9
|
+
const txtX = imagePosition === "left" ? x + halfW : x;
|
|
10
|
+
|
|
11
|
+
// Image Placeholder
|
|
12
|
+
if (imagePath) {
|
|
13
|
+
slide.addImage({
|
|
14
|
+
path: imagePath,
|
|
15
|
+
x: imgX + 0.2, y: y + 0.2, w: halfW - 0.4, h: h - 0.4,
|
|
16
|
+
sizing: { type: "contain" }
|
|
17
|
+
});
|
|
18
|
+
} else {
|
|
19
|
+
slide.addShape("rect", {
|
|
20
|
+
x: imgX + 0.2, y: y + 0.2, w: halfW - 0.4, h: h - 0.4,
|
|
21
|
+
fill: "EEEEEE"
|
|
22
|
+
});
|
|
23
|
+
slide.addText("Image", { x: imgX, y: y, w: halfW, h, align: "center", valign: "middle" });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Text Content
|
|
27
|
+
slide.addText(title, {
|
|
28
|
+
x: txtX + 0.2, y: y + 0.5, w: halfW - 0.4, h: 0.5,
|
|
29
|
+
bold: true, fontSize: 18, color: "333333"
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
slide.addText(desc, {
|
|
33
|
+
x: txtX + 0.2, y: y + 1.2, w: halfW - 0.4, h: h - 1.2,
|
|
34
|
+
fontSize: 12, color: "666666", valign: "top"
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = { addSaaSFeatureBlock };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
function addSWOTMatrix(slide, data, options = {}) {
|
|
2
|
+
const {
|
|
3
|
+
x = 1, y = 1, w = 8, h = 5,
|
|
4
|
+
gap = 0.1
|
|
5
|
+
} = options;
|
|
6
|
+
|
|
7
|
+
const halfW = (w - gap) / 2;
|
|
8
|
+
const halfH = (h - gap) / 2;
|
|
9
|
+
|
|
10
|
+
// Definitions for quadrants
|
|
11
|
+
const quads = [
|
|
12
|
+
{ type: 'Strengths', ox: 0, oy: 0, color: "E8F5E9", header: "S" },
|
|
13
|
+
{ type: 'Weaknesses', ox: halfW+gap, oy: 0, color: "FFEBEE", header: "W" },
|
|
14
|
+
{ type: 'Opportunities', ox: 0, oy: halfH+gap, color: "E3F2FD", header: "O" },
|
|
15
|
+
{ type: 'Threats', ox: halfW+gap, oy: halfH+gap, color: "FFF3E0", header: "T" }
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
quads.forEach(q => {
|
|
19
|
+
const qX = x + q.ox;
|
|
20
|
+
const qY = y + q.oy;
|
|
21
|
+
const items = data[q.type.toLowerCase()] || [];
|
|
22
|
+
|
|
23
|
+
// Background
|
|
24
|
+
slide.addShape("rect", {
|
|
25
|
+
x: qX, y: qY, w: halfW, h: halfH,
|
|
26
|
+
fill: q.color
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Header Letter (Background Circle)
|
|
30
|
+
slide.addShape("oval", {
|
|
31
|
+
x: qX + 0.2, y: qY + 0.2, w: 0.5, h: 0.5, fill: "FFFFFF"
|
|
32
|
+
});
|
|
33
|
+
// Header Letter (Text)
|
|
34
|
+
slide.addText(q.header, {
|
|
35
|
+
x: qX + 0.2, y: qY + 0.2, w: 0.5, h: 0.5,
|
|
36
|
+
bold: true, align: "center", valign: "middle", fontSize: 14
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Title
|
|
40
|
+
slide.addText(q.type, {
|
|
41
|
+
x: qX + 0.8, y: qY + 0.2, w: halfW - 1, h: 0.5,
|
|
42
|
+
bold: true, fontSize: 14, valign: "middle"
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// List
|
|
46
|
+
if (items.length > 0) {
|
|
47
|
+
slide.addText(items.map(i => `• ${i}`).join("\n"), {
|
|
48
|
+
x: qX + 0.3, y: qY + 0.9, w: halfW - 0.6, h: halfH - 1,
|
|
49
|
+
fontSize: 11, color: "333333", valign: "top"
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = { addSWOTMatrix };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
function addTeamMemberProfile(slide, profile, options = {}) {
|
|
2
|
+
const {
|
|
3
|
+
x = 1, y = 1, w = 2.5, h = 4,
|
|
4
|
+
bgColor = "FFFFFF",
|
|
5
|
+
borderColor = "E0E0E0"
|
|
6
|
+
} = options;
|
|
7
|
+
|
|
8
|
+
// Card Container
|
|
9
|
+
slide.addShape("rect", {
|
|
10
|
+
x, y, w, h,
|
|
11
|
+
fill: bgColor,
|
|
12
|
+
line: { color: borderColor }
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// Avatar Area
|
|
16
|
+
const imgSize = w * 0.5;
|
|
17
|
+
const imgX = x + (w - imgSize) / 2;
|
|
18
|
+
const imgY = y + 0.3;
|
|
19
|
+
|
|
20
|
+
if (profile.image) {
|
|
21
|
+
slide.addImage({
|
|
22
|
+
path: profile.image,
|
|
23
|
+
x: imgX,
|
|
24
|
+
y: imgY,
|
|
25
|
+
w: imgSize,
|
|
26
|
+
h: imgSize,
|
|
27
|
+
sizing: { type: "cover", w: imgSize, h: imgSize },
|
|
28
|
+
rounding: true
|
|
29
|
+
});
|
|
30
|
+
} else {
|
|
31
|
+
slide.addShape("oval", {
|
|
32
|
+
x: imgX, y: imgY, w: imgSize, h: imgSize, fill: "CCCCCC"
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Name
|
|
37
|
+
slide.addText(profile.name, {
|
|
38
|
+
x: x,
|
|
39
|
+
y: imgY + imgSize + 0.2,
|
|
40
|
+
w: w,
|
|
41
|
+
h: 0.4,
|
|
42
|
+
bold: true,
|
|
43
|
+
fontSize: 14,
|
|
44
|
+
align: "center",
|
|
45
|
+
color: "333333"
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Role
|
|
49
|
+
slide.addText(profile.role, {
|
|
50
|
+
x: x,
|
|
51
|
+
y: imgY + imgSize + 0.6,
|
|
52
|
+
w: w,
|
|
53
|
+
h: 0.3,
|
|
54
|
+
fontSize: 11,
|
|
55
|
+
italic: true,
|
|
56
|
+
align: "center",
|
|
57
|
+
color: "007AFF"
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Bio
|
|
61
|
+
if (profile.bio) {
|
|
62
|
+
slide.addText(profile.bio, {
|
|
63
|
+
x: x + 0.2,
|
|
64
|
+
y: imgY + imgSize + 1.0,
|
|
65
|
+
w: w - 0.4,
|
|
66
|
+
h: h - (imgSize + 1.2),
|
|
67
|
+
fontSize: 10,
|
|
68
|
+
align: "center",
|
|
69
|
+
color: "666666",
|
|
70
|
+
valign: "top"
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports = { addTeamMemberProfile };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
function addTrafficLight(slide, status = "green", options = {}) {
|
|
2
|
+
const { x = 1, y = 1, w = 1, h = 3 } = options;
|
|
3
|
+
|
|
4
|
+
// Housing
|
|
5
|
+
slide.addShape("roundRect", {
|
|
6
|
+
x, y, w, h,
|
|
7
|
+
fill: "333333",
|
|
8
|
+
rounding: 0.2
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const lights = [
|
|
12
|
+
{ color: "FF0000", id: "red" },
|
|
13
|
+
{ color: "FFA500", id: "amber" },
|
|
14
|
+
{ color: "00FF00", id: "green" }
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const lightSize = w * 0.6;
|
|
18
|
+
const gap = (h - (lights.length * lightSize)) / 4;
|
|
19
|
+
|
|
20
|
+
lights.forEach((light, i) => {
|
|
21
|
+
const isActive = light.id === status;
|
|
22
|
+
const curY = y + gap + i * (lightSize + gap);
|
|
23
|
+
|
|
24
|
+
slide.addShape("oval", {
|
|
25
|
+
x: x + (w - lightSize) / 2,
|
|
26
|
+
y: curY,
|
|
27
|
+
w: lightSize,
|
|
28
|
+
h: lightSize,
|
|
29
|
+
fill: isActive ? light.color : "#555555", // Bright or Dim
|
|
30
|
+
line: { color: "000000", width: 1 }
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = { addTrafficLight };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
function addVennDiagram(slide, labels = ["A", "B"], options = {}) {
|
|
2
|
+
const {
|
|
3
|
+
x = 2, y = 2, w = 6, h = 3,
|
|
4
|
+
colors = ["FF0000", "0000FF", "00FF00"],
|
|
5
|
+
opacity = 50
|
|
6
|
+
} = options;
|
|
7
|
+
|
|
8
|
+
const circleSize = 2.5;
|
|
9
|
+
const overlap = 0.8;
|
|
10
|
+
|
|
11
|
+
labels.forEach((label, i) => {
|
|
12
|
+
// Arrange horizontally with overlap
|
|
13
|
+
const curX = x + i * (circleSize - overlap);
|
|
14
|
+
|
|
15
|
+
// For 3rd item, maybe center it below?
|
|
16
|
+
// This is a simple linear overlap logic for now.
|
|
17
|
+
const curY = y;
|
|
18
|
+
|
|
19
|
+
slide.addShape("oval", {
|
|
20
|
+
x: curX,
|
|
21
|
+
y: curY,
|
|
22
|
+
w: circleSize,
|
|
23
|
+
h: circleSize,
|
|
24
|
+
fill: { color: colors[i % colors.length], transparency: 100 - opacity },
|
|
25
|
+
line: { color: "FFFFFF", width: 1 }
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
slide.addText(label, {
|
|
29
|
+
x: curX, y: curY + circleSize/2 - 0.2, w: circleSize, h: 0.4,
|
|
30
|
+
align: "center", bold: true, fontSize: 14
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = { addVennDiagram };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
function addWinLossChart(slide, data = [], options = {}) {
|
|
2
|
+
const { x = 1, y = 1, w = 6, h = 3 } = options;
|
|
3
|
+
|
|
4
|
+
// data = [{ label: "Q1", value: 10 }, { label: "Q2", value: -5 }]
|
|
5
|
+
|
|
6
|
+
const maxVal = Math.max(...data.map(d => Math.abs(d.value)));
|
|
7
|
+
const zeroY = y + h / 2; // Center line
|
|
8
|
+
|
|
9
|
+
const barW = (w / data.length) * 0.6;
|
|
10
|
+
const gap = (w / data.length) * 0.4;
|
|
11
|
+
|
|
12
|
+
// Zero Line
|
|
13
|
+
slide.addShape("line", { x, y: zeroY, w, h: 0, line: { color: "333333" } });
|
|
14
|
+
|
|
15
|
+
data.forEach((d, i) => {
|
|
16
|
+
const barH = (Math.abs(d.value) / maxVal) * (h / 2 - 0.2);
|
|
17
|
+
const curX = x + (gap / 2) + i * (barW + gap);
|
|
18
|
+
|
|
19
|
+
let curY;
|
|
20
|
+
let color;
|
|
21
|
+
|
|
22
|
+
if (d.value >= 0) {
|
|
23
|
+
curY = zeroY - barH;
|
|
24
|
+
color = "4CAF50"; // Green
|
|
25
|
+
} else {
|
|
26
|
+
curY = zeroY;
|
|
27
|
+
color = "F44336"; // Red
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
slide.addShape("rect", {
|
|
31
|
+
x: curX, y: curY, w: barW, h: barH,
|
|
32
|
+
fill: color
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Label
|
|
36
|
+
slide.addText(d.label, {
|
|
37
|
+
x: curX, y: zeroY + (d.value >= 0 ? 0.1 : -0.3), w: barW, h: 0.3,
|
|
38
|
+
fontSize: 9, align: "center", color: "FFFFFF" // Text inside? or outside
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = { addWinLossChart };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders a large statistic with a label
|
|
3
|
+
*/
|
|
4
|
+
function addStatMetric(slide, options = {}) {
|
|
5
|
+
const {
|
|
6
|
+
x = 1,
|
|
7
|
+
y = 1,
|
|
8
|
+
w = 3,
|
|
9
|
+
h = 2,
|
|
10
|
+
value = "0",
|
|
11
|
+
label = "Metric",
|
|
12
|
+
valueColor = "000000",
|
|
13
|
+
labelColor = "666666",
|
|
14
|
+
align = "center",
|
|
15
|
+
} = options;
|
|
16
|
+
|
|
17
|
+
// The Big Number
|
|
18
|
+
slide.addText(value, {
|
|
19
|
+
x: x,
|
|
20
|
+
y: y,
|
|
21
|
+
w: w,
|
|
22
|
+
h: h * 0.6, // Takes up top 60%
|
|
23
|
+
fontSize: 48,
|
|
24
|
+
bold: true,
|
|
25
|
+
color: valueColor,
|
|
26
|
+
align: align,
|
|
27
|
+
valign: "bottom", // Align to bottom of its box so it sits near label
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// The Label
|
|
31
|
+
slide.addText(label, {
|
|
32
|
+
x: x,
|
|
33
|
+
y: y + h * 0.6,
|
|
34
|
+
w: w,
|
|
35
|
+
h: h * 0.4, // Takes up bottom 40%
|
|
36
|
+
fontSize: 14,
|
|
37
|
+
color: labelColor,
|
|
38
|
+
align: align,
|
|
39
|
+
valign: "top",
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = { addStatMetric };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
function addAlertBox(slide, text, type = "info", options = {}) {
|
|
2
|
+
const { x = 1, y = 1, w = 8, h = 0.8 } = options;
|
|
3
|
+
|
|
4
|
+
let bg, border, icon;
|
|
5
|
+
switch (type) {
|
|
6
|
+
case "success": bg = "E8F5E9"; border = "4CAF50"; icon = "✓"; break;
|
|
7
|
+
case "warning": bg = "FFF3E0"; border = "FF9800"; icon = "!"; break;
|
|
8
|
+
case "error": bg = "FFEBEE"; border = "F44336"; icon = "✕"; break;
|
|
9
|
+
default: bg = "E3F2FD"; border = "2196F3"; icon = "i"; break;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Box
|
|
13
|
+
slide.addShape("rect", {
|
|
14
|
+
x, y, w, h,
|
|
15
|
+
fill: bg,
|
|
16
|
+
line: { color: border, width: 2 }
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Icon area
|
|
20
|
+
slide.addShape("rect", {
|
|
21
|
+
x, y, w: 0.6, h,
|
|
22
|
+
fill: border // Darker side strip
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
slide.addText(icon, {
|
|
26
|
+
x, y, w: 0.6, h,
|
|
27
|
+
color: "FFFFFF",
|
|
28
|
+
align: "center",
|
|
29
|
+
valign: "middle",
|
|
30
|
+
bold: true,
|
|
31
|
+
fontSize: 18
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Message
|
|
35
|
+
slide.addText(text, {
|
|
36
|
+
x: x + 0.7,
|
|
37
|
+
y,
|
|
38
|
+
w: w - 0.8,
|
|
39
|
+
h,
|
|
40
|
+
color: "333333",
|
|
41
|
+
valign: "middle",
|
|
42
|
+
fontSize: 12
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = { addAlertBox };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
function addAvatarGroup(slide, avatars = [], options = {}) {
|
|
2
|
+
const {
|
|
3
|
+
x = 1,
|
|
4
|
+
y = 1,
|
|
5
|
+
radius = 0.5,
|
|
6
|
+
offset = 0.3, // overlap amount
|
|
7
|
+
borderColor = "FFFFFF",
|
|
8
|
+
} = options;
|
|
9
|
+
|
|
10
|
+
avatars.forEach((avatar, index) => {
|
|
11
|
+
const curX = x + index * (radius - offset);
|
|
12
|
+
|
|
13
|
+
// 1. Circle Border (for the overlap effect)
|
|
14
|
+
slide.addShape("oval", {
|
|
15
|
+
x: curX - 0.02,
|
|
16
|
+
y: y - 0.02,
|
|
17
|
+
w: radius + 0.04,
|
|
18
|
+
h: radius + 0.04,
|
|
19
|
+
fill: borderColor,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// 2. Avatar Fill (Image or Initials)
|
|
23
|
+
if (avatar.image) {
|
|
24
|
+
slide.addImage({
|
|
25
|
+
path: avatar.image,
|
|
26
|
+
x: curX,
|
|
27
|
+
y: y,
|
|
28
|
+
w: radius,
|
|
29
|
+
h: radius,
|
|
30
|
+
sizing: { type: "cover", w: radius, h: radius },
|
|
31
|
+
rounding: true, // makes image circular in some viewers
|
|
32
|
+
});
|
|
33
|
+
} else {
|
|
34
|
+
// Fallback: Colored circle with initials
|
|
35
|
+
slide.addShape("oval", {
|
|
36
|
+
x: curX,
|
|
37
|
+
y: y,
|
|
38
|
+
w: radius,
|
|
39
|
+
h: radius,
|
|
40
|
+
fill: avatar.color || "CCCCCC",
|
|
41
|
+
});
|
|
42
|
+
slide.addText(avatar.initials || "U", {
|
|
43
|
+
x: curX,
|
|
44
|
+
y: y,
|
|
45
|
+
w: radius,
|
|
46
|
+
h: radius,
|
|
47
|
+
align: "center",
|
|
48
|
+
valign: "middle",
|
|
49
|
+
fontSize: 10,
|
|
50
|
+
color: "FFFFFF",
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = { addAvatarGroup };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders a text badge (pill shape)
|
|
3
|
+
*/
|
|
4
|
+
function addBadge(slide, text, options = {}) {
|
|
5
|
+
const {
|
|
6
|
+
x = 1,
|
|
7
|
+
y = 1,
|
|
8
|
+
w = 1.5,
|
|
9
|
+
h = 0.4,
|
|
10
|
+
backgroundColor = "E1F5FE", // Light Blue
|
|
11
|
+
color = "0277BD", // Dark Blue
|
|
12
|
+
fontSize = 10,
|
|
13
|
+
} = options;
|
|
14
|
+
|
|
15
|
+
// Background Pill
|
|
16
|
+
slide.addShape("roundRect", {
|
|
17
|
+
x,
|
|
18
|
+
y,
|
|
19
|
+
w,
|
|
20
|
+
h,
|
|
21
|
+
fill: backgroundColor,
|
|
22
|
+
line: { color: backgroundColor }, // Seamless look
|
|
23
|
+
rounding: 0.5, // Fully rounded
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Text
|
|
27
|
+
slide.addText(text, {
|
|
28
|
+
x,
|
|
29
|
+
y,
|
|
30
|
+
w,
|
|
31
|
+
h,
|
|
32
|
+
fontSize,
|
|
33
|
+
color,
|
|
34
|
+
align: "center",
|
|
35
|
+
valign: "middle",
|
|
36
|
+
bold: true,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = { addBadge };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
function addBreadcrumbNav(slide, path = [], options = {}) {
|
|
2
|
+
const { x = 0.5, y = 0.3, fontSize = 10, color = "888888" } = options;
|
|
3
|
+
|
|
4
|
+
let currentX = x;
|
|
5
|
+
|
|
6
|
+
path.forEach((item, index) => {
|
|
7
|
+
const isLast = index === path.length - 1;
|
|
8
|
+
|
|
9
|
+
// Calculate approximate width (rough estimate: char count * 0.08)
|
|
10
|
+
const textW = item.length * 0.09;
|
|
11
|
+
|
|
12
|
+
slide.addText(item, {
|
|
13
|
+
x: currentX, y, w: textW, h: 0.3,
|
|
14
|
+
fontSize,
|
|
15
|
+
color: isLast ? "000000" : color,
|
|
16
|
+
bold: isLast
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
currentX += textW;
|
|
20
|
+
|
|
21
|
+
if (!isLast) {
|
|
22
|
+
slide.addText(">", {
|
|
23
|
+
x: currentX, y, w: 0.2, h: 0.3,
|
|
24
|
+
fontSize, color, align: "center"
|
|
25
|
+
});
|
|
26
|
+
currentX += 0.2;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = { addBreadcrumbNav };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
function addBrowserWindow(slide, options = {}) {
|
|
2
|
+
const {
|
|
3
|
+
x = 1,
|
|
4
|
+
y = 1,
|
|
5
|
+
w = 6,
|
|
6
|
+
h = 3.5,
|
|
7
|
+
url = "https://example.com",
|
|
8
|
+
imagePath = null, // Optional screenshot to fill the body
|
|
9
|
+
barColor = "E0E0E0",
|
|
10
|
+
borderColor = "D0D0D0"
|
|
11
|
+
} = options;
|
|
12
|
+
|
|
13
|
+
const headerHeight = 0.4;
|
|
14
|
+
const btnSize = 0.12;
|
|
15
|
+
const btnGap = 0.2;
|
|
16
|
+
|
|
17
|
+
// 1. Main Frame (Border)
|
|
18
|
+
slide.addShape("roundRect", {
|
|
19
|
+
x, y, w, h,
|
|
20
|
+
fill: "FFFFFF",
|
|
21
|
+
line: { color: borderColor },
|
|
22
|
+
rounding: 0.1
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// 2. Top Bar Background
|
|
26
|
+
// We mask the top area. For a perfect "top-rounded" rect, we usually just draw over the main rect.
|
|
27
|
+
// PptxGenJS doesn't easily support "top-only" rounding, so we overlay a rect.
|
|
28
|
+
slide.addShape("rect", {
|
|
29
|
+
x, y, w, h: headerHeight,
|
|
30
|
+
fill: barColor,
|
|
31
|
+
rectRadius: 0.1 // Try to match rounding if library supports, or just Rect
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// 3. Traffic Light Buttons (Red, Yellow, Green)
|
|
35
|
+
const colors = ["FF5F56", "FFBD2E", "27C93F"];
|
|
36
|
+
colors.forEach((c, i) => {
|
|
37
|
+
slide.addShape("oval", {
|
|
38
|
+
x: x + 0.15 + i * (btnSize + 0.08),
|
|
39
|
+
y: y + (headerHeight - btnSize) / 2,
|
|
40
|
+
w: btnSize,
|
|
41
|
+
h: btnSize,
|
|
42
|
+
fill: c,
|
|
43
|
+
line: { color: undefined }
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// 4. Address Bar (White pill)
|
|
48
|
+
const addrX = x + 0.8;
|
|
49
|
+
const addrW = w - 1;
|
|
50
|
+
const addrH = headerHeight * 0.6;
|
|
51
|
+
slide.addShape("roundRect", {
|
|
52
|
+
x: addrX,
|
|
53
|
+
y: y + (headerHeight - addrH) / 2,
|
|
54
|
+
w: addrW,
|
|
55
|
+
h: addrH,
|
|
56
|
+
fill: "FFFFFF",
|
|
57
|
+
line: { color: "CCCCCC", width: 1 },
|
|
58
|
+
rounding: 0.5
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// 5. URL Text
|
|
62
|
+
slide.addText(url, {
|
|
63
|
+
x: addrX + 0.1,
|
|
64
|
+
y: y + (headerHeight - addrH) / 2,
|
|
65
|
+
w: addrW - 0.2,
|
|
66
|
+
h: addrH,
|
|
67
|
+
fontSize: 8,
|
|
68
|
+
color: "666666",
|
|
69
|
+
valign: "middle"
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// 6. Content / Screenshot
|
|
73
|
+
if (imagePath) {
|
|
74
|
+
// Render image inside the frame, below the header
|
|
75
|
+
slide.addImage({
|
|
76
|
+
path: imagePath,
|
|
77
|
+
x: x + 0.05,
|
|
78
|
+
y: y + headerHeight + 0.05,
|
|
79
|
+
w: w - 0.1,
|
|
80
|
+
h: h - headerHeight - 0.1,
|
|
81
|
+
sizing: { type: "contain", w: w - 0.1, h: h - headerHeight - 0.1 }
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = { addBrowserWindow };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
function addBrushStroke(slide, options = {}) {
|
|
2
|
+
const {
|
|
3
|
+
x = 1,
|
|
4
|
+
y = 1,
|
|
5
|
+
w = 4,
|
|
6
|
+
h = 0.5,
|
|
7
|
+
color = "FFEB3B", // Highlighter Yellow
|
|
8
|
+
opacity = 50 // Semi-transparent
|
|
9
|
+
} = options;
|
|
10
|
+
|
|
11
|
+
// We use a rounded rect with full rounding to look like a marker stroke
|
|
12
|
+
slide.addShape("rect", {
|
|
13
|
+
x: x,
|
|
14
|
+
y: y,
|
|
15
|
+
w: w,
|
|
16
|
+
h: h,
|
|
17
|
+
fill: { color: color, transparency: 100 - opacity },
|
|
18
|
+
line: { color: undefined },
|
|
19
|
+
rounding: 1 // Max rounding
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = { addBrushStroke };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
function addCallToAction(slide, text, link, options = {}) {
|
|
2
|
+
const {
|
|
3
|
+
x = 4, y = 3, w = 3, h = 0.6,
|
|
4
|
+
color = "007AFF"
|
|
5
|
+
} = options;
|
|
6
|
+
|
|
7
|
+
// Button Shape
|
|
8
|
+
slide.addShape("roundRect", {
|
|
9
|
+
x, y, w, h,
|
|
10
|
+
fill: color,
|
|
11
|
+
line: { color: color },
|
|
12
|
+
rounding: 0.5
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// Text with Link
|
|
16
|
+
slide.addText(text, {
|
|
17
|
+
x, y, w, h,
|
|
18
|
+
color: "FFFFFF",
|
|
19
|
+
bold: true,
|
|
20
|
+
fontSize: 14,
|
|
21
|
+
align: "center",
|
|
22
|
+
valign: "middle",
|
|
23
|
+
hyperlink: { url: link }
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = { addCallToAction };
|