feat: custom number of slides

This commit is contained in:
Suraj Jha 2025-08-20 00:43:38 +05:45
parent afb32c4c1a
commit 044aa243d4
No known key found for this signature in database
GPG key ID: 5AC6C16355CE2C14
5 changed files with 98 additions and 28 deletions

View file

@ -23,6 +23,7 @@ import {
PopoverTrigger,
} from "@/components/ui/popover";
import { cn } from "@/lib/utils";
import { Input } from "@/components/ui/input";
// Types
interface ConfigurationSelectsProps {
@ -41,28 +42,89 @@ const SLIDE_OPTIONS: SlideOption[] = ["5", "8", "9", "10", "11", "12", "13", "14
const SlideCountSelect: React.FC<{
value: string | null;
onValueChange: (value: string) => void;
}> = ({ value, onValueChange }) => (
<Select value={value || ""} onValueChange={onValueChange} name="slides">
<SelectTrigger
className="w-[180px] font-instrument_sans font-medium bg-blue-100 border-blue-200 focus-visible:ring-blue-300"
data-testid="slides-select"
>
<SelectValue placeholder="Select Slides" />
</SelectTrigger>
<SelectContent className="font-instrument_sans">
{SLIDE_OPTIONS.map((option) => (
<SelectItem
key={option}
value={option}
className="font-instrument_sans text-sm font-medium"
role="option"
}> = ({ value, onValueChange }) => {
const [customInput, setCustomInput] = useState(
value && !SLIDE_OPTIONS.includes(value as SlideOption) ? value : ""
);
const sanitizeToPositiveInteger = (raw: string): string => {
const digitsOnly = raw.replace(/\D+/g, "");
if (!digitsOnly) return "";
// Remove leading zeros
const noLeadingZeros = digitsOnly.replace(/^0+/, "");
return noLeadingZeros;
};
const applyCustomValue = () => {
const sanitized = sanitizeToPositiveInteger(customInput);
if (sanitized && Number(sanitized) > 0) {
onValueChange(sanitized);
}
};
return (
<Select value={value || ""} onValueChange={onValueChange} name="slides">
<SelectTrigger
className="w-[180px] font-instrument_sans font-medium bg-blue-100 border-blue-200 focus-visible:ring-blue-300"
data-testid="slides-select"
>
<SelectValue placeholder="Select Slides" />
</SelectTrigger>
<SelectContent className="font-instrument_sans">
{/* Sticky custom input at the top */}
<div
className="sticky top-0 z-10 bg-white dark:bg-slate-900 p-2 border-b"
onMouseDown={(e) => e.stopPropagation()}
onPointerDown={(e) => e.stopPropagation()}
onClick={(e) => e.stopPropagation()}
>
{option} slides
</SelectItem>
))}
</SelectContent>
</Select>
);
<div className="flex items-center gap-2">
<Input
inputMode="numeric"
pattern="[0-9]*"
value={customInput}
onMouseDown={(e) => e.stopPropagation()}
onPointerDown={(e) => e.stopPropagation()}
onClick={(e) => e.stopPropagation()}
onChange={(e) => {
const next = sanitizeToPositiveInteger(e.target.value);
setCustomInput(next);
}}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
applyCustomValue();
}
}}
onBlur={applyCustomValue}
placeholder="X"
className="h-8 w-16 px-2 text-sm"
/>
<span className="text-sm font-medium">slides</span>
</div>
</div>
{/* Hidden item to allow SelectValue to render custom selection */}
{value && !SLIDE_OPTIONS.includes(value as SlideOption) && (
<SelectItem value={value} className="hidden">
{value} slides
</SelectItem>
)}
{SLIDE_OPTIONS.map((option) => (
<SelectItem
key={option}
value={option}
className="font-instrument_sans text-sm font-medium"
role="option"
>
{option} slides
</SelectItem>
))}
</SelectContent>
</Select>
);
};
/**
* Renders a language selection component with search functionality

View file

@ -20,8 +20,10 @@ export async function POST(req: NextRequest) {
});
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 720 });
page.setDefaultNavigationTimeout(300000);
page.setDefaultTimeout(300000);
await page.goto(`http://localhost/pdf-maker?id=${id}`, { waitUntil: 'networkidle0', timeout: 180000 });
await page.goto(`http://localhost/pdf-maker?id=${id}`, { waitUntil: 'networkidle0', timeout: 300000 });
await page.waitForFunction('() => document.readyState === "complete"')
@ -47,7 +49,7 @@ export async function POST(req: NextRequest) {
return (loadedElements / totalElements) >= 0.99;
}
`,
{ timeout: 10000 }
{ timeout: 300000 }
);
await new Promise(resolve => setTimeout(resolve, 1000));

View file

@ -22,12 +22,14 @@ export async function GET(request: Request) {
});
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 720 });
page.setDefaultNavigationTimeout(300000);
page.setDefaultTimeout(300000);
await page.goto(schemaPageUrl, {
waitUntil: "networkidle0",
timeout: 80000,
timeout: 300000,
});
await page.waitForSelector("[data-layouts]", { timeout: 30000 });
await page.waitForSelector("[data-layouts]", { timeout: 300000 });
// Extract both data-layouts and data-group-settings attributes
const { dataLayouts, dataGroupSettings } = await page.$eval(

View file

@ -75,9 +75,11 @@ async function getBrowserAndPage(id: string): Promise<[Browser, Page]> {
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 720, deviceScaleFactor: 1 });
page.setDefaultNavigationTimeout(300000);
page.setDefaultTimeout(300000);
await page.goto(`http://localhost/pdf-maker?id=${id}`, {
waitUntil: "networkidle0",
timeout: 180000,
timeout: 300000,
});
return [browser, page];
}

View file

@ -16,9 +16,11 @@ export async function GET(request: Request) {
browser = await puppeteer.launch({ headless: true, args: ["--no-sandbox", "--disable-web-security"] });
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 720 });
await page.goto(schemaPageUrl, { waitUntil: "networkidle0", timeout: 80000 });
page.setDefaultNavigationTimeout(300000);
page.setDefaultTimeout(300000);
await page.goto(schemaPageUrl, { waitUntil: "networkidle0", timeout: 300000 });
await page.waitForSelector("[data-layouts]", { timeout: 30000 });
await page.waitForSelector("[data-layouts]", { timeout: 300000 });
const { dataLayouts, dataGroupSettings } = await page.$eval(
"[data-layouts]",