-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
583 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
66 changes: 66 additions & 0 deletions
66
apps/expo/src/device-file-saving/SavePdfToAndroidButton.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import React, { FC } from "react"; | ||
import { Button, Alert, Platform } from "react-native"; | ||
import * as FileSystem from "expo-file-system"; | ||
import { shareAsync } from "expo-sharing"; | ||
import { trpc } from "../utils/trpc"; | ||
|
||
type DownloadPdfButtonProps = { | ||
testId: string; | ||
}; | ||
|
||
const DownloadPdfButton: FC<DownloadPdfButtonProps> = (props) => { | ||
const generatePdfMutation = trpc.pdfKit.generatePdfByTestId.useMutation(); | ||
|
||
const saveFile = async (uri: string, filename: string) => { | ||
if (Platform.OS === "android") { | ||
const permissions = | ||
await FileSystem.StorageAccessFramework.requestDirectoryPermissionsAsync(); | ||
if (permissions.granted) { | ||
const base64 = await FileSystem.readAsStringAsync(uri, { | ||
encoding: FileSystem.EncodingType.Base64, | ||
}); | ||
await FileSystem.StorageAccessFramework.createFileAsync( | ||
permissions.directoryUri, | ||
filename, | ||
"testtrek/saves/pdf", | ||
) | ||
.then(async (uri) => { | ||
await FileSystem.writeAsStringAsync(uri, base64, { | ||
encoding: FileSystem.EncodingType.Base64, | ||
}); | ||
Alert.alert("File Saved", "File has been saved successfully"); | ||
}) | ||
.catch((e) => { | ||
console.log(e); | ||
Alert.alert("Error", "Failed to save file"); | ||
}); | ||
} else { | ||
shareAsync(uri); | ||
} | ||
} else { | ||
shareAsync(uri); | ||
} | ||
}; | ||
|
||
const downloadAndSavePdf = async () => { | ||
try { | ||
const result = await generatePdfMutation.mutateAsync(props.testId); | ||
const base64pdf = result.pdfBuffer; | ||
const filename = `test-${Date.now()}.pdf`; | ||
const localUri = FileSystem.documentDirectory + filename; | ||
|
||
await FileSystem.writeAsStringAsync(localUri, base64pdf, { | ||
encoding: FileSystem.EncodingType.Base64, | ||
}); | ||
|
||
await saveFile(localUri, filename); | ||
} catch (error) { | ||
console.error("Error saving PDF:", error); | ||
Alert.alert("Error", "Unable to generate or download PDF"); | ||
} | ||
}; | ||
|
||
return <Button title="Save PDF" onPress={downloadAndSavePdf} />; | ||
}; | ||
|
||
export default DownloadPdfButton; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import PDFDocument from "pdfkit"; | ||
import { Question, CustomTest, Choice } from "./types/pdfKit"; | ||
|
||
export const formatChoice = (choices: Choice[]): string => { | ||
return choices | ||
.map((choice, index) => { | ||
return `${String.fromCharCode(65 + index)}. ${choice.text}`; | ||
}) | ||
.join(" "); | ||
}; | ||
|
||
export const formatQuestion = ( | ||
question: Question, | ||
itemNumber: number, | ||
): string => { | ||
switch (question.type) { | ||
case "multiple_choice": | ||
case "multi_select": | ||
return `${itemNumber}. ${question.title}\n\n${formatChoice( | ||
question.choices, | ||
)}`; | ||
case "true_or_false": | ||
case "identification": | ||
return `_______________ ${itemNumber}. ${question.title}`; | ||
default: | ||
return ""; | ||
} | ||
}; | ||
|
||
export const formatTestData = (testData: CustomTest): string => { | ||
return testData.questions | ||
.map((question, index) => formatQuestion(question, index + 1)) | ||
.join("\n\n"); | ||
}; | ||
|
||
export const generatePdf = async (test: CustomTest): Promise<Buffer> => { | ||
return new Promise((resolve, reject) => { | ||
const doc = new PDFDocument(); | ||
const buffers: Buffer[] = []; | ||
|
||
doc.on("data", buffers.push.bind(buffers)); | ||
doc.on("end", () => { | ||
resolve(Buffer.concat(buffers)); | ||
}); | ||
doc.on("error", reject); | ||
|
||
doc.fontSize(16).text(test.title, { underline: true }).moveDown(); | ||
doc.fontSize(12).text(test.description).moveDown(2); | ||
|
||
const formattedData = formatTestData(test); | ||
doc.text(formattedData, { indent: 20, align: "left" }); | ||
|
||
doc.end(); | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
export type Keyword = { | ||
id: string; | ||
name: string; | ||
}; | ||
|
||
export type Choice = { | ||
id: string; | ||
isCorrect: boolean; | ||
text: string; | ||
}; | ||
|
||
export type Question = { | ||
id: string; | ||
image?: string | null; | ||
points: number; | ||
time: number; | ||
title: string; | ||
type: "multiple_choice" | "true_or_false" | "multi_select" | "identification"; | ||
choices: Choice[]; | ||
}; | ||
|
||
export type CustomTest = { | ||
id: string; | ||
title: string; | ||
description: string; | ||
imageUrl?: string; | ||
keywords: Keyword[]; | ||
createdAt: Date; | ||
updatedAt: Date; | ||
questions: Question[]; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { z } from "zod"; | ||
import { protectedProcedure, router } from "../trpc"; | ||
import { generatePdf } from "../functions/pdfKitHandlers"; | ||
|
||
export const pdfKitRouter = router({ | ||
generatePdfByTestId: protectedProcedure | ||
.input(z.string()) | ||
.mutation(async ({ ctx, input }) => { | ||
const testData = await ctx.prisma.test.findUnique({ | ||
where: { | ||
id: input, | ||
}, | ||
select: { | ||
id: true, | ||
title: true, | ||
description: true, | ||
imageUrl: true, | ||
keywords: { | ||
select: { | ||
id: true, | ||
name: true, | ||
}, | ||
}, | ||
createdAt: true, | ||
updatedAt: true, | ||
questions: { | ||
select: { | ||
choices: { | ||
select: { | ||
id: true, | ||
isCorrect: true, | ||
text: true, | ||
}, | ||
}, | ||
id: true, | ||
image: true, | ||
points: true, | ||
time: true, | ||
title: true, | ||
type: true, | ||
}, | ||
}, | ||
}, | ||
}); | ||
|
||
try { | ||
if (!testData) { | ||
throw new Error("Test data not found"); | ||
} | ||
const pdfBuffer = await generatePdf(testData); | ||
|
||
return { pdfBuffer: pdfBuffer.toString("base64") }; | ||
} catch (error) { | ||
throw new Error("PDF generation failed"); | ||
} | ||
}), | ||
}); |
Oops, something went wrong.
48de83c
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
test-trek-backend – ./apps/nextjs
test-trek-backend.vercel.app
test-trek-backend-git-main-dadili-test-trek.vercel.app
test-trek-backend-dadili-test-trek.vercel.app