puppeteer removed in ipc & used Font displayed

This commit is contained in:
shiva raj badu 2025-05-14 23:36:05 +05:45
parent 2f432aceea
commit cc76b9cea4
8 changed files with 297 additions and 403 deletions

View file

@ -1,8 +1,7 @@
import { ipcMain } from "electron";
import puppeteer from "puppeteer";
import { BrowserWindow, ipcMain } from "electron";
import fs from 'fs';
import path from 'path';
import { baseDir, isDev, tempDir } from "../utils/constants";
import { tempDir } from "../utils/constants";
interface Position {
left: number;
top: number;
@ -80,255 +79,271 @@ interface GraphElement {
type SlideElement = TextElement | PictureElement | BoxElement | LineElement | GraphElement;
interface SlideMetadata {
slideIndex: number;
backgroundColor: string;
elements: SlideElement[];
}
export function setupSlideMetadataHandlers() {
ipcMain.handle("get-slide-metadata", async (_, url: string, theme: string, customColors?: any) => {
console.log("get-slide-metadata", process.env.NEXT_PUBLIC_FAST_API, url);
let browser;
let win:BrowserWindow | null = null;
try {
browser = await puppeteer.launch({
headless: true,
executablePath: isDev ? undefined : path.join(baseDir, "dependencies/chrome-headless-shell/linux_build/chrome-headless-shell"),
args: ['--no-sandbox', '--disable-setuid-sandbox']
});
win = new BrowserWindow({
width: 1920,
height: 1080,
webPreferences: {
webSecurity: false,
preload: path.join(__dirname, '../preloads/index.js'),
},
show: false,
});
const page = await browser.newPage();
await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
// Inject the environment variables before loading the page
await page.evaluateOnNewDocument(`
window.env = {
NEXT_PUBLIC_FAST_API: "${process.env.NEXT_PUBLIC_FAST_API}",
};
`);
await page.goto(url, { waitUntil: "networkidle0", timeout: 60000 });
await page.waitForSelector('[data-element-type="slide-container"]', { timeout: 80000 });
// Apply theme
await page.evaluate((params: any) => {
const { theme, customColors } = params;
document.querySelectorAll(".slide-theme").forEach((container) => {
container.setAttribute("data-theme", theme);
await win.loadURL(url,{userAgent:'electron'});
await win.webContents.executeJavaScript(`
new Promise((resolve) => {
const check = () => {
const el = document.querySelector('[data-element-type="slide-container"]');
if (el) return resolve(true);
setTimeout(check, 200);
};
check();
});
`);
const metadata = await win.webContents.executeJavaScript(
`
(() => {
const rgbToHex = (color) => {
if (!color || color === "transparent" || color === "none") return "000000";
if (color.startsWith("#")) return color.replace("#", "");
const matches = color.match(/\\d+/g);
if (!matches) return "000000";
const [r, g, b] = matches.map(x => parseInt(x));
return [r, g, b].map(x => x.toString(16).padStart(2, "0")).join("");
};
if (theme === "custom" && customColors) {
const root = document.documentElement;
root.style.setProperty("--custom-slide-bg", customColors.slideBg);
root.style.setProperty("--custom-slide-title", customColors.slideTitle);
root.style.setProperty("--custom-slide-heading", customColors.slideHeading);
root.style.setProperty("--custom-slide-description", customColors.slideDescription);
root.style.setProperty("--custom-slide-box", customColors.slideBox);
}
}, { theme, customColors });
const slidesMetadata = [];
const slideContainers = document.querySelectorAll('[data-element-type="slide-container"]');
// Get slide metadata
const metadata = await page.evaluate(() => {
const rgbToHex = (color: string): string => {
if (!color || color === "transparent" || color === "none") return "000000";
if (color.startsWith("#")) return color.replace("#", "");
const matches = color.match(/\d+/g);
if (!matches) return "000000";
const [r, g, b] = matches.map(x => parseInt(x));
return [r, g, b].map(x => x.toString(16).padStart(2, "0")).join("");
};
slideContainers.forEach((container) => {
const containerEl = container;
containerEl.style.width = "1280px";
containerEl.style.height = "720px";
containerEl.style.transform = "none";
const slidesMetadata: SlideMetadata[] = [];
const slideContainers = document.querySelectorAll('[data-element-type="slide-container"]');
const containerRect = containerEl.getBoundingClientRect();
const slideIndex = parseInt(containerEl.getAttribute("data-slide-index") || "0");
const backgroundColor = rgbToHex(window.getComputedStyle(containerEl).backgroundColor);
slideContainers.forEach((container) => {
const containerEl = container as HTMLElement;
containerEl.style.width = "1280px";
containerEl.style.height = "720px";
containerEl.style.transform = "none";
const elements = [];
const slideElements = containerEl.querySelectorAll('[data-slide-element]:not([data-element-type="slide-container"])');
const containerRect = containerEl.getBoundingClientRect();
const slideIndex = parseInt(containerEl.getAttribute("data-slide-index") || "0");
const backgroundColor = rgbToHex(window.getComputedStyle(containerEl).backgroundColor);
slideElements.forEach((element) => {
const el = element;
const elementRect = el.getBoundingClientRect();
const computedStyle = window.getComputedStyle(el);
const elements: SlideElement[] = [];
const slideElements = containerEl.querySelectorAll('[data-slide-element]:not([data-element-type="slide-container"])');
const position = {
left: Math.round(elementRect.left - containerRect.left),
top: Math.round(elementRect.top - containerRect.top),
width: Math.round(elementRect.width),
height: Math.round(elementRect.height),
};
slideElements.forEach((element) => {
const el = element as HTMLElement;
const elementRect = el.getBoundingClientRect();
const computedStyle = window.getComputedStyle(el);
const elementType = el.getAttribute("data-element-type");
if (!elementType) return;
const position: Position = {
left: Math.round(elementRect.left - containerRect.left),
top: Math.round(elementRect.top - containerRect.top),
width: Math.round(elementRect.width),
height: Math.round(elementRect.height),
};
switch (elementType) {
case "text":
elements.push({
position,
paragraphs: [{
alignment: el.getAttribute("data-is-align") === 'true' ? 2 : 1,
text: el.getAttribute("data-text-content") || el.textContent || "",
font: {
name: computedStyle.fontFamily.split('_')[2] || 'Inter',
size: parseInt(computedStyle.fontSize),
bold: parseInt(computedStyle.fontWeight) >= 500,
weight: parseInt(computedStyle.fontWeight),
color: rgbToHex(computedStyle.color),
},
}],
});
break;
const elementType = el.getAttribute("data-element-type");
if (!elementType) return;
case "picture":
const imgEl = el.tagName.toLowerCase() === "img" ? el : el.querySelector("img");
if (imgEl) {
elements.push({
position,
picture: {
is_network: imgEl.src.startsWith("http"),
path: imgEl.src || imgEl.getAttribute("data-image-path") || "",
},
shape: imgEl.getAttribute('data-image-type'),
object_fit: {
fit: imgEl.getAttribute('data-object-fit'),
focus: [
parseFloat(imgEl.getAttribute('data-focial-point-x') || '0'),
parseFloat(imgEl.getAttribute('data-focial-point-y') || '0'),
],
},
overlay: el.getAttribute("data-is-icon") ? "ffffff" : null,
border_radius: Array(4).fill(parseInt(computedStyle.borderRadius) || 0),
});
}
break;
switch (elementType) {
case "text":
elements.push({
position,
paragraphs: [{
alignment: el.getAttribute("data-is-align") === 'true' ? 2 : 1,
text: el.getAttribute("data-text-content") || el.textContent || "",
font: {
name: computedStyle.fontFamily.split('_')[2] || 'Inter',
size: parseInt(computedStyle.fontSize),
bold: parseInt(computedStyle.fontWeight) >= 500,
weight: parseInt(computedStyle.fontWeight),
color: rgbToHex(computedStyle.color),
},
}],
} as TextElement);
break;
case "picture":
const imgEl = el.tagName.toLowerCase() === "img" ? el as HTMLImageElement : el.querySelector("img") as HTMLImageElement;
if (imgEl) {
case "graph":
elements.push({
position,
picture: {
is_network: imgEl.src.startsWith("http"),
path: imgEl.src || imgEl.getAttribute("data-image-path") || "",
is_network: true,
path: \`__GRAPH_PLACEHOLDER__\${el.getAttribute("data-element-id")}\`,
},
shape: imgEl.getAttribute('data-image-type'),
object_fit: {
fit: imgEl.getAttribute('data-object-fit'),
focus: [
parseFloat(imgEl.getAttribute('data-focial-point-x') || '0'),
parseFloat(imgEl.getAttribute('data-focial-point-y') || '0'),
],
},
overlay: el.getAttribute("data-is-icon") ? "ffffff" : null,
border_radius: Array(4).fill(parseInt(computedStyle.borderRadius) || 0),
} as PictureElement);
}
break;
border_radius: [0, 0, 0, 0],
});
break;
case "graph":
elements.push({
position,
picture: {
is_network: true,
path: `__GRAPH_PLACEHOLDER__${el.getAttribute("data-element-id")}`,
},
border_radius: [0, 0, 0, 0],
} as GraphElement);
break;
case "slide-box":
case "filledbox":
const boxShadow = computedStyle.boxShadow;
let shadowRadius = 0;
let shadowColor = "000000";
let shadowOffsetX = 0;
let shadowOffsetY = 0;
let shadowOpacity = 0;
case "slide-box":
case "filledbox":
const boxShadow = computedStyle.boxShadow;
let shadowRadius = 0;
let shadowColor = "000000";
let shadowOffsetX = 0;
let shadowOffsetY = 0;
let shadowOpacity = 0;
if (boxShadow && boxShadow !== "none") {
const boxShadowRegex =
/rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+),?\\s*([\\d.]+)?\\)?\\s+(-?\\d+)px\\s+(-?\\d+)px\\s+(-?\\d+)px/;
const match = boxShadow.match(boxShadowRegex);
if (boxShadow && boxShadow !== "none") {
const boxShadowRegex =
/rgba?\((\d+),\s*(\d+),\s*(\d+),?\s*([\d.]+)?\)?\s+(-?\d+)px\s+(-?\d+)px\s+(-?\d+)px/;
const match = boxShadow.match(boxShadowRegex);
if (match) {
const r = match[1];
const g = match[2];
const b = match[3];
const rgbStr = "rgb(" + r + ", " + g + ", " + b + ")";
shadowColor = rgbToHex(rgbStr);
shadowOpacity = match[4] ? parseFloat(match[4]) : 1;
shadowOffsetX = parseInt(match[5]);
shadowOffsetY = parseInt(match[6]);
shadowRadius = parseInt(match[7]);
if (match) {
const r = match[1];
const g = match[2];
const b = match[3];
const rgbStr = "rgb(" + r + ", " + g + ", " + b + ")";
shadowColor = rgbToHex(rgbStr);
shadowOpacity = match[4] ? parseFloat(match[4]) : 1;
shadowOffsetX = parseInt(match[5]);
shadowOffsetY = parseInt(match[6]);
shadowRadius = parseInt(match[7]);
}
}
}
elements.push({
position,
type:
computedStyle.borderRadius === "9999px" ||
computedStyle.borderRadius === "50%"
? 9
: 5,
fill: {
color: rgbToHex(computedStyle.backgroundColor),
},
border_radius: parseInt(computedStyle.borderRadius) || 0,
stroke: {
color: rgbToHex(computedStyle.borderColor),
thickness: parseInt(computedStyle.borderWidth) || 0,
},
shadow: {
radius: shadowRadius,
color: shadowColor,
offset: Math.sqrt(
shadowOffsetX * shadowOffsetX +
shadowOffsetY * shadowOffsetY
),
opacity: shadowOpacity,
angle: Math.round(
(Math.atan2(shadowOffsetY, shadowOffsetX) * 180) / Math.PI
),
},
});
break;
elements.push({
position,
type:
computedStyle.borderRadius === "9999px" ||
computedStyle.borderRadius === "50%"
? 9
: 5,
fill: {
color: rgbToHex(computedStyle.backgroundColor),
},
border_radius: parseInt(computedStyle.borderRadius) || 0,
stroke: {
color: rgbToHex(computedStyle.borderColor),
thickness: parseInt(computedStyle.borderWidth) || 0,
},
shadow: {
radius: shadowRadius,
color: shadowColor,
offset: Math.sqrt(
shadowOffsetX * shadowOffsetX +
shadowOffsetY * shadowOffsetY
),
opacity: shadowOpacity,
angle: Math.round(
(Math.atan2(shadowOffsetY, shadowOffsetX) * 180) / Math.PI
),
},
});
break;
case "line":
elements.push({
position,
lineType: 1,
thickness: computedStyle.borderWidth || computedStyle.height,
color: rgbToHex(
computedStyle.borderColor || computedStyle.backgroundColor
),
});
break;
}
elements.push({
position,
lineType: 1,
thickness: computedStyle.borderWidth || computedStyle.height,
color: rgbToHex(
computedStyle.borderColor || computedStyle.backgroundColor
),
});
break;
}
});
slidesMetadata.push({ slideIndex, backgroundColor, elements });
});
slidesMetadata.push({ slideIndex, backgroundColor, elements });
});
return slidesMetadata;
})();
`
)
// ✅ Handle Graphs: capture each graph element as an image
const graphIds: { id: string; bounds: Electron.Rectangle }[] = await win.webContents.executeJavaScript(`
(() => {
return Array.from(document.querySelectorAll('[data-element-type="graph"]')).map(el => el.getAttribute("data-element-id"));
})();
`);
for (const id of graphIds) {
try {
// Scroll into view first
await win.webContents.executeJavaScript(`
document.querySelector('[data-element-id="${id}"]').scrollIntoView({ behavior: 'instant', block: 'center' });
`);
// Wait a bit for any animations/rendering to complete
await new Promise((r) => setTimeout(r, 2000));
const bounds: Electron.Rectangle = await win.webContents.executeJavaScript(`
(() => {
const el = document.querySelector('[data-element-id="${id}"]');
if (!el) return null;
const rect = el.getBoundingClientRect();
return {
x: Math.round(rect.left),
y: Math.round(rect.top),
width: Math.round(rect.width),
height: Math.round(rect.height),
};
})();
`);
return slidesMetadata;
});
// Handle graph elements
const graphElements = await page.$$('[data-element-type="graph"]');
for (const graphElement of graphElements) {
const graphId = await graphElement.evaluate(el => el.getAttribute("data-element-id"));
const screenshot = await graphElement.screenshot({
type: "jpeg",
encoding: "base64",
quality: 100,
omitBackground: true,
});
const filename = `chart-${graphId}-${Date.now()}.jpg`;
const filePath = path.join(tempDir, filename);
fs.writeFileSync(filePath, Buffer.from(screenshot, 'base64'));
metadata.forEach(slide => {
slide.elements.forEach(element => {
if ('picture' in element && element.picture.path === `__GRAPH_PLACEHOLDER__${graphId}`) {
const image = await win.webContents.capturePage(bounds);
const buffer = image.toJPEG(100);
if (buffer.length === 0) {
console.error("Empty buffer! Graph not captured.");
continue;
}
const filePath = path.join(tempDir, `chart-${id}-${Date.now()}.jpeg`);
fs.writeFileSync(filePath, buffer);
// Update metadata
metadata.forEach((slide: any) => {
slide.elements.forEach((element: any) => {
if ("picture" in element && element.picture.path === `__GRAPH_PLACEHOLDER__${id}`) {
element.picture.path = filePath;
}
});
});
} catch (err) {
console.error(`Failed to capture or save chart-${id}:`, err);
}
}
return metadata;
} catch (error) {
console.error("Error during page preparation:", error);
throw error;
} finally {
if (browser) await browser.close();
// if (browser) await browser.close();
if (win) win.close();
}
});
}

View file

@ -30,24 +30,25 @@ const AllChart = ({
const slide =
state.presentationGeneration?.presentationData?.slides[slideIndex];
const style = slide?.content.graph.style;
const style = slide?.content.graph.style || {};
return Object.keys(
style === null || style === undefined ? {} : (style as ChartSettings)
).length > 0
? (style as ChartSettings)
: {
showLegend: false,
showGrid: false,
showAxisLabel: true,
showDataLabel: true,
dataLabel: {
dataLabelPosition:
slide?.content.graph.type === "pie"
? ("Outside" as const)
: ("Inside" as const),
dataLabelAlignment: "Center" as const,
},
};
showLegend: false,
showGrid: false,
showAxisLabel: true,
showDataLabel: true,
dataLabel: {
dataLabelPosition:
slide?.content.graph.type === "pie"
? ("Outside" as const)
: ("Inside" as const),
dataLabelAlignment: "Center" as const,
},
};
});
useEffect(() => {

View file

@ -140,175 +140,7 @@ const LayoutPreview = ({ type }: { type: string }) => {
</div>
</div>
)
case 'type10':
return (
<div className="w-full h-[120px] bg-white p-3 flex flex-col gap-2">
<div className="h-3 bg-gray-200 w-1/2 mx-auto"></div>
<div className="flex justify-center gap-8 flex-1">
{[1, 2].map((i) => (
<div key={i} className="text-center flex flex-col items-center">
<div className="w-12 h-12 rounded-full border-4 border-blue-200 flex items-center justify-center">
<div className="text-xs text-gray-500">{i * 10}%</div>
</div>
<div className="h-2 bg-gray-200 w-24 mt-2"></div>
<div className="h-1.5 bg-gray-100 w-20 mt-1"></div>
</div>
))}
</div>
</div>
)
case 'type11':
return (
<div className="w-full h-[120px] bg-white p-3 flex flex-col gap-2">
<div className="h-3 bg-gray-200 w-1/2 mx-auto"></div>
<div className="flex justify-between gap-4 flex-1">
{[1, 2].map((i) => (
<div key={i} className="flex-1 bg-gray-50 p-2 rounded flex flex-col items-center">
<div className="w-10 h-10 rounded-full bg-blue-200 flex items-center justify-center mb-2">
<div className="text-xs text-gray-500">257</div>
</div>
<div className="h-2 bg-gray-200 w-3/4 mb-1"></div>
<div className="h-1.5 bg-gray-100 w-2/3"></div>
</div>
))}
</div>
</div>
)
case 'type12':
return (
<div className="w-full h-[120px] bg-white p-3 flex justify-between gap-4">
<div className="w-1/2 flex flex-col justify-center space-y-2">
<div className="h-3 bg-gray-200 w-3/4"></div>
<div className="h-2 bg-gray-100 w-full"></div>
</div>
<div className="w-1/2 flex justify-center items-center">
<div className="w-16 h-16 rounded-full border-4 border-t-blue-200 border-r-blue-200 border-b-gray-50 border-l-gray-50 flex items-center justify-center">
<div className="w-4 h-4 text-xs ">Icon</div>
</div>
</div>
</div>
)
case 'type13':
return (
<div className="w-full h-[120px] bg-white p-3 flex flex-col gap-2">
<div className="text-center space-y-1">
<div className="h-3 bg-gray-200 w-1/2 mx-auto"></div>
<div className="h-2 bg-gray-100 w-3/4 mx-auto"></div>
</div>
<div className="flex justify-center gap-12 flex-1">
{[1, 2].map((i) => (
<div key={i} className="text-center flex flex-col items-center">
<div className="w-12 h-12 rounded-full border-4 border-t-blue-200 border-r-blue-200 border-b-gray-50 border-l-gray-50 flex items-center justify-center mb-1">
<div className="text-xs ">Icon</div>
</div>
<div className="h-2 bg-gray-200 w-32 mb-1"></div>
<div className="h-1.5 bg-gray-100 w-24"></div>
</div>
))}
</div>
</div>
)
case 'type14':
return (
<div className="w-full h-[120px] bg-white p-3 flex flex-col gap-2">
<div className="text-center space-y-1">
<div className="h-3 bg-gray-200 w-1/2 mx-auto"></div>
<div className="h-2 bg-gray-100 w-3/4 mx-auto"></div>
</div>
<div className="flex justify-center gap-6 flex-1">
{[1, 2].map((i) => (
<div key={i} className="flex-1 bg-gray-50 p-2 rounded flex flex-col items-center">
<div className="relative w-16 h-10">
<svg width="64" height="40" viewBox="0 0 64 40">
<path
d="M 4,36 A 28,28 0 0 1 60,36"
fill="none"
stroke="#4895ef"
strokeWidth="6"
strokeLinecap="round"
/>
<text
x="32"
y="28"
dominantBaseline="middle"
textAnchor="middle"
fontSize="12"
fontWeight="500"
fill="#4895ef"
>
{i === 1 ? '30%' : '80%'}
</text>
</svg>
</div>
<div className="h-2 bg-gray-200 w-3/4 mb-1 mt-2"></div>
<div className="h-1.5 bg-gray-100 w-2/3 text-center"></div>
</div>
))}
</div>
</div>
)
case 'type15':
return (
<div className="w-full h-[120px] bg-white p-3 flex flex-col gap-2">
<div className="text-center space-y-1">
<div className="h-3 bg-gray-200 w-1/2 mx-auto"></div>
<div className="h-2 bg-gray-100 w-3/4 mx-auto"></div>
</div>
<div className="flex justify-center gap-6 flex-1">
{[1, 2].map((i) => (
<div key={i} className="flex-1 bg-white shadow-sm rounded-lg p-2 flex flex-col items-center">
<div className="relative w-16 h-10">
<svg width="64" height="40" viewBox="0 0 64 40">
<path
d="M 4,36 A 28,28 0 0 1 60,36"
fill="none"
stroke="#4895ef"
strokeWidth="6"
strokeLinecap="round"
/>
<g transform={`rotate(${i === 1 ? '-60' : '-20'} 32 36)`}>
<line
x1="32"
y1="36"
x2="32"
y2="14"
stroke="#4895ef"
strokeWidth="2"
strokeLinecap="round"
/>
</g>
</svg>
</div>
<div className="h-2 bg-gray-200 w-3/4 mb-1 mt-2"></div>
<div className="h-1.5 bg-gray-100 w-2/3 text-center"></div>
</div>
))}
</div>
</div>
)
case 'type16':
return (
<div className="w-full h-[120px] bg-white p-3 flex flex-col gap-2">
<div className="text-center space-y-1">
<div className="h-3 bg-gray-200 w-1/2 mx-auto"></div>
<div className="h-2 bg-gray-100 w-3/4 mx-auto"></div>
</div>
<div className="flex justify-center gap-6 flex-1">
{[1, 2].map((i) => (
<div key={i} className="flex-1 bg-white shadow-sm rounded-lg p-3 flex flex-col">
<div className="h-2 w-full bg-gray-100 rounded-full mb-2">
<div
className={`h-full ${i === 1 ? 'w-2/3' : 'w-3/5'} bg-blue-300 rounded-full`}
></div>
</div>
<div className="h-2 bg-gray-200 w-3/4 mb-1"></div>
<div className="h-1.5 bg-gray-100 w-full text-center"></div>
</div>
))}
</div>
</div>
)
default:
return null
}
@ -323,7 +155,7 @@ const NewSlide = ({ onSelectLayout, setShowNewSlideSelection }: NewSlideProps) =
<Trash2 onClick={() => setShowNewSlideSelection(false)} className='text-gray-500 text-2xl cursor-pointer' />
</div>
<div className='grid grid-cols-4 gap-4'>
{['type1', 'type2', 'type4', 'type5', 'type6', 'type7', 'type8', 'type9', 'type10', 'type11', 'type12', 'type13', 'type14', 'type15', 'type16'].map((type) => (
{['type1', 'type2', 'type4', 'type5', 'type6', 'type7', 'type8', 'type9'].map((type) => (
<div
key={type}
className="transform hover:scale-105 transition-transform cursor-pointer"

View file

@ -6,6 +6,7 @@ import {
SquareArrowOutUpRight,
Play,
Loader2,
ExternalLink,
} from "lucide-react";
import React, { useState } from "react";
import Wrapper from "@/components/Wrapper";
@ -45,6 +46,7 @@ import ThemeSelector from "./ThemeSelector";
import Modal from "./Modal";
import Announcement from "@/components/Announcement";
import { getFontLink } from "../../utils/others";
const Header = ({
presentation_id,
@ -125,9 +127,8 @@ const Header = ({
const getSlideMetadata = async () => {
try {
// Get the current URL without any query parameters
// const baseUrl = `${window.location.origin}/pdf-maker?id=${presentation_id}`;
const baseUrl = window.location.href;
// @ts-ignore
const metadata = await window.electron.getSlideMetadata(
baseUrl,
@ -258,6 +259,12 @@ const Header = ({
<img src="/pptx.svg" alt="pptx export" width={30} height={30} />
Export as PPTX
</Button>
<p className="text-sm pt-3 border-t border-gray-300">
Font Used:
<a className="text-blue-500 flex items-center gap-1" href={getFontLink(currentColors.fontFamily).link || ''} target="_blank" rel="noopener noreferrer">
{getFontLink(currentColors.fontFamily).name || ''} <ExternalLink className="w-4 h-4" />
</a>
</p>
</div>
);
@ -320,9 +327,7 @@ const Header = ({
{isStreaming && (
<Loader2 className="animate-spin text-white font-bold w-6 h-6" />
)}
<button onClick={() => router.push(`/pdf-maker?id=${presentation_id}`)}>
go there
</button>
<Select value={currentTheme} onValueChange={handleThemeSelect}>
<SelectTrigger className="w-[160px] bg-[#6358fd] text-white border-none hover:bg-[#5146E5] transition-colors">
<div className="flex items-center gap-2">

View file

@ -94,6 +94,7 @@ const PresentationPage = ({ presentation_id }: { presentation_id: string }) => {
console.error("Error AAYO", error);
})
.finally(() => {
console.log("Auto finished");
setAutoSaveLoading(false);
});
},
@ -114,6 +115,7 @@ const PresentationPage = ({ presentation_id }: { presentation_id: string }) => {
(slide) => slide.images && slide.images.length > 0
)
) {
debouncedSave({
presentation_id: presentation_id,
slides: presentationData.slides,

View file

@ -64,6 +64,7 @@ const SlideContent = ({
);
if (response) {
console.log("response", response);
dispatch(updateSlide({ index: slide.index, slide: response }));
toast({
title: "Success",
@ -196,9 +197,8 @@ const SlideContent = ({
<button
disabled={isUpdating}
type="submit"
className={`bg-gradient-to-r from-[#9034EA] to-[#5146E5] rounded-[32px] px-4 py-2 text-white flex items-center justify-end gap-2 ml-auto ${
isUpdating ? "opacity-70 cursor-not-allowed" : ""
}`}
className={`bg-gradient-to-r from-[#9034EA] to-[#5146E5] rounded-[32px] px-4 py-2 text-white flex items-center justify-end gap-2 ml-auto ${isUpdating ? "opacity-70 cursor-not-allowed" : ""
}`}
>
{isUpdating ? "Updating..." : "Update"}
<SendHorizontal className="w-4 sm:w-5 h-4 sm:h-5" />

View file

@ -1,11 +1,12 @@
import { randomChartGenerator } from "@/lib/utils";
import { Slide } from "../types/slide";
import { generateRandomId } from "./others";
const randomGraph = (presentation_id: string) => {
const randomData = randomChartGenerator();
return {
id: crypto.randomUUID(),
id: generateRandomId(),
name: "Sales Performance",
type: "bar",
presentation: presentation_id,
@ -20,7 +21,7 @@ export const getEmptySlideContent = (
presentation_id: string
): Slide => {
const baseSlide: Slide = {
id: crypto.randomUUID(),
id: generateRandomId(),
type,
index,
design_index: 1,

View file

@ -49,6 +49,44 @@ export function removeUUID(fileName: string) {
return fileName; // In case there's no extension
}
export function generateRandomId(): string {
const length = 36;
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_';
let id = '';
for (let i = 0; i < length; i++) {
const randomIndex = Math.floor(Math.random() * chars.length);
id += chars[randomIndex];
}
return id;
}
export const getFontLink = (fontName: string) => {
if (!fontName) {
return {link: '', name: ''};
}
if ( fontName.includes('instrument')) {
return{link: 'https://fonts.google.com/specimen/Instrument+Sans', name: 'Instrument Sans'}
}
if (fontName.includes('fraunces')) {
return{link: 'https://fonts.google.com/specimen/Fraunces', name: 'Fraunces'}
}
if (fontName.includes('montserrat')) {
return{link: 'https://fonts.google.com/specimen/Montserrat', name: 'Montserrat'}
}
if (fontName.includes('inria-serif')) {
return{link: 'https://fonts.google.com/specimen/Inria+Serif', name: 'Inria Serif'}
}
if(fontName.includes('inter')) {
return{link: 'https://fonts.google.com/specimen/Inter', name: 'Inter'}
}
else {
return {link: '', name: ''};
}
}
export const numberTranslations: any = {
// Key languages
"English (English)": ["01", "02", "03", "04", "05"],