diff --git a/libs/html-templates-lib.bak2.ts b/libs/html-templates-lib.bak2.ts
new file mode 100644
index 0000000..45d8391
--- /dev/null
+++ b/libs/html-templates-lib.bak2.ts
@@ -0,0 +1,215 @@
+import express from "express"
+export const htmlTemplateRoute = express.Router()
+import { mimeToExtension, templateOption } from "./report-template"
+// import fs from "fs"
+//import { chromium } from 'playwright'
+import puppeteer, { PDFOptions } from 'puppeteer'
+import Handlebars from 'handlebars'
+import e from "express"
+//import { createReport } from "docx-templates"
+// แก้ package.json ของ LibreOfficeFileConverter
+// https://github.com/microsoft/TypeScript/issues/52363#issuecomment-1659179354
+//import { LibreOfficeFileConverter } from "libreoffice-file-converter"
+const TEMPLATE_FOLDER_NAME = "templates/html"
+const width_px = 1200; //TODO read from htmlOption
+
+function wait(ms: number) {
+ return new Promise(resolve => setTimeout(resolve, ms));
+}
+
+/**
+ * docxTemplate Uses docx-template to convert input data and template to output buffer.
+ * SPA and lazy load page may not fully render(eg. pantip.com).
+ * You have to handle exception throw by function
+ * handlebars template support only content from Buffer
+ * @param {Buffer|String} t template in buffer format or url to web page
+ * @param {templateOption} tdata Template Information in JSON format
+ * @param {String} outputMediaType output extension, support pdf, jpeg, png
+ * @return {Promise} output buffer after apply template.
+ */
+export async function htmlTemplateX(t: Buffer | String, tdata: templateOption, outputMediaType: string = "pdf"): Promise {
+ try {
+ if (!["pdf", "jpeg", "png"].find((e) => e === outputMediaType)) {
+ throw "FormatError"
+ }
+ const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox'] });
+ const page = await browser.newPage();
+ if (typeof t === 'string') {
+ // await page.goto(t, { waitUntil: 'networkidle0' });
+ await page.goto(t, { waitUntil: 'load' });
+ } else {
+ if (tdata.data) {
+ const template = Handlebars.compile(t.toString());
+ const html = template(tdata.data);
+ await page.setContent(html);
+ } else {
+ await page.setContent(t.toString())
+ }
+ }
+
+
+ /*
+ // try to load whole page
+ let x = await page.evaluate(async (tdata) => {
+ const scrollableSection =
+ (tdata.htmlOption?.querySelector && document.querySelector(tdata.htmlOption.querySelector)) ?
+ document.querySelector(tdata.htmlOption.querySelector) : document.body
+ if (scrollableSection) {
+ const childElement = scrollableSection.firstElementChild;
+ let scrollPosition = 0;
+ let viewportHeight = window.innerHeight;
+ if (childElement)
+ while (scrollPosition < childElement.scrollHeight) {
+ scrollableSection.scrollBy(0, viewportHeight);
+ await new Promise(resolve => setTimeout(resolve, 500));
+ scrollPosition += viewportHeight;
+ }
+ return scrollPosition
+ }
+ return 0
+ }, tdata);
+ //console.log("scrollPosition=" + x)
+
+ */
+ //force scroll for lazy load page
+ //tdata.htmlOption?
+ await page.evaluate(async (_viewportHeight, _ms, _loop) => {
+ for (let i = 0; i < _loop; i++) {
+ window.scrollBy(0, _viewportHeight);
+ await new Promise(resolve => setTimeout(resolve, 500));
+ }
+ }, 1000, 300, 5);
+
+
+
+ //find real page height
+ const totalHeight = await page.evaluate(async (tdata) => {
+ let scrollableSection =
+ (tdata.htmlOption?.querySelector &&
+ document.querySelector(tdata.htmlOption.querySelector) && document.querySelector(tdata.htmlOption.querySelector)) ?
+ document.querySelector(tdata.htmlOption.querySelector) : null
+
+ const childElement = scrollableSection ? scrollableSection : document.body
+ if (scrollableSection == null)
+ scrollableSection = document.body
+ //const childElement = scrollableSection.firstElementChild;
+ let scrollPosition = 0;
+ let viewportHeight = window.innerHeight;
+ while (scrollPosition < childElement.scrollHeight) {
+ scrollableSection.scrollBy(0, viewportHeight);
+ await new Promise(resolve => setTimeout(resolve, 500));
+ scrollPosition += viewportHeight;
+ }
+ return childElement.scrollHeight
+ }, tdata);
+
+ if (!totalHeight) {
+ throw new Error(`Unable to determine the page height ${totalHeight}. The selector may not correct or no body tag`);
+ } else {
+ console.log("Page height adjusted to:", totalHeight);
+ }
+
+ console.log("set viewport ")
+ await page.setViewport({
+ width: width_px,
+ height: totalHeight,
+ deviceScaleFactor: 2,
+ isMobile: false
+ });
+
+ ///// output to photo end here
+ if (outputMediaType === "png" || outputMediaType === "jpeg") {
+ const photoBuffer = await page.screenshot({
+ // path: 'url_pup.png',
+ fullPage: true,
+ type: outputMediaType // 'webp'
+ })
+ await browser.close();
+ return photoBuffer
+ }
+ ///// output to PDF
+ //const updatedFruit = {...afruit ,...req.body}
+ //TODO overide option from htmlTemplateOption
+ let pdfOption = tdata.htmlOption?.pdfOption??{
+ // path: './url_prop.pdf',
+ // format:"A4",
+ width: width_px,
+ height: totalHeight,
+ printBackground: true,
+ scale: 1,
+ displayHeaderFooter: false,
+ margin: { top: 5, right: 5, bottom: 5, left: 5 }
+ }
+ const buffer = await page.pdf(pdfOption);
+ await browser.close();
+ return buffer
+ } catch (e) {
+ //console.log(e)
+ throw e
+ }
+}
+
+/** javascript-obfuscator:disable
+ * @swagger
+ * /api/v1/report-template/html:
+ * post:
+ * summary: แปลหน้าเวปไปเป็น pdf , png, jpeg (ฟีเจอร์ html template ด้วย handlebars ยังไม่เสร็จ) ค่า template เป็น url ของหน้าเวปที่ต้องการ, reportName เป็นชื่อไฟล์ที่ต้องการ
+ * tags: [report-template]
+ * requestBody:
+ * required: true
+ * content:
+ * application/json:
+ * schema:
+ * $ref: '#/components/schemas/templateOption'
+ * example:
+ * template: https://bma-dashboard.frappet.synology.me/d/ANtkJay4z/4Lic4Li54LmJ4Lie4Li04LiB4Liy4Lij?orgId=1&kiosk
+ * reportName: html-report
+ * htmlOption: {"querySelector": ".scrollbar-view"}
+ * responses:
+ * 201:
+ * description: เอกสารถูกสร้างขึ้น
+ * content:
+ * application/pdf:
+ * schema:
+ * type: string
+ * format: binary
+ * image/png:
+ * schema:
+ * type: string
+ * format: binary
+ * image/jpeg:
+ * schema:
+ * type: string
+ * format: binary
+ * 400:
+ * description: format error
+ * 500:
+ * description: Server error
+ *
+ */
+htmlTemplateRoute.post("/", async function (req, res) {
+ try {
+ if (!req.headers["content-type"] || !req.headers["accept"]) throw new Error("Require header content-type, accept")
+ let inputType = mimeToExtension(req.headers["content-type"])// application/json
+ let outputMediaType = mimeToExtension(req.headers["accept"])
+ let buffer = await htmlTemplateX(req.body.template, req.body, outputMediaType)
+ res.statusCode = 201
+ res.setHeader("Content-Type", req.headers["accept"])
+ res.setHeader("Content-Disposition", `attachment;filename=${req.body.reportName}.${outputMediaType}`)
+ res.setHeader("Content-Length", buffer.length)
+ res.end(buffer)
+ } catch (ex) {
+ if (ex instanceof SyntaxError) {
+ res.statusCode = 400
+ res.statusMessage = ex.message
+ res.end(res.statusMessage)
+ console.error("report-template/html: ", ex)
+ } else {
+ res.statusCode = 500
+ res.statusMessage = "Internal Server Error during POST report-template/html"
+ res.end(res.statusMessage)
+ console.error("report-template/html: ", ex)
+ }
+ }
+})
+
diff --git a/test-run/lazy-load-puppeteer.ts b/test-run/lazy-load-puppeteer.ts
new file mode 100644
index 0000000..cdd8e8e
--- /dev/null
+++ b/test-run/lazy-load-puppeteer.ts
@@ -0,0 +1,91 @@
+import puppeteer, { Browser, PDFOptions } from 'puppeteer'
+function wait(ms: number) {
+ return new Promise(resolve => setTimeout(resolve, ms));
+}
+
+export default async function capture(browser: Browser, url: string) {
+ // Load the specified page
+ const page = await browser.newPage();
+ // if(!page) return
+ await page.goto(url, { waitUntil: 'load' });
+
+ /*
+ //force load page
+ for(let i=0; i< 50;i++){
+ await page.evaluate(_viewportHeight => {
+ window.scrollBy(0, _viewportHeight);
+ }, 1000);
+ await wait(500);
+ }*/
+
+ //force load page and find page height
+ const totalHeight = await page.evaluate(async (_viewportHeight, _ms, _loop) => {
+ const querySelector = ".scrollbar-view"
+ //const querySelector = "body"
+ let scrollableSection = document.querySelector(querySelector) ?? window
+ for (let i = 0; i < _loop; i++) {
+ scrollableSection.scrollBy(0, _viewportHeight);
+ await new Promise(resolve => setTimeout(resolve, 500));
+ }
+ return document.body.scrollHeight
+ }, 1000, 2000, 50);
+
+ // Get the height of the rendered page
+ const bodyHandle = await page.$('body');
+ if (!bodyHandle) return
+ const boundingBox = await bodyHandle.boundingBox();
+ if (!boundingBox) return
+ const { height } = boundingBox;
+ await bodyHandle.dispose();
+ console.log("height", height)
+ // Scroll one viewport at a time, pausing to let content load
+ const viewportHeight = page.viewport()?.height;
+ if (!viewportHeight) return
+ let viewportIncr = 0;
+
+
+ /*
+ while (viewportIncr + viewportHeight < boundingBox.height) {
+ await page.evaluate(_viewportHeight => {
+ window.scrollBy(0, _viewportHeight);
+ }, viewportHeight);
+ await wait(300);
+ viewportIncr = viewportIncr + viewportHeight;
+ }
+ */
+ // Scroll back to top
+ await page.evaluate(() => {
+ window.scrollTo(0, 0);
+ });
+
+ // Some extra delay to let images load
+ await wait(100);
+
+ // return await page.screenshot({ path: '.output/url_pup.png',type: 'png',fullPage: true });
+ let pdfOption: PDFOptions = {
+ path: '.output/url_prop.pdf',
+ format: "A4",
+ width: 1200,
+ height: totalHeight,
+ printBackground: true,
+ scale: 1,
+ displayHeaderFooter: false,
+ margin: { top: 5, right: 5, bottom: 5, left: 5 }
+ }
+ const buffer = await page.pdf(pdfOption);
+ return buffer
+
+
+
+}
+
+(async () => {
+ const browser = await puppeteer.launch({
+ // executablePath: '/usr/bin/chromium',
+ headless: true,
+ //args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-gpu']
+ });
+
+ await capture(browser, "https://bma-dashboard.frappet.synology.me/d/ANtkJay4z/4Lic4Li54LmJ4Lie4Li04LiB4Liy4Lij?orgId=1&kiosk")
+ await browser.close();
+})();
\ No newline at end of file