From 14442072e3ba95184d3acae89bb48cf55a289010 Mon Sep 17 00:00:00 2001 From: shiva raj badu Date: Mon, 12 May 2025 13:24:28 +0545 Subject: [PATCH] nextjs api router transfer to electron --- app/ipc/footer_handlers.ts | 32 ++ app/ipc/index.ts | 7 + app/ipc/slide_metadata.ts | 22 +- app/ipc/theme_handlers.ts | 32 ++ app/preload.ts | 7 +- app/settings-store.ts | 68 +++ .../components/CustomThemeSettings.tsx | 17 +- .../context/footerContext.tsx | 22 +- .../components/DocumentPreviewPage.tsx | 3 +- .../documents-preview/components/Header.tsx | 38 -- .../presentation/components/Header.tsx | 32 +- ...ooterSqliteService.ts => footerService.ts} | 48 +- ...{themeSqliteService.ts => themeService.ts} | 33 +- .../upload/components/SupportingDoc.tsx | 2 +- servers/nextjs/app/api/footer/route.ts | 71 --- .../nextjs/app/api/slide-metadata/route.ts | 411 ------------------ servers/nextjs/app/api/theme/route.ts | 68 --- .../app/dashboard/components/Header.tsx | 4 +- .../components/PresentationListItem.tsx | 3 +- servers/nextjs/app/setting/page.tsx | 9 +- servers/nextjs/components/homePage/Footer.tsx | 115 ----- servers/nextjs/components/homePage/header.tsx | 37 -- servers/nextjs/next.config.mjs | 40 +- servers/nextjs/package.json | 7 +- 24 files changed, 215 insertions(+), 913 deletions(-) create mode 100644 app/ipc/footer_handlers.ts create mode 100644 app/ipc/theme_handlers.ts create mode 100644 app/settings-store.ts delete mode 100644 servers/nextjs/app/(presentation-generator)/documents-preview/components/Header.tsx rename servers/nextjs/app/(presentation-generator)/services/{footerSqliteService.ts => footerService.ts} (50%) rename servers/nextjs/app/(presentation-generator)/services/{themeSqliteService.ts => themeService.ts} (53%) delete mode 100644 servers/nextjs/app/api/footer/route.ts delete mode 100644 servers/nextjs/app/api/slide-metadata/route.ts delete mode 100644 servers/nextjs/app/api/theme/route.ts delete mode 100644 servers/nextjs/components/homePage/Footer.tsx delete mode 100644 servers/nextjs/components/homePage/header.tsx diff --git a/app/ipc/footer_handlers.ts b/app/ipc/footer_handlers.ts new file mode 100644 index 00000000..0612267b --- /dev/null +++ b/app/ipc/footer_handlers.ts @@ -0,0 +1,32 @@ +import { ipcMain } from 'electron'; +import { settingsStore } from '../settings-store'; + +const FOOTER_KEY = 'footer'; + +export function setupFooterHandlers() { + ipcMain.handle('get-footer', async () => { + try { + const properties = settingsStore.get(FOOTER_KEY); + console.log('Getting footer properties:', properties); + return { properties }; + } catch (error) { + console.error('Error retrieving footer properties:', error); + throw error; + } + }); + + ipcMain.handle('set-footer', async (_, properties: any) => { + try { + if (!properties) { + throw new Error('Properties are required'); + } + + console.log('Setting footer properties:', properties); + settingsStore.set(FOOTER_KEY, properties); + return { success: true }; + } catch (error) { + console.error('Error saving footer properties:', error); + throw error; + } + }); +} \ No newline at end of file diff --git a/app/ipc/index.ts b/app/ipc/index.ts index bd5ad874..f4a60fc7 100644 --- a/app/ipc/index.ts +++ b/app/ipc/index.ts @@ -1,8 +1,15 @@ import { setupExportHandlers } from "./export_handlers"; import { setupUserConfigHandlers } from "./user_config_handlers"; +import { setupSlideMetadataHandlers } from "./slide_metadata"; import { setupReadFile } from "./read_file"; +import { setupFooterHandlers } from "./footer_handlers"; +import { setupThemeHandlers } from "./theme_handlers"; + export function setupIpcHandlers() { setupExportHandlers(); setupUserConfigHandlers(); + setupSlideMetadataHandlers(); setupReadFile(); + setupFooterHandlers(); + setupThemeHandlers(); } \ No newline at end of file diff --git a/app/ipc/slide_metadata.ts b/app/ipc/slide_metadata.ts index f9fda489..db2a8af0 100644 --- a/app/ipc/slide_metadata.ts +++ b/app/ipc/slide_metadata.ts @@ -86,19 +86,10 @@ interface SlideMetadata { elements: SlideElement[]; } -interface ThemeParams { - theme: string; - customColors?: { - slideBg: string; - slideTitle: string; - slideHeading: string; - slideDescription: string; - slideBox: string; - }; -} + export function setupSlideMetadataHandlers() { - ipcMain.handle("get-slide-metadata", async (_, url: string, theme: string, customColors?: ThemeParams["customColors"]) => { + ipcMain.handle("get-slide-metadata", async (_, url: string, theme: string, customColors?: any) => { let browser; try { browser = await puppeteer.launch({ @@ -108,12 +99,18 @@ export function setupSlideMetadataHandlers() { 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: ThemeParams) => { + await page.evaluate((params: any) => { const { theme, customColors } = params; document.querySelectorAll(".slide-theme").forEach((container) => { container.setAttribute("data-theme", theme); @@ -245,7 +242,6 @@ export function setupSlideMetadataHandlers() { const filename = `chart-${graphId}-${Date.now()}.jpg`; const filePath = path.join(tempDir, filename); - fs.writeFileSync(filePath, Buffer.from(screenshot, 'base64')); metadata.forEach(slide => { diff --git a/app/ipc/theme_handlers.ts b/app/ipc/theme_handlers.ts new file mode 100644 index 00000000..2fce49a7 --- /dev/null +++ b/app/ipc/theme_handlers.ts @@ -0,0 +1,32 @@ +import { ipcMain } from 'electron'; +import { settingsStore } from '../settings-store'; + +const THEME_KEY = 'theme'; + +export function setupThemeHandlers() { + ipcMain.handle('get-theme', async () => { + try { + const theme = settingsStore.get(THEME_KEY); + console.log('Getting theme:', theme); + return { theme }; + } catch (error) { + console.error('Error retrieving theme:', error); + throw error; + } + }); + + ipcMain.handle('set-theme', async (_, themeData: any) => { + try { + if (!themeData) { + throw new Error('Theme data is required'); + } + + console.log('Setting theme:', themeData); + settingsStore.set(THEME_KEY, themeData); + return { success: true }; + } catch (error) { + console.error('Error saving theme:', error); + throw error; + } + }); +} \ No newline at end of file diff --git a/app/preload.ts b/app/preload.ts index 3094c0ee..14084829 100644 --- a/app/preload.ts +++ b/app/preload.ts @@ -13,5 +13,10 @@ contextBridge.exposeInMainWorld('electron', { getUserConfig: () => ipcRenderer.invoke("get-user-config"), setUserConfig: (userConfig: UserConfig) => ipcRenderer.invoke("set-user-config", userConfig), readFile: (filePath: string) => ipcRenderer.invoke("read-file", filePath), - getSlideMetadata: (url: string, theme: string, customColors?: any) => ipcRenderer.invoke("get-slide-metadata", url, theme, customColors), + getSlideMetadata: (url: string, theme: string, customColors?: any, tempDirectory?: string) => + ipcRenderer.invoke("get-slide-metadata", url, theme, customColors, tempDirectory), + getFooter: (userId: string) => ipcRenderer.invoke("get-footer", userId), + setFooter: (userId: string, properties: any) => ipcRenderer.invoke("set-footer", userId, properties), + getTheme: (userId: string) => ipcRenderer.invoke("get-theme", userId), + setTheme: (userId: string, themeData: any) => ipcRenderer.invoke("set-theme", userId, themeData), }); diff --git a/app/settings-store.ts b/app/settings-store.ts new file mode 100644 index 00000000..f21983ac --- /dev/null +++ b/app/settings-store.ts @@ -0,0 +1,68 @@ +import { app } from 'electron'; +import path from 'path'; +import fs from 'fs'; + + +class SettingsStore { + private settingsPath: string; + private settings: { [key: string]: any }; + + constructor() { + this.settingsPath = path.join(app.getPath('userData'), 'settings.json'); + this.settings = {}; + this.loadSettings(); + } + + private loadSettings() { + try { + if (fs.existsSync(this.settingsPath)) { + const data = fs.readFileSync(this.settingsPath, 'utf-8'); + this.settings = JSON.parse(data); + + } else { + this.settings = {}; + this.saveSettings(); + + } + } catch (error) { + console.error('Error loading settings:', error); + this.settings = {}; + } + } + + private saveSettings() { + try { + fs.writeFileSync(this.settingsPath, JSON.stringify(this.settings, null, 2)); + + } catch (error) { + console.error('Error saving settings:', error); + throw error; + } + } + + get(key: string, defaultValue: any = null): any { + const value = this.settings[key]; + + return value || defaultValue; + } + + set(key: string, value: any): void { + + this.settings[key] = value; + this.saveSettings(); + } + + // Helper method to check if settings exist + has(key: string): boolean { + return key in this.settings; + } + + // Helper method to delete a setting + delete(key: string): void { + delete this.settings[key]; + this.saveSettings(); + } +} + +// Export a singleton instance +export const settingsStore = new SettingsStore(); \ No newline at end of file diff --git a/servers/nextjs/app/(presentation-generator)/components/CustomThemeSettings.tsx b/servers/nextjs/app/(presentation-generator)/components/CustomThemeSettings.tsx index b7948067..256523b9 100644 --- a/servers/nextjs/app/(presentation-generator)/components/CustomThemeSettings.tsx +++ b/servers/nextjs/app/(presentation-generator)/components/CustomThemeSettings.tsx @@ -11,7 +11,7 @@ import { } from "../store/themeSlice"; import { ThemeType } from "../upload/type"; -import { useThemeService, ThemeColors } from "../services/themeSqliteService"; +import { useThemeService, ThemeColors } from "../services/themeService"; interface CustomThemeSettingsProps { onClose?: () => void; @@ -23,12 +23,16 @@ const CustomThemeSettings = ({ presentationId, }: CustomThemeSettingsProps) => { const dispatch = useDispatch(); - const { currentColors } = useSelector((state: RootState) => state.theme); const [draftColors, setDraftColors] = useState({ - ...currentColors, - iconBg: currentColors.iconBg || "#1F1F2D", - chartColors: currentColors.chartColors || ["#1F1F2D"], - fontFamily: currentColors.fontFamily || "var(--font-inter)", + background: "#63ceff", + slideBg: "#F4F4F4", + slideTitle: "#1A1A1A", + slideHeading: "#2D2D2D", + slideDescription: "#4A4A4A", + slideBox: "#d8c6c6", + iconBg: "#281810", + chartColors: ["#281810", "#4A3728", "#665E57", "#665E57", "#665E57"], + fontFamily: "var(--font-inter)", }); const themeService = useThemeService(); @@ -132,7 +136,6 @@ const CustomThemeSettings = ({ dispatch(setLoadingState(true)); const savedTheme = await themeService.getTheme(); if (savedTheme) { - // dispatch(loadSavedTheme(savedTheme)); setDraftColors(savedTheme.colors); } } catch (error) { diff --git a/servers/nextjs/app/(presentation-generator)/context/footerContext.tsx b/servers/nextjs/app/(presentation-generator)/context/footerContext.tsx index a4a719e6..518663ba 100644 --- a/servers/nextjs/app/(presentation-generator)/context/footerContext.tsx +++ b/servers/nextjs/app/(presentation-generator)/context/footerContext.tsx @@ -4,7 +4,7 @@ import React, { createContext, useContext, useState, useEffect } from "react"; import { FooterProperties, useFooterService, -} from "../services/footerSqliteService"; +} from "../services/footerService"; // Default footer properties export const defaultFooterProperties: FooterProperties = { @@ -52,13 +52,12 @@ export const FooterProvider: React.FC<{ children: React.ReactNode }> = ({ }) => { const [footerProperties, setFooterProperties] = useState(defaultFooterProperties); const footerService = useFooterService(); - const userId = "local-user"; // Since this is a desktop app, we can use a fixed ID // Load footer properties only once when the provider mounts useEffect(() => { const loadFooterProperties = async () => { try { - const properties = await footerService.getFooterProperties(userId); + const properties = await footerService.getFooterProperties(); if (properties) { setFooterProperties(properties); } @@ -70,20 +69,9 @@ export const FooterProvider: React.FC<{ children: React.ReactNode }> = ({ loadFooterProperties(); }, []); // Empty dependency array ensures this runs only once - // const updateFooterProperties = async (newProperties: FooterProperties) => { - // try { - // const success = await footerService.saveFooterProperties(userId, newProperties); - // if (success) { - // setFooterProperties(newProperties); - // } - // } catch (error) { - // console.error("Failed to update footer properties:", error); - // } - // }; - const resetFooterProperties = async () => { try { - const success = await footerService.resetFooterProperties(userId, defaultFooterProperties); + const success = await footerService.resetFooterProperties(defaultFooterProperties); if (success) { setFooterProperties(defaultFooterProperties); } @@ -91,9 +79,10 @@ export const FooterProvider: React.FC<{ children: React.ReactNode }> = ({ console.error("Failed to reset footer properties:", error); } }; + const saveFooterProperties = async (newProperties: FooterProperties) => { try { - const success = await footerService.saveFooterProperties(userId, newProperties); + const success = await footerService.saveFooterProperties(newProperties); if (success) { setFooterProperties(newProperties); } @@ -106,7 +95,6 @@ export const FooterProvider: React.FC<{ children: React.ReactNode }> = ({ { showProgress={showLoading.progress} duration={showLoading.duration} /> -
{!isOpen && ( diff --git a/servers/nextjs/app/(presentation-generator)/documents-preview/components/Header.tsx b/servers/nextjs/app/(presentation-generator)/documents-preview/components/Header.tsx deleted file mode 100644 index e6746a60..00000000 --- a/servers/nextjs/app/(presentation-generator)/documents-preview/components/Header.tsx +++ /dev/null @@ -1,38 +0,0 @@ -'use client' -import Wrapper from '@/components/Wrapper' -import React from 'react' -import Image from 'next/image' -import Link from 'next/link' -import UserAccount from '@/app/(presentation-generator)/components/UserAccount' -import { usePathname } from 'next/navigation' -import BackBtn from '@/components/BackBtn' -const Header = ({ children }: { children: any }) => { - const pathname = usePathname(); - - return ( -
- -
-
- - - Presentation logo - -
-
- {children} - -
-
-
-
- ) -} - -export default Header \ No newline at end of file diff --git a/servers/nextjs/app/(presentation-generator)/presentation/components/Header.tsx b/servers/nextjs/app/(presentation-generator)/presentation/components/Header.tsx index ff3b5372..ee077836 100644 --- a/servers/nextjs/app/(presentation-generator)/presentation/components/Header.tsx +++ b/servers/nextjs/app/(presentation-generator)/presentation/components/Header.tsx @@ -131,28 +131,15 @@ const Header = ({ try { // Get the current URL without any query parameters const baseUrl = window.location.origin + window.location.pathname; - const urls = getEnv(); - const response = await fetch( - `${urls.NEXT_PUBLIC_URL}/api/slide-metadata`, - { - method: "POST", - headers: getHeader(), - body: JSON.stringify({ - url: baseUrl, - theme: currentTheme, - customColors: currentColors, - tempDirectory: urls.TEMP_DIRECTORY, - baseUrl: urls.BASE_URL, - }), - } + + // @ts-ignore + const metadata = await window.electron.getSlideMetadata( + baseUrl, + currentTheme, + currentColors, ); - if (!response.ok) { - const errorData = await response.json(); - throw new Error(errorData.error || "Failed to fetch metadata"); - } - const metadata = await response.json(); console.log("metadata", metadata); return metadata; } catch (error) { @@ -269,7 +256,7 @@ const Header = ({ variant="ghost" className="pb-4 border-b rounded-none border-gray-300 w-full flex justify-start text-[#5146E5]" > - pdf export + pdf export Export as PDF
@@ -329,12 +316,11 @@ const Header = ({ - Presentation logo diff --git a/servers/nextjs/app/(presentation-generator)/services/footerSqliteService.ts b/servers/nextjs/app/(presentation-generator)/services/footerService.ts similarity index 50% rename from servers/nextjs/app/(presentation-generator)/services/footerSqliteService.ts rename to servers/nextjs/app/(presentation-generator)/services/footerService.ts index e2d685c8..e357e922 100644 --- a/servers/nextjs/app/(presentation-generator)/services/footerSqliteService.ts +++ b/servers/nextjs/app/(presentation-generator)/services/footerService.ts @@ -1,4 +1,3 @@ -// app/(presentation-generator)/services/footerService.ts import { useCallback } from "react"; export interface FooterProperties { @@ -28,20 +27,11 @@ export interface FooterProperties { export const useFooterService = () => { // Get footer properties const getFooterProperties = useCallback( - async (userId: string): Promise => { + async (): Promise => { try { - const response = await fetch( - `/api/footer?userId=${encodeURIComponent(userId)}` - ); - - if (!response.ok) { - throw new Error( - `Failed to fetch footer properties: ${response.status}` - ); - } - - const data = await response.json(); - return data.properties; + // @ts-ignore + const result = await window.electron.getFooter(); + return result.properties; } catch (error) { console.error("Error retrieving footer properties:", error); return null; @@ -52,24 +42,11 @@ export const useFooterService = () => { // Save footer properties const saveFooterProperties = useCallback( - async (userId: string, properties: FooterProperties): Promise => { + async (properties: FooterProperties): Promise => { try { - const response = await fetch("/api/footer", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ userId, properties }), - }); - - if (!response.ok) { - throw new Error( - `Failed to save footer properties: ${response.status}` - ); - } - - const data = await response.json(); - return data.success; + // @ts-ignore + const result = await window.electron.setFooter(properties); + return result.success; } catch (error) { console.error("Error saving footer properties:", error); return false; @@ -80,11 +57,8 @@ export const useFooterService = () => { // Reset footer properties const resetFooterProperties = useCallback( - async ( - userId: string, - defaultProperties: FooterProperties - ): Promise => { - return saveFooterProperties(userId, defaultProperties); + async (defaultProperties: FooterProperties): Promise => { + return saveFooterProperties(defaultProperties); }, [saveFooterProperties] ); @@ -94,4 +68,4 @@ export const useFooterService = () => { saveFooterProperties, resetFooterProperties, }; -}; +}; \ No newline at end of file diff --git a/servers/nextjs/app/(presentation-generator)/services/themeSqliteService.ts b/servers/nextjs/app/(presentation-generator)/services/themeService.ts similarity index 53% rename from servers/nextjs/app/(presentation-generator)/services/themeSqliteService.ts rename to servers/nextjs/app/(presentation-generator)/services/themeService.ts index 09a15da5..aae9a124 100644 --- a/servers/nextjs/app/(presentation-generator)/services/themeSqliteService.ts +++ b/servers/nextjs/app/(presentation-generator)/services/themeService.ts @@ -20,16 +20,9 @@ export const useThemeService = () => { colors: ThemeColors; } | null> => { try { - // Since this is a desktop app, we can use a fixed user ID - const userId = "local-user"; - const response = await fetch(`/api/theme?userId=${userId}`); - - if (!response.ok) { - throw new Error(`Failed to fetch theme: ${response.status}`); - } - - const data = await response.json(); - return data.theme; + // @ts-ignore + const result = await window.electron.getTheme(); + return result.theme; } catch (error) { console.error("Error retrieving theme:", error); return null; @@ -42,21 +35,9 @@ export const useThemeService = () => { colors: ThemeColors; }): Promise => { try { - const userId = "local-user"; - const response = await fetch("/api/theme", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ userId, themeData }), - }); - - if (!response.ok) { - throw new Error(`Failed to save theme: ${response.status}`); - } - - const data = await response.json(); - return data.success; + // @ts-ignore + const result = await window.electron.setTheme(themeData); + return result.success; } catch (error) { console.error("Error saving theme:", error); return false; @@ -69,4 +50,4 @@ export const useThemeService = () => { getTheme, saveTheme, }; -}; +}; \ No newline at end of file diff --git a/servers/nextjs/app/(presentation-generator)/upload/components/SupportingDoc.tsx b/servers/nextjs/app/(presentation-generator)/upload/components/SupportingDoc.tsx index 16d986ad..3c45d453 100644 --- a/servers/nextjs/app/(presentation-generator)/upload/components/SupportingDoc.tsx +++ b/servers/nextjs/app/(presentation-generator)/upload/components/SupportingDoc.tsx @@ -185,7 +185,7 @@ const SupportingDoc = ({ files, onFilesChange }: SupportingDocProps) => { transition-colors flex items-center justify-center relative" > {isImageFile(file) ? ( - + ) : ( )} diff --git a/servers/nextjs/app/api/footer/route.ts b/servers/nextjs/app/api/footer/route.ts deleted file mode 100644 index 018c6c82..00000000 --- a/servers/nextjs/app/api/footer/route.ts +++ /dev/null @@ -1,71 +0,0 @@ -// app/api/footer/route.ts -import { NextRequest, NextResponse } from "next/server"; -import { getDb } from "../../(presentation-generator)/services/db"; - -// GET handler to retrieve properties -export async function GET(request: NextRequest) { - try { - const { searchParams } = new URL(request.url); - const userId = searchParams.get("userId"); - - if (!userId) { - return NextResponse.json( - { error: "User ID is required" }, - { status: 400 } - ); - } - - const db = await getDb(); - const result = await db.get( - "SELECT data FROM settings WHERE key = 'footer' AND userId = ?", - userId - ); - await db.close(); - - if (result) { - return NextResponse.json({ properties: JSON.parse(result.data) }); - } - - return NextResponse.json({ properties: null }); - } catch (error) { - console.error("Error retrieving footer properties:", error); - return NextResponse.json( - { error: "Failed to retrieve footer properties" }, - { status: 500 } - ); - } -} - -// POST handler to save properties -export async function POST(request: NextRequest) { - try { - const body = await request.json(); - const { userId, properties } = body; - - if (!userId || !properties) { - return NextResponse.json( - { error: "User ID and properties are required" }, - { status: 400 } - ); - } - - const db = await getDb(); - const propertiesJson = JSON.stringify(properties); - - await db.run( - `INSERT OR REPLACE INTO settings (key, userId, data, updated_at) - VALUES ('footer', ?, ?, CURRENT_TIMESTAMP)`, - [userId, propertiesJson] - ); - - await db.close(); - - return NextResponse.json({ success: true }); - } catch (error) { - console.error("Error saving footer properties:", error); - return NextResponse.json( - { error: "Failed to save footer properties" }, - { status: 500 } - ); - } -} diff --git a/servers/nextjs/app/api/slide-metadata/route.ts b/servers/nextjs/app/api/slide-metadata/route.ts deleted file mode 100644 index dfd89c33..00000000 --- a/servers/nextjs/app/api/slide-metadata/route.ts +++ /dev/null @@ -1,411 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; -import puppeteer from "puppeteer"; -import fs from 'fs'; -import path from 'path'; -import os from 'os'; - -interface Position { - left: number; - top: number; - width: number; - height: number; -} - -interface FontStyles { - name: string; - size: number; - bold: boolean; - weight: number; - color: string; -} - -interface TextElement { - position: Position; - paragraphs: { - alignment: number; - text: string; - font: FontStyles; - }[]; -} - -interface PictureElement { - position: Position; - picture: { - is_network: boolean; - path: string; - }; - shape: string | null; - object_fit: { - fit: string | null; - focus: number[]; - }; - overlay: string | null; - border_radius: number[]; -} - -interface BoxElement { - position: Position; - type: number; - fill: { - color: string; - }; - border_radius: number; - stroke: { - color: string; - thickness: number; - }; - shadow: { - radius: number; - color: string; - offset: number; - opacity: number; - angle: number; - }; -} - -interface LineElement { - position: Position; - lineType: number; - thickness: string; - color: string; -} - -interface GraphElement { - position: Position; - picture: { - is_network: boolean; - path: string; - }; - border_radius: number[]; -} - -type SlideElement = TextElement | PictureElement | BoxElement | LineElement | GraphElement; - -interface SlideMetadata { - slideIndex: number; - backgroundColor: string; - elements: SlideElement[]; -} - -interface ThemeParams { - theme: string; - customColors?: { - slideBg: string; - slideTitle: string; - slideHeading: string; - slideDescription: string; - slideBox: string; - }; -} - -export async function POST(request: NextRequest) { - let browser; - try { - const body = await request.json(); - const { url, theme, customColors, tempDirectory, baseUrl } = body; - - if (!url) { - return NextResponse.json({ error: "Missing URL" }, { status: 400 }); - } - - browser = await puppeteer.launch({ - headless: true, - args: ['--no-sandbox', '--disable-setuid-sandbox'] - }); - - 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: "${baseUrl || 'http://localhost:8000'}", - NEXT_PUBLIC_URL: "${url}", - }; - `); - - try { - await page.goto(url, { - waitUntil: "networkidle0", - timeout: 60000, - }); - } catch (error) { - console.error("Navigation error:", error); - await browser.close(); - return NextResponse.json({ error: "Failed to Navigate to provided URL" }, { status: 500 }); - } - - - - try { - await page.waitForSelector('[data-element-type="slide-container"]', { - timeout: 80000, - }); - await page.evaluate( - async (params: ThemeParams) => { - const { theme, customColors } = params; - const containers = document.querySelectorAll(".slide-theme"); - - containers.forEach((container) => { - container.removeAttribute("data-theme"); - container.setAttribute("data-theme", theme); - }); - - 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 } - ); - } catch (error) { - await browser.close(); - return NextResponse.json({ error: "Slide container not found" }, { status: 500 }); - } - - const metadata = await page.evaluate(async () => { - function rgbToHex(color: 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 = parseInt(matches[0]); - const g = parseInt(matches[1]); - const b = parseInt(matches[2]); - return [r, g, b].map((x) => x.toString(16).padStart(2, "0")).join(""); - } - - async function collectSlideMetadata(): Promise { - const slidesMetadata: SlideMetadata[] = []; - const slideContainers = Array.from( - document.querySelectorAll('[data-element-type="slide-container"]') - ); - - for (const container of slideContainers) { - const containerEl = container as HTMLElement; - containerEl.style.width = "1280px"; - containerEl.style.height = "720px"; - containerEl.style.transform = "none"; - - const containerRect = containerEl.getBoundingClientRect(); - const slideIndex = parseInt( - containerEl.getAttribute("data-slide-index") || "0" - ); - const containerComputedStyle = window.getComputedStyle(containerEl); - - const slideMetadata: SlideMetadata = { - slideIndex, - backgroundColor: rgbToHex(containerComputedStyle.backgroundColor), - elements: [], - }; - const slideType = containerEl.getAttribute("data-slide-type"); - - const elements = Array.from( - containerEl.querySelectorAll( - '[data-slide-element]:not([data-element-type="slide-container"])' - ) - ); - - for (const element of elements) { - const el = element as HTMLElement; - const isIcon = el.getAttribute("data-is-icon"); - const isAlign = el.getAttribute("data-is-align"); - - const elementRect = el.getBoundingClientRect(); - const computedStyle = window.getComputedStyle(el); - - 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), - }; - - const elementType = el.getAttribute("data-element-type"); - if (!elementType) continue; - - const fontStyles: FontStyles = { - name: computedStyle.fontFamily.split('_')[2] || 'Inter', - size: parseInt(computedStyle.fontSize), - bold: parseInt(computedStyle.fontWeight) >= 500 ? true : false, - weight: parseInt(computedStyle.fontWeight), - color: rgbToHex(computedStyle.color), - }; - - switch (elementType) { - case "text": - const textContent = el.getAttribute("data-text-content"); - const textElement: TextElement = { - position, - paragraphs: [ - { - alignment: isAlign === 'true' ? 2 : 1, - text: textContent || el.textContent || "", - font: fontStyles, - }, - ], - }; - slideMetadata.elements.push(textElement); - break; - - case "picture": - const imgEl = el.tagName.toLowerCase() === "img" ? el as HTMLImageElement : el.querySelector("img") as HTMLImageElement; - if (imgEl) { - const focialPointx = parseFloat(imgEl.getAttribute('data-focial-point-x') || '0'); - const focialPointy = parseFloat(imgEl.getAttribute('data-focial-point-y') || '0'); - const image_type = imgEl.getAttribute('data-image-type'); - const objectFit = imgEl.getAttribute('data-object-fit'); - - const pictureElement: PictureElement = { - position, - picture: { - is_network: imgEl.src.startsWith("http"), - path: imgEl.src || imgEl.getAttribute("data-image-path") || "", - }, - shape: image_type, - object_fit: { - fit: objectFit, - focus: [focialPointx, focialPointy], - }, - overlay: isIcon ? "ffffff" : null, - border_radius: slideType === "4" - ? [parseInt(computedStyle.borderRadius), parseInt(computedStyle.borderRadius), 0, 0] - : [parseInt(computedStyle.borderRadius), parseInt(computedStyle.borderRadius), parseInt(computedStyle.borderRadius), parseInt(computedStyle.borderRadius)], - }; - slideMetadata.elements.push(pictureElement); - } - 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; - - 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]); - } - } - - const boxElement: BoxElement = { - 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), - }, - }; - slideMetadata.elements.push(boxElement); - break; - - case "line": - const lineElement: LineElement = { - position, - lineType: 1, - thickness: computedStyle.borderWidth || computedStyle.height, - color: rgbToHex(computedStyle.borderColor || computedStyle.backgroundColor), - }; - slideMetadata.elements.push(lineElement); - break; - - case "graph": - const graphId = el.getAttribute("data-element-id"); - const graphElement: GraphElement = { - position, - picture: { - is_network: true, - path: `__GRAPH_PLACEHOLDER__${graphId}`, - }, - border_radius: [0, 0, 0, 0], - }; - slideMetadata.elements.push(graphElement); - break; - } - } - - slidesMetadata.push(slideMetadata); - } - - return slidesMetadata; - } - - return await collectSlideMetadata(); - }); - - const graphElements = await page.$$('[data-element-type="graph"]'); - - for (const graphElement of graphElements) { - const graphId = await graphElement.evaluate((el: Element) => - el.getAttribute("data-element-id") - ); - - const screenshot = await graphElement.screenshot({ - type: "jpeg", - encoding: "base64", - quality: 100, - omitBackground: true, - }); - - try { - const tempDir = tempDirectory || os.tmpdir(); - - // Generate a unique filename - const filename = `chart-${graphId}-${Date.now()}.jpg`; - const filePath = path.join(tempDir, filename); - - // Save the file - fs.writeFileSync(filePath, Buffer.from(screenshot, 'base64')); - - metadata.forEach((slide) => { - slide.elements.forEach((element) => { - if ('picture' in element && element.picture.path === `__GRAPH_PLACEHOLDER__${graphId}`) { - element.picture.path = filePath; - } - }); - }); - } catch (error) { - console.error('Error saving screenshot:', error); - continue; - } - } - - await browser.close(); - return NextResponse.json(metadata); - } catch (error) { - console.error("Error during page preparation:", error); - if (browser) await browser.close(); - return NextResponse.json({ error: "Internal server error" }, { status: 500 }); - } finally { - if (browser) await browser.close(); - } -} \ No newline at end of file diff --git a/servers/nextjs/app/api/theme/route.ts b/servers/nextjs/app/api/theme/route.ts deleted file mode 100644 index 32337a94..00000000 --- a/servers/nextjs/app/api/theme/route.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; -import { getDb } from "../../(presentation-generator)/services/db"; - -export async function GET(request: NextRequest) { - try { - const { searchParams } = new URL(request.url); - const userId = searchParams.get("userId"); - - if (!userId) { - return NextResponse.json( - { error: "User ID is required" }, - { status: 400 } - ); - } - - const db = await getDb(); - const result = await db.get( - "SELECT data FROM settings WHERE key = 'theme' AND userId = ?", - userId - ); - await db.close(); - - if (result) { - return NextResponse.json({ theme: JSON.parse(result.data) }); - } - - return NextResponse.json({ theme: null }); - } catch (error) { - console.error("Error retrieving theme:", error); - return NextResponse.json( - { error: "Failed to retrieve theme" }, - { status: 500 } - ); - } -} - -export async function POST(request: NextRequest) { - try { - const body = await request.json(); - const { userId, themeData } = body; - - if (!userId || !themeData) { - return NextResponse.json( - { error: "User ID and theme data are required" }, - { status: 400 } - ); - } - - const db = await getDb(); - const themeDataJson = JSON.stringify(themeData); - - await db.run( - `INSERT OR REPLACE INTO settings (key, userId, data, updated_at) - VALUES ('theme', ?, ?, CURRENT_TIMESTAMP)`, - [userId, themeDataJson] - ); - - await db.close(); - - return NextResponse.json({ success: true }); - } catch (error) { - console.error("Error saving theme:", error); - return NextResponse.json( - { error: "Failed to save theme" }, - { status: 500 } - ); - } -} diff --git a/servers/nextjs/app/dashboard/components/Header.tsx b/servers/nextjs/app/dashboard/components/Header.tsx index 9cbf81ab..132e0f2b 100644 --- a/servers/nextjs/app/dashboard/components/Header.tsx +++ b/servers/nextjs/app/dashboard/components/Header.tsx @@ -16,12 +16,12 @@ const Header = () => {
{pathname !== '/upload' && } - Presentation logo
diff --git a/servers/nextjs/app/dashboard/components/PresentationListItem.tsx b/servers/nextjs/app/dashboard/components/PresentationListItem.tsx index 7a5e7f86..b957b641 100644 --- a/servers/nextjs/app/dashboard/components/PresentationListItem.tsx +++ b/servers/nextjs/app/dashboard/components/PresentationListItem.tsx @@ -14,10 +14,9 @@ export const PresentationListItem: React.FC = ({
- {title}
diff --git a/servers/nextjs/app/setting/page.tsx b/servers/nextjs/app/setting/page.tsx index 7eafc72d..1aca4ed3 100644 --- a/servers/nextjs/app/setting/page.tsx +++ b/servers/nextjs/app/setting/page.tsx @@ -1,2 +1,9 @@ +import React from 'react' import SettingPage from './SettingPage' -export default SettingPage +const page = () => { + return ( + + ) +} + +export default page diff --git a/servers/nextjs/components/homePage/Footer.tsx b/servers/nextjs/components/homePage/Footer.tsx deleted file mode 100644 index df366d19..00000000 --- a/servers/nextjs/components/homePage/Footer.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import React from "react"; -import Link from "next/link"; -import Wrapper from "@/components/Wrapper"; - -interface FooterLink { - label: string; - href: string; -} - -interface FooterSection { - title: string; - links: FooterLink[]; -} - -const footerSections: FooterSection[] = [ - { - title: "Main Menu", - links: [ - // { label: "About Us", href: "/about" }, - // { label: "Templates", href: "/templates" }, - { label: "Blogs", href: "/blogs" }, - { label: "Pricing", href: "#pricing" }, - // { label: "Enterprise", href: "/enterprise" }, - { label: "Contact Us", href: "/contact" }, - ], - }, - { - title: "Our Products", - links: [ - { label: "Presentation Generator", href: "/upload" }, - { label: "Presentation-to-video", href: "/editor" }, - // { label: "Presentation-to-ppt", href: "/product-3" }, - ], - }, - // { - // title: "Solutions", - // links: [ - // { label: "Solution 1", href: "/solution-1" }, - // { label: "Solution 2", href: "/solution-2" }, - // { label: "Solution 3", href: "/solution-3" }, - // ] - // }, - { - title: "Other Links", - links: [ - { label: "FAQ", href: "#faq" }, - { label: "Terms & Conditions", href: "/terms-and-conditions" }, - { label: "Privacy Policy", href: "/privacy-policy" }, - // { label: "Cookies Policy", href: "/cookies-policy" }, - ], - }, -]; - -const Footer = () => { - return ( -
- -
- {/* Logo and Description Section */} -
- - Presenton.ai Logo - -

- No more struggling with slides. Just drop your content and let - your AI buddy craft beautiful, ready-to-share presentations for - work, study, or business — in minutes. -

-
- - {/* Links Sections */} -
- {footerSections.map((section) => ( -
-

- {section.title}{" "} -

-
    - {section.links.map((link) => ( -
  • - - {link.label} - -
  • - ))} -
-
- ))} -
-
- - {/* Copyright Section */} -
-

- Copyright {new Date().getFullYear()} Presenton. All Right Reserved. -

-
-
-
- ); -}; - -export default Footer; diff --git a/servers/nextjs/components/homePage/header.tsx b/servers/nextjs/components/homePage/header.tsx deleted file mode 100644 index b43adbe5..00000000 --- a/servers/nextjs/components/homePage/header.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import Link from "next/link"; -import Image from "next/image"; -import Wrapper from "../Wrapper"; - -export default function Header() { - return ( - -
- -
- {/*
- Home - About - Contact -
*/} - - Get Started - -
-
-
- ); -} diff --git a/servers/nextjs/next.config.mjs b/servers/nextjs/next.config.mjs index c0b530cf..8203bb3a 100644 --- a/servers/nextjs/next.config.mjs +++ b/servers/nextjs/next.config.mjs @@ -1,5 +1,6 @@ /** @type {import('next').NextConfig} */ const nextConfig = { + // output:"export", reactStrictMode: false, images: { @@ -42,43 +43,8 @@ const nextConfig = { }, ], }, - async headers() { - return [ - { - source: "/:path*", - headers: [ - { - key: "Access-Control-Allow-Origin", - value: "*", - }, - { - key: "Access-Control-Allow-Methods", - value: "GET, POST, PUT, DELETE, OPTIONS", - }, - { - key: "Access-Control-Allow-Headers", - value: "X-Requested-With, Content-Type, Authorization", - }, - ], - }, - ]; - }, - transpilePackages: ["remotion"], - webpack: (config) => { - config.externals = [ - ...config.externals, - { - canvas: "canvas", - }, - ]; - return config; - }, - typescript: { - ignoreBuildErrors: true, - }, - eslint: { - ignoreDuringBuilds: true, - }, + + }; export default nextConfig; diff --git a/servers/nextjs/package.json b/servers/nextjs/package.json index 5d0c64f0..412744f2 100644 --- a/servers/nextjs/package.json +++ b/servers/nextjs/package.json @@ -46,18 +46,17 @@ "lucide-react": "^0.447.0", "marked": "^15.0.11", "next": "^14.2.14", - "puppeteer": "^24.8.2", + "react": "^18", "react-dom": "^18", "react-redux": "^9.1.2", "recharts": "^2.15.0", - "sqlite": "^5.1.1", - "sqlite3": "^5.1.7", + "tailwind-merge": "^2.5.3", "tailwind-scrollbar-hide": "^2.0.0", "tailwindcss-animate": "^1.0.7", "tiptap-markdown": "^0.8.10", - "@tailwindcss/typography": "^0.5.16" + "@tailwindcss/typography": "^0.5.16" }, "devDependencies": {