diff options
Diffstat (limited to 'src/scripts')
22 files changed, 502 insertions, 369 deletions
diff --git a/src/scripts/captcha/application-receipt.ts b/src/scripts/captcha/application-receipt.ts deleted file mode 100644 index 595195e..0000000 --- a/src/scripts/captcha/application-receipt.ts +++ /dev/null @@ -1,4 +0,0 @@ -document.addEventListener("DOMContentLoaded", () => { - $("#werter > div > form > div:nth-child(4)").hide(); - (window as any).validateForm = () => true; -}); diff --git a/src/scripts/captcha/know-your-property.ts b/src/scripts/captcha/know-your-property.ts deleted file mode 100644 index 6e11a3f..0000000 --- a/src/scripts/captcha/know-your-property.ts +++ /dev/null @@ -1,4 +0,0 @@ -document.addEventListener("DOMContentLoaded", () => { - $("#khatianPlotDiv > div:nth-child(6) > div").hide(); - (window as any).validateCaptcha = () => true; -}); diff --git a/src/scripts/common/login/captcha.ts b/src/scripts/common/login/captcha.ts new file mode 100644 index 0000000..a63aa24 --- /dev/null +++ b/src/scripts/common/login/captcha.ts @@ -0,0 +1,128 @@ +import { observeDOM } from "@/shared/observe-dom"; +import { OCRRequest, OCRResponse } from "@/shared/ocr-request"; + +/** + * Pre-processes a CAPTCHA image on a canvas by removing grayish + * background noise while preserving dark pixels, producing a clean + * binary image suitable for OCR. + * + * @param ctx - The 2D rendering context of the canvas. + * @param width - Canvas width in pixels. + * @param height - Canvas height in pixels. + */ +export function prepareCaptcha(ctx: CanvasRenderingContext2D, width: number, height: number): void { + const imgData = ctx.getImageData(0, 0, width, height); + const data = imgData.data; + const radius = 1; + + /** Returns true when all three channels are below 50 (very dark). */ + function isBlack(r: number, g: number, b: number): boolean { + return r < 50 && g < 50 && b < 50; + } + + /** Returns true when the colour is a mid-range grey (no strong hue). */ + function isGrayish(r: number, g: number, b: number): boolean { + return Math.abs(r - g) < 15 && Math.abs(g - b) < 15 && r > 100 && r < 200; + } + + /** + * Checks whether a black pixel exists within `radius` pixels of (x, y). + * Used to preserve dark structures when removing grey noise. + */ + function hasNearbyBlack(x: number, y: number): boolean { + for (let dx = -radius; dx <= radius; dx++) { + for (let dy = -radius; dy <= radius; dy++) { + const nx = x + dx; + const ny = y + dy; + if (nx >= 0 && nx < width && ny >= 0 && ny < height) { + const i = (ny * width + nx) * 4; + if (isBlack(data[i], data[i + 1], data[i + 2])) return true; + } + } + } + return false; + } + + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + const i = (y * width + x) * 4; + const [r, g, b] = [data[i], data[i + 1], data[i + 2]]; + if (isGrayish(r, g, b) && !hasNearbyBlack(x, y)) { + data[i] = data[i + 1] = data[i + 2] = 255; + } + } + } + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + const i = (y * width + x) * 4; + const [r, g, b] = [data[i], data[i + 1], data[i + 2]]; + + if (isGrayish(r, g, b)) { + data[i] = data[i + 1] = data[i + 2] = 0; // make black + } + } + } + ctx.putImageData(imgData, 0, 0); +} + +observeDOM(() => { + const loginFormElement = document.querySelector<HTMLFormElement>("#loginform"); + + if (loginFormElement) { + const captchaImg = loginFormElement.querySelector<HTMLImageElement>("#captchaImg"); + const captchaInput = loginFormElement.querySelector<HTMLInputElement>("#txtInput"); + + if (!captchaImg || !captchaInput) + return false; + + // Wait for image to load to get correct dimensions + captchaImg.addEventListener("load", function(): void { + const { width, height } = captchaImg; + + // 1. Create canvas + const canvas = document.createElement("canvas"); + canvas.width = width; + canvas.height = height; + canvas.style.display = "block"; + + // 2. Copy styles (optional) + canvas.style.cssText = getComputedStyle(captchaImg).cssText; + canvas.style.display = "none"; + + // 3. Insert canvas before image + captchaImg.parentNode?.querySelectorAll("canvas") + .forEach((c) => { c.remove(); }); + captchaImg.parentNode?.insertBefore(canvas, captchaImg); + + // 4. Hide image + captchaImg.style.display = "none"; + + // 5. Draw the image onto the canvas + const ctx = canvas.getContext("2d"); + if (!ctx) + return; + ctx.drawImage(captchaImg, 0, 0); + prepareCaptcha(ctx, canvas.width, canvas.height); + canvas.style.display = ""; + const dataURL = canvas.toDataURL("image/png"); + + // Perform OCR + chrome.runtime.sendMessage<OCRRequest, OCRResponse>( + { type: "OCR", dataURL }, + function(ocrResponse): void { + if (ocrResponse.success) { + const textResult = ocrResponse.text.trim(); + if (ocrResponse.confidence < 80 || textResult.length !== 6) + // Reset the captcha if confidence is low + captchaImg.src = `generateCaptcha?${new Date().getTime().toString()}`; + else + captchaInput.value = textResult; + } + } + ); + }); + } + + // This runs forever + return false; +}); diff --git a/src/scripts/common/login/index.ts b/src/scripts/common/login/index.ts new file mode 100644 index 0000000..95b9c5b --- /dev/null +++ b/src/scripts/common/login/index.ts @@ -0,0 +1 @@ +import "@/scripts/common/login/captcha"; diff --git a/src/scripts/common/stop-blocking.ts b/src/scripts/common/stop-blocking.ts new file mode 100644 index 0000000..8588834 --- /dev/null +++ b/src/scripts/common/stop-blocking.ts @@ -0,0 +1,52 @@ +/** + * Proxies jQuery's `.bind()` (for the `"cut copy paste"` event) and + * `.keydown()` so that the website's copy/paste/right-click blocking + * is disabled. Reverts the prototypes after patching to avoid + * interfering with other code. + */ + +type JQueryBindFn = ( + eventName: string, + handler: (event: JQuery.Event) => unknown +) => JQuery; + +type JQueryKeydownFn = ( + handler: (event: JQuery.Event) => unknown +) => JQuery; + +type JQueryPrototypeShape = { + bind: JQueryBindFn; + keydown: JQueryKeydownFn +}; + +const jQueryPrototype = $.prototype as unknown as JQueryPrototypeShape; + +document.addEventListener("DOMContentLoaded", () => { + const proxiedBind = jQueryPrototype.bind; + /** Proxies jQuery `.bind()` to no-op the `"cut copy paste"` event. */ + jQueryPrototype.bind = function( + this: JQuery, + ...args: Parameters<JQueryBindFn> + ): JQuery { + const [eventName, handler] = args; + const noop = (): undefined => undefined; + return proxiedBind.call( + this, + eventName, + eventName.trim() === "cut copy paste" ? noop : handler + ); + }; + + const proxiedKeydown = jQueryPrototype.keydown; + /** Proxies jQuery `.keydown()` so all key presses are allowed. */ + jQueryPrototype.keydown = function( + this: JQuery, + ..._args: Parameters<JQueryKeydownFn> + ): JQuery { + return proxiedKeydown.call(this, () => true); + }; + + jQueryPrototype.bind = proxiedBind; + jQueryPrototype.keydown = proxiedKeydown; + document.oncontextmenu = (): boolean => true; +}); diff --git a/src/scripts/functionality/mutation-application.ts b/src/scripts/functionality/mutation-application.ts deleted file mode 100644 index 96aec8a..0000000 --- a/src/scripts/functionality/mutation-application.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { PDFDocument, StandardFonts } from "pdf-lib"; -import { observeDOM } from "@/shared/observe-dom"; - -const sanghaFacilitationCentreBannerUrl = "$l{ /assets/sangha-facilitation-centre-banner.jpg }l$"; - -document.addEventListener("DOMContentLoaded", () => { - observeDOM(() => { - const formElement = $("#form_MutationApplication > div > div:nth-child(14) > div.col-sm-2.btreset > form"); - const submitButtonElement = $("#btnDeclareForm"); - if (formElement.length > 0 && submitButtonElement.length > 0) { - formElement.removeAttr("action"); - submitButtonElement.click(e => { - e.preventDefault(); - - (async () => { - const pdfDoc = await PDFDocument.load(await fetch("/BanglarBhumi/MutationDeclarationForm.action", { - method: "post", - credentials: "same-origin", - headers: { - "Content-Type": "application/x-www-form-urlencoded" - }, - body: "hdnAppNo=" - }).then(res => res.arrayBuffer())); - const pages = pdfDoc.getPages(); - const lastPage = pages[pages.length - 1]; - const { width, height } = lastPage.getSize(); - const bannerImage = await pdfDoc.embedJpg(await fetch(sanghaFacilitationCentreBannerUrl).then(res => res.arrayBuffer())); - const bannerImageHeight = width * (bannerImage.height / bannerImage.width); - - lastPage.drawImage(bannerImage, { - x: 0, - y: 0, - width: width, - height: bannerImageHeight - }); - - lastPage.drawText(pdfDoc.getCreationDate()!.toLocaleString(), { - x: 10, - y: bannerImageHeight, - size: 12, - font: await pdfDoc.embedFont(StandardFonts.TimesRoman) - }); - - const link = document.createElement("a"); - link.href = window.URL.createObjectURL(new Blob([new Uint8Array(await pdfDoc.save())], {type: "application/pdf"})); - link.download = "Declaration.pdf"; - link.click(); - })(); - }); - - return true; - } else { - return false; - } - }); -}); diff --git a/src/scripts/functionality/sheet-map/index.ts b/src/scripts/functionality/sheet-map/index.ts deleted file mode 100644 index 71228fe..0000000 --- a/src/scripts/functionality/sheet-map/index.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { generateWebPage } from "@/shared/generate-web-page"; -import getDownloadMapPDFPageContent from "@/scripts/functionality/sheet-map/download-map-pdf-page-content.html"; -import getMapPlotPolygonPathElement from "@/scripts/functionality/sheet-map/map-plot-polygon-path-element.html"; -import getMapPlotNumberLabelTextElement from "@/scripts/functionality/sheet-map/map-plot-number-label-text-element.html"; -import getPlotInformationElement from "@/scripts/functionality/sheet-map/plot-information-element.html"; - -interface PlotPolygon { - plotArea: number; - plotno: string; - polygon: string; -}; - -interface PlotNumberLabel { - plotno: string; - point: string; -} - -const headerElement = document.querySelector("#headerTable > tbody > tr")!; -const plotNumberInput = document.querySelector<HTMLInputElement>("#txtPlotNo")!; -const plotInformation = document.createElement("div") as HTMLDivElement; -let plotPolygonPathElements = ""; -let plotNumberLabelTextElements = ""; - -document.body.appendChild(plotInformation); - -const createHeaderButton = (text: string) => { - const buttonContainer = document.createElement("td"); - const button = document.createElement("button"); - button.innerHTML = text; - button.style.display = "none"; - buttonContainer.appendChild(button); - headerElement.appendChild(buttonContainer); - return button; -}; - -const setPlotInformation = (plotPolygon: PlotPolygon | null | undefined = null) => { - plotInformation.innerHTML = plotPolygon ? getPlotInformationElement({ - area: (plotPolygon.plotArea/1000).toFixed(3), - plotNumber: plotPolygon.plotno - }) : getPlotInformationElement({ - area: "0.000", - plotNumber: "" - }); -}; - -const downloadPDF = (labelPoints: boolean = true) => { - const _getMapDetail = (i: number) => { - const detail = document.querySelector(`#headerTable > tbody > tr > td:nth-child(${i})`)! - .innerHTML - .split("[")[1] - .trim(); - return detail.substr(0, detail.length - 1); - }; - generateWebPage( - getDownloadMapPDFPageContent({ - district: _getMapDetail(1), - block: _getMapDetail(2), - mouza: _getMapDetail(3), - mapContent: plotPolygonPathElements + (labelPoints ? plotNumberLabelTextElements : "") - }), - "map" - ); -}; - -const downloadPDFButton = createHeaderButton("SAVE PDF"); -const downloadPDFNoLabelButton = createHeaderButton("SAVE PDF (NO LABEL)"); - -downloadPDFButton.addEventListener("click", e => { - e.preventDefault(); - downloadPDF(); -}); - -downloadPDFNoLabelButton.addEventListener("click", e => { - e.preventDefault(); - downloadPDF(false); -}); - -setPlotInformation(); - -(async () => { - const searchParams = new URLSearchParams(window.location.search); - const response1 = await fetch( - "/BanglarBhumi/sheetMap_populateLayerData?lstSheetNo=" + searchParams.get("lstSheetNo"), - { - method: "POST" - }); - const plotPolygonList: PlotPolygon[] = (await response1.json()).features; - const response2 = await fetch( - "/BanglarBhumi/sheetMap_populateCentroidData?lstSheetNo=" + searchParams.get("lstSheetNo"), - { - method: "POST" - }); - const plotNumberLabelList: PlotNumberLabel[] = (await response2.json()).features; - let plotPolygons: { [key: string]: PlotPolygon } = {}; - - for (const plotPolygon of plotPolygonList) { - const plotPolygonPoints = plotPolygon.polygon.split("(((")[1].split(")))")[0]; - plotPolygonPathElements += getMapPlotPolygonPathElement({ - polygonVertices: plotPolygonPoints - }); - } - - for (const plotNumberLabel of plotNumberLabelList) { - const plotNumberLabelPoint = plotNumberLabel.point.split("(")[1].split(")")[0].split(" "); - plotNumberLabelTextElements += getMapPlotNumberLabelTextElement({ - x: plotNumberLabelPoint[0], - y: plotNumberLabelPoint[1], - plotNumber: plotNumberLabel.plotno - }); - } - - downloadPDFButton.style.display = "inherit"; - downloadPDFNoLabelButton.style.display = "inherit"; - - document.querySelector("#btnSearch")!.addEventListener("click", () => { - let plotPolygon; - for (const p of plotPolygonList) { - if (p.plotno === plotNumberInput.value) { - plotPolygon = p; - break; - } - } - setPlotInformation(plotPolygon); - }); - - await new Promise<void>((resolve, reject) => { - const plotElementIdsControl = setInterval(() => { - let i = 0; - let plotElements = document.querySelector("#OpenLayers_Layer_Vector_25_vroot")?.children || []; - if (plotElements.length) { - clearInterval(plotElementIdsControl); - for (let i = 0; i < plotElements.length; i++) { - plotPolygons[plotElements[i].id] = plotPolygonList[i]; - } - resolve(); - } - }, 50); - }); - - setInterval(() => { - for (const plotElementId in plotPolygons) { - const e = document.getElementById(plotElementId); - if (e) { - e.removeEventListener("click", handlePlotClick); - e.addEventListener("click", handlePlotClick); - } - } - }, 300); - - const handlePlotClick = (e: MouseEvent) => { - document.querySelectorAll("path").forEach(e => { - e.setAttribute("fill", "#ffcc66"); - }); - - const target = e.target as SVGPathElement; - - target.setAttribute("fill", "#8aeeef"); - setPlotInformation(plotPolygons[target.id]); - }; -})(); diff --git a/src/scripts/login/index.ts b/src/scripts/login/index.ts deleted file mode 100644 index bed05f9..0000000 --- a/src/scripts/login/index.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { interceptPost, interceptGet } from "@/shared/intercept-jquery-ajax"; - -function prepareCaptcha(ctx: CanvasRenderingContext2D, width: number, height: number) { - const imgData = ctx.getImageData(0, 0, width, height); - const data = imgData.data; - const radius = 1; - - function isBlack(r: number, g: number, b: number) { - return r < 50 && g < 50 && b < 50; - } - - function isGrayish(r: number, g: number, b: number) { - return Math.abs(r - g) < 15 && Math.abs(g - b) < 15 && r > 100 && r < 200; - } - - function hasNearbyBlack(x: number, y: number) { - for (let dx = -radius; dx <= radius; dx++) { - for (let dy = -radius; dy <= radius; dy++) { - const nx = x + dx; - const ny = y + dy; - if (nx >= 0 && nx < width && ny >= 0 && ny < height) { - const i = (ny * width + nx) * 4; - if (isBlack(data[i], data[i + 1], data[i + 2])) return true; - } - } - } - return false; - } - - for (let y = 0; y < height; y++) { - for (let x = 0; x < width; x++) { - const i = (y * width + x) * 4; - const [r, g, b] = [data[i], data[i + 1], data[i + 2]]; - if (isGrayish(r, g, b) && !hasNearbyBlack(x, y)) { - data[i] = data[i + 1] = data[i + 2] = 255; - } - } - } - for (let y = 0; y < height; y++) { - for (let x = 0; x < width; x++) { - const i = (y * width + x) * 4; - const [r, g, b] = [data[i], data[i + 1], data[i + 2]]; - - if (isGrayish(r, g, b)) { - data[i] = data[i + 1] = data[i + 2] = 0; // make black - } - } - } - ctx.putImageData(imgData, 0, 0); -} - -const observer = new MutationObserver((mutationsList, obs) => { - for (const mutation of mutationsList) { - for (const node of mutation.addedNodes) { - if ((node as any).nodeType === 1 && (node as any).id === "loginform") { - const img = (node as HTMLFormElement).querySelector("#captchaImg") as HTMLImageElement; - const captchaInput = (node as HTMLFormElement).querySelector("#txtInput") as HTMLInputElement; - - // Wait for image to load to get correct dimensions - img.addEventListener("load", async () => { - const { width, height } = img; - - // 1. Create canvas - const canvas = document.createElement("canvas"); - canvas.width = width; - canvas.height = height; - canvas.style.display = "block"; - - // 2. Copy styles (optional) - canvas.style.cssText = getComputedStyle(img).cssText; - canvas.style.display = "none"; - - // 3. Insert canvas before image - img.parentNode!.querySelectorAll("canvas").forEach(c => c.remove()); - img.parentNode!.insertBefore(canvas, img); - - // 4. Hide image - img.style.display = "none"; - - // 5. Draw the image onto the canvas - const ctx = canvas.getContext("2d")!; - ctx.drawImage(img, 0, 0); - prepareCaptcha(ctx, canvas.width, canvas.height); - canvas.style.display = ""; - const dataURL = canvas.toDataURL("image/png"); - - // Perform OCR - chrome.runtime.sendMessage( - { type: "OCR", dataURL }, - ({ success, text, confidence }) => { - if (success) { - const textResult = text.trim(); - if (confidence < 80 || textResult.length !== 6) { - // Reset the captcha if confidence is low - img.src = "generateCaptcha?" + new Date().getTime(); - } else { - captchaInput.value = textResult; - } - } - }, - ); - }); - } - } - } -}); - -// Start observing the body -observer.observe(document.body, { childList: true, subtree: true }); diff --git a/src/scripts/pages/application-receipt/captcha.ts b/src/scripts/pages/application-receipt/captcha.ts new file mode 100644 index 0000000..94e6b0e --- /dev/null +++ b/src/scripts/pages/application-receipt/captcha.ts @@ -0,0 +1,13 @@ +/** + * Hides the CAPTCHA section on the application-receipt page and + * stubs the form validation so the user can proceed without + * solving a CAPTCHA. + */ +type WindowApplicationReceipt = { + validateForm?: () => boolean +} & Window; + +document.addEventListener("DOMContentLoaded", () => { + $("#werter > div > form > div:nth-child(4)").hide(); + (window as WindowApplicationReceipt).validateForm = () => true; +}); diff --git a/src/scripts/pages/application-receipt/index.ts b/src/scripts/pages/application-receipt/index.ts new file mode 100644 index 0000000..5a8d0a6 --- /dev/null +++ b/src/scripts/pages/application-receipt/index.ts @@ -0,0 +1 @@ +import "@/scripts/pages/application-receipt/captcha"; diff --git a/src/scripts/pages/know-your-property/captcha.ts b/src/scripts/pages/know-your-property/captcha.ts new file mode 100644 index 0000000..e304342 --- /dev/null +++ b/src/scripts/pages/know-your-property/captcha.ts @@ -0,0 +1,13 @@ +/** + * Hides the CAPTCHA section on the Know-Your-Property page and + * stubs the captcha validation so the user can proceed without + * solving a CAPTCHA. + */ +type WindowKnowYourProperty = { + validateCaptcha?: () => boolean +} & Window; + +document.addEventListener("DOMContentLoaded", () => { + $("#khatianPlotDiv > div:nth-child(6) > div").hide(); + (window as WindowKnowYourProperty).validateCaptcha = () => true; +}); diff --git a/src/scripts/pages/know-your-property/index.ts b/src/scripts/pages/know-your-property/index.ts new file mode 100644 index 0000000..d4d7f63 --- /dev/null +++ b/src/scripts/pages/know-your-property/index.ts @@ -0,0 +1 @@ +import "@/scripts/pages/know-your-property/captcha"; diff --git a/src/scripts/pages/mutation-application/download-button.ts b/src/scripts/pages/mutation-application/download-button.ts new file mode 100644 index 0000000..6463079 --- /dev/null +++ b/src/scripts/pages/mutation-application/download-button.ts @@ -0,0 +1,63 @@ +import { PDFDocument, StandardFonts } from "pdf-lib"; + +import { observeDOM } from "@/shared/observe-dom"; + +const sanghaFacilitationCentreBannerUrl = "$l{ /assets/sangha-facilitation-centre-banner.jpg }l$"; + +/** + * On the Mutation Application form, removes the form action (to prevent + * the default server-side submit) and attaches a custom click handler + * that fetches the declaration PDF, overlays a banner image and date + * stamp on the last page, then triggers a download. + */ +observeDOM(() => { + const formElement = $("#form_MutationApplication > div > div:nth-child(14) > div.col-sm-2.btreset > form"); + const submitButtonElement = $("#btnDeclareForm"); + if (formElement.length > 0 && submitButtonElement.length > 0) { + formElement.removeAttr("action"); + submitButtonElement.on("click", (submitButtonClickEvent) => { + submitButtonClickEvent.preventDefault(); + + void (async function(): Promise<void> { + const pdfDoc = await PDFDocument.load(await fetch("/BanglarBhumi/MutationDeclarationForm.action", { + method: "post", + credentials: "same-origin", + headers: { + "Content-Type": "application/x-www-form-urlencoded" + }, + body: "hdnAppNo=" + }).then((res) => res.arrayBuffer())); + const pages = pdfDoc.getPages(); + const lastPage = pages[pages.length - 1]; + const { width } = lastPage.getSize(); + const bannerImage + = await pdfDoc.embedJpg(await fetch(sanghaFacilitationCentreBannerUrl) + .then((res) => res.arrayBuffer())); + const bannerImageHeight = width * (bannerImage.height / bannerImage.width); + + lastPage.drawImage(bannerImage, { + x: 0, + y: 0, + width: width, + height: bannerImageHeight + }); + + lastPage.drawText(pdfDoc.getCreationDate()?.toLocaleString() ?? "", { + x: 10, + y: bannerImageHeight, + size: 12, + font: await pdfDoc.embedFont(StandardFonts.TimesRoman) + }); + + const link = document.createElement("a"); + link.href = window.URL.createObjectURL(new Blob([new Uint8Array(await pdfDoc.save())], { type: "application/pdf" })); + link.download = "Declaration.pdf"; + link.click(); + })(); + }); + + return true; + } else { + return false; + } +}); diff --git a/src/scripts/pages/mutation-application/index.ts b/src/scripts/pages/mutation-application/index.ts new file mode 100644 index 0000000..93e07ad --- /dev/null +++ b/src/scripts/pages/mutation-application/index.ts @@ -0,0 +1 @@ +import "@/scripts/pages/mutation-application/download-button"; diff --git a/src/scripts/functionality/sheet-map/download-map-pdf-page-content.html b/src/scripts/pages/sheet-map/download-map-pdf-page-content.html index da765ec..da765ec 100644 --- a/src/scripts/functionality/sheet-map/download-map-pdf-page-content.html +++ b/src/scripts/pages/sheet-map/download-map-pdf-page-content.html diff --git a/src/scripts/pages/sheet-map/index.ts b/src/scripts/pages/sheet-map/index.ts new file mode 100644 index 0000000..c16a2ae --- /dev/null +++ b/src/scripts/pages/sheet-map/index.ts @@ -0,0 +1,197 @@ +import getDownloadMapPDFPageContent from "@/scripts/pages/sheet-map/download-map-pdf-page-content.html"; +import getMapPlotNumberLabelTextElement from "@/scripts/pages/sheet-map/map-plot-number-label-text-element.html"; +import getMapPlotPolygonPathElement from "@/scripts/pages/sheet-map/map-plot-polygon-path-element.html"; +import getPlotInformationElement from "@/scripts/pages/sheet-map/plot-information-element.html"; +import { generateWebPage } from "@/shared/generate-web-page"; + +type PlotPolygon = { + plotArea: number; + plotno: string; + polygon: string +}; + +type PlotNumberLabel = { + plotno: string; + point: string +}; + +const headerElement = document.querySelector<HTMLTableRowElement>("#headerTable > tbody > tr"); +const plotNumberInput = document.querySelector<HTMLInputElement>("#txtPlotNo"); +const plotInformation = document.createElement("div"); +let plotPolygonPathElements = ""; +let plotNumberLabelTextElements = ""; + +document.body.appendChild(plotInformation); + +/** + * Creates a header toolbar button and appends it to the page header. + * The button starts hidden and is shown when data is ready. + * + * @param text - The button label text. + * @returns The created button element. + */ +function createHeaderButton(text: string): HTMLButtonElement { + const buttonContainer = document.createElement("td"); + const button = document.createElement("button"); + button.innerHTML = text; + button.style.display = "none"; + buttonContainer.appendChild(button); + if (headerElement) + headerElement.appendChild(buttonContainer); + return button; +}; + +/** + * Displays the area and plot number for the given polygon in a floating + * info panel, or shows "0.000" / empty if no polygon is selected. + * + * @param plotPolygon - The selected plot's data, or `null`/`undefined` to clear. + */ +function setPlotInformation(plotPolygon: PlotPolygon | null | undefined = null): void { + plotInformation.innerHTML = plotPolygon + ? getPlotInformationElement({ + area: (plotPolygon.plotArea / 1000).toFixed(3), + plotNumber: plotPolygon.plotno + }) + : getPlotInformationElement({ + area: "0.000", + plotNumber: "" + }); +}; + +/** + * Opens a new window with a printable PDF view of the map, optionally + * including plot-number labels. + * + * @param labelPoints - Whether to include plot number text labels. + */ +function downloadPDF(labelPoints = true): void { + /** Extracts a detail value (district/block/mouza) from the header table by column index. */ + function _getMapDetail(i: number): string { + const detail = (document.querySelector<HTMLTableCellElement>(`#headerTable > tbody > tr > td:nth-child(${i.toString()})`)?.innerHTML ?? "") + .split("[")[1] + .trim(); + return detail.substring(0, detail.length - 1); + }; + generateWebPage( + getDownloadMapPDFPageContent({ + district: _getMapDetail(1), + block: _getMapDetail(2), + mouza: _getMapDetail(3), + mapContent: plotPolygonPathElements + (labelPoints ? plotNumberLabelTextElements : "") + }), + "map" + ); +}; + +const downloadPDFButton = createHeaderButton("SAVE PDF"); +const downloadPDFNoLabelButton = createHeaderButton("SAVE PDF (NO LABEL)"); + +downloadPDFButton.addEventListener("click", (e) => { + e.preventDefault(); + downloadPDF(); +}); + +downloadPDFNoLabelButton.addEventListener("click", (e) => { + e.preventDefault(); + downloadPDF(false); +}); + +setPlotInformation(); + +void (async function(): Promise<void> { + const searchParams = new URLSearchParams(window.location.search); + const sheetMapLayerDataResponse = await fetch( + `/BanglarBhumi/sheetMap_populateLayerData?lstSheetNo=${searchParams.get("lstSheetNo")?.toString() ?? ""}`, + { + method: "POST" + }); + type SheetMapLayerDataResponseType = { + features: PlotPolygon[] + }; + const sheetMapLayerDataResponseJson + = await sheetMapLayerDataResponse.json() as SheetMapLayerDataResponseType; + const plotPolygonList: PlotPolygon[] = sheetMapLayerDataResponseJson.features; + const sheetMapCentroidDataResponse = await fetch( + `/BanglarBhumi/sheetMap_populateCentroidData?lstSheetNo=${searchParams.get("lstSheetNo")?.toString() ?? ""}`, + { + method: "POST" + }); + type SheetMapCentroidDataResponseType = { + features: PlotNumberLabel[] + }; + const sheetMapCentroidDataResponseJson + = await sheetMapCentroidDataResponse.json() as SheetMapCentroidDataResponseType; + const plotNumberLabelList: PlotNumberLabel[] = sheetMapCentroidDataResponseJson.features; + const plotPolygons: Record<string, PlotPolygon> = {}; + + for (const plotPolygon of plotPolygonList) { + const plotPolygonPoints = plotPolygon.polygon.split("(((")[1].split(")))")[0]; + plotPolygonPathElements += getMapPlotPolygonPathElement({ + polygonVertices: plotPolygonPoints + }); + } + + for (const plotNumberLabel of plotNumberLabelList) { + const plotNumberLabelPoint = plotNumberLabel.point.split("(")[1].split(")")[0].split(" "); + plotNumberLabelTextElements += getMapPlotNumberLabelTextElement({ + x: plotNumberLabelPoint[0], + y: plotNumberLabelPoint[1], + plotNumber: plotNumberLabel.plotno + }); + } + + downloadPDFButton.style.display = "inherit"; + downloadPDFNoLabelButton.style.display = "inherit"; + + document.querySelector<HTMLButtonElement>("#btnSearch")?.addEventListener("click", () => { + let plotPolygon; + for (const p of plotPolygonList) { + if (p.plotno === plotNumberInput?.value) { + plotPolygon = p; + break; + } + } + setPlotInformation(plotPolygon); + }); + + await new Promise<void>((resolve, _reject) => { + const plotElementIdsControl = setInterval(() => { + const plotElements = document.querySelector("#OpenLayers_Layer_Vector_25_vroot")?.children ?? []; + if (plotElements.length) { + clearInterval(plotElementIdsControl); + for (let i = 0; i < plotElements.length; i++) { + plotPolygons[plotElements[i].id] = plotPolygonList[i]; + } + resolve(); + } + }, 50); + }); + + setInterval(() => { + for (const plotElementId in plotPolygons) { + const e = document.getElementById(plotElementId); + if (e) { + e.removeEventListener("click", handlePlotClick); + e.addEventListener("click", handlePlotClick); + } + } + }, 300); + + /** + * Highlights the clicked plot on the SVG map and shows its + * information in the info panel. + * + * @param e - The mouse click event. + */ + function handlePlotClick(e: MouseEvent): void { + document.querySelectorAll("path").forEach((e) => { + e.setAttribute("fill", "#ffcc66"); + }); + + const target = e.target as SVGPathElement; + + target.setAttribute("fill", "#8aeeef"); + setPlotInformation(plotPolygons[target.id]); + }; +})(); diff --git a/src/scripts/functionality/sheet-map/map-plot-number-label-text-element.html b/src/scripts/pages/sheet-map/map-plot-number-label-text-element.html index d220367..d220367 100644 --- a/src/scripts/functionality/sheet-map/map-plot-number-label-text-element.html +++ b/src/scripts/pages/sheet-map/map-plot-number-label-text-element.html diff --git a/src/scripts/functionality/sheet-map/map-plot-polygon-path-element.html b/src/scripts/pages/sheet-map/map-plot-polygon-path-element.html index d4d38a6..d4d38a6 100644 --- a/src/scripts/functionality/sheet-map/map-plot-polygon-path-element.html +++ b/src/scripts/pages/sheet-map/map-plot-polygon-path-element.html diff --git a/src/scripts/functionality/sheet-map/plot-information-element.html b/src/scripts/pages/sheet-map/plot-information-element.html index fd740a8..fd740a8 100644 --- a/src/scripts/functionality/sheet-map/plot-information-element.html +++ b/src/scripts/pages/sheet-map/plot-information-element.html diff --git a/src/scripts/functionality/view-khatian/download-information-pdf-page-content.html b/src/scripts/pages/view-khatian/download-information-pdf-page-content.html index 129608e..129608e 100644 --- a/src/scripts/functionality/view-khatian/download-information-pdf-page-content.html +++ b/src/scripts/pages/view-khatian/download-information-pdf-page-content.html diff --git a/src/scripts/functionality/view-khatian/index.ts b/src/scripts/pages/view-khatian/index.ts index 93d03cf..bb44a38 100644 --- a/src/scripts/functionality/view-khatian/index.ts +++ b/src/scripts/pages/view-khatian/index.ts @@ -1,6 +1,6 @@ +import getDownloadInformationPDFPageContent from "@/scripts/pages/view-khatian/download-information-pdf-page-content.html"; import { generateWebPage } from "@/shared/generate-web-page"; -import { interceptPost } from "@/shared/intercept-jquery-ajax"; -import getDownloadInformationPDFPageContent from "@/scripts/functionality/view-khatian/download-information-pdf-page-content.html"; +import { addPostInterceptor, JQueryAjaxArgs } from "@/shared/intercept-jquery-ajax"; const submitButtonElement = $("#khbutton"); const separatorElement = $("#bodycover > div > form > hr:nth-child(2)"); @@ -14,18 +14,28 @@ const styles = Array.from(submitButtonElementComputedStyles) "" ); -const getValueOfSelectElement = (selector: string) => { - const element = document.querySelector<HTMLSelectElement>(selector)!; - return element.options[element.selectedIndex].text; +/** + * Reads the currently selected text from a `<select>` element. + * + * @param selector - CSS selector for the select element. + * @returns The text of the selected option. + */ +function getValueOfSelectElement(selector: string): string { + const element = document.querySelector<HTMLSelectElement>(selector); + return element?.options[element.selectedIndex].text ?? ""; }; let isPlotInformation: boolean | null = null; -const downloadInformationPDF = () => { +/** + * Opens a new window with a printable PDF view of the current khatian + * or plot details, including district/block/mouza info. + */ +function downloadInformationPDF(): void { generateWebPage( getDownloadInformationPDFPageContent({ isPlotInformation: !!isPlotInformation, - details: $(isPlotInformation ? "#plotdetails" : "#khdetails").html(), + details: $(isPlotInformation ? "#plotdetails" : "#khdetails").html() || "", district: getValueOfSelectElement("#lstDistrictCode1"), block: getValueOfSelectElement("#lstBlockCode1"), mouza: getValueOfSelectElement("#lstMouzaList") @@ -36,11 +46,18 @@ const downloadInformationPDF = () => { ); }; -const showDownloadButton = (args: any) => { +/** + * Intercepts the success callback of a jQuery AJAX call. When a valid + * details table is returned, it inserts a "Download PDF" button; + * otherwise it removes an existing button. + * + * @param args - The `arguments` object from the intercepted `$.post` call. + */ +function showDownloadButton(args: JQueryAjaxArgs): void { if (isPlotInformation !== null) { const callback = args[2]; - args[2] = (data: any) => { - callback(data); + args[2] = (...data) => { + callback(...data); const tableElement = $(isPlotInformation ? "#plotdetails > div:nth-child(1) > div:nth-child(4) > table > tbody" : "#khdetails > table > tbody > tr > td:nth-child(1) > div:nth-child(5) > table > tbody" @@ -56,23 +73,23 @@ const showDownloadButton = (args: any) => { downloadPDFButton[0].style.cssText = styles; downloadPDFButton.css("width", "auto"); downloadPDFButton.css("margin-bottom", "1rem"); - downloadPDFButton.click(downloadInformationPDF); + downloadPDFButton.on("click", downloadInformationPDF); formElement[0].insertBefore(downloadPDFButton[0], separatorElement[0]); } } else if (downloadPDFButton) { - ($(downloadPDFButton as any) as any).remove(); + ($(downloadPDFButton)).remove(); isPlotInformation = null; downloadPDFButton = null; } - } + }; } }; -interceptPost("plotDetailsAction_LandInfo.action", args => { +addPostInterceptor("plotDetailsAction_LandInfo.action", (args: JQueryAjaxArgs) => { isPlotInformation = true; showDownloadButton(args); }); -interceptPost("khDetailsAction_LandInfo.action", args => { +addPostInterceptor("khDetailsAction_LandInfo.action", (args: JQueryAjaxArgs) => { isPlotInformation = false; showDownloadButton(args); }); diff --git a/src/scripts/stop-blocking.ts b/src/scripts/stop-blocking.ts deleted file mode 100644 index b8f4783..0000000 --- a/src/scripts/stop-blocking.ts +++ /dev/null @@ -1,21 +0,0 @@ -document.addEventListener("DOMContentLoaded", () => { - const proxiedBind = $.prototype.bind; - $.prototype.bind = function() { - if (arguments[0].trim() === "cut copy paste") { - arguments[1] = (_: any) => {}; - } - - return proxiedBind.apply(this, Array.from(arguments) as any); - } - - const proxiedKeydown = $.prototype.keydown; - $.prototype.keydown = function() { - arguments[0] = (_: any) => true; - - return proxiedKeydown.apply(this, Array.from(arguments) as any); - } - - $.prototype.bind = proxiedBind; - $.prototype.keydown = proxiedKeydown; - document.oncontextmenu = () => true; -}); |
