import { type ClassValue, clsx } from "clsx" import { SyntheticEvent } from "react" import { twMerge } from "tailwind-merge" import { LineGroup } from "./types" import { BRUSH_COLOR } from "./const" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } export function keepGUIAlive() { async function getRequest(url = "") { const response = await fetch(url, { method: "GET", cache: "no-cache", }) return response.json() } const keepAliveServer = () => { const url = document.location const route = "/flaskwebgui-keep-server-alive" getRequest(url + route).then((data) => { return data }) } const intervalRequest = 3 * 1000 keepAliveServer() setInterval(keepAliveServer, intervalRequest) } export function dataURItoBlob(dataURI: string) { const mime = dataURI.split(",")[0].split(":")[1].split(";")[0] const binary = atob(dataURI.split(",")[1]) const array = [] for (let i = 0; i < binary.length; i += 1) { array.push(binary.charCodeAt(i)) } return new Blob([new Uint8Array(array)], { type: mime }) } export function loadImage(image: HTMLImageElement, src: string) { return new Promise((resolve, reject) => { const initSRC = image.src const img = image img.onload = resolve img.onerror = (err) => { img.src = initSRC reject(err) } img.src = src }) } export function canvasToImage( canvas: HTMLCanvasElement ): Promise { return new Promise((resolve, reject) => { const image = new Image() image.addEventListener("load", () => { resolve(image) }) image.addEventListener("error", (error) => { reject(error) }) image.src = canvas.toDataURL() }) } export function fileToImage(file: File): Promise { return new Promise((resolve, reject) => { const reader = new FileReader() reader.onload = () => { const image = new Image() image.onload = () => { resolve(image) } image.onerror = () => { reject("无法加载图像。") } image.src = reader.result as string } reader.onerror = () => { reject("无法读取文件。") } reader.readAsDataURL(file) }) } export function srcToFile(src: string, fileName: string, mimeType: string) { return fetch(src) .then(function (res) { return res.arrayBuffer() }) .then(function (buf) { return new File([buf], fileName, { type: mimeType }) }) } export async function askWritePermission() { try { // The clipboard-write permission is granted automatically to pages // when they are the active tab. So it's not required, but it's more safe. const { state } = await navigator.permissions.query({ name: "clipboard-write" as PermissionName, }) return state === "granted" } catch (error) { // Browser compatibility / Security error (ONLY HTTPS) ... return false } } function canvasToBlob(canvas: HTMLCanvasElement, mime: string): Promise { return new Promise((resolve, reject) => canvas.toBlob(async (d) => { if (d) { resolve(d) } else { reject(new Error("Expected toBlob() to be defined")) } }, mime) ) } const setToClipboard = async (blob: any) => { const data = [new ClipboardItem({ [blob.type]: blob })] await navigator.clipboard.write(data) } export function isRightClick(ev: SyntheticEvent) { const mouseEvent = ev.nativeEvent as MouseEvent return mouseEvent.button === 2 } export function isMidClick(ev: SyntheticEvent) { const mouseEvent = ev.nativeEvent as MouseEvent return mouseEvent.button === 1 } export async function copyCanvasImage(canvas: HTMLCanvasElement) { const blob = await canvasToBlob(canvas, "image/png") try { await setToClipboard(blob) } catch { console.log("Copy image failed!") } } export function downloadImage(uri: string, name: string) { const link = document.createElement("a") link.href = uri link.download = name // this is necessary as link.click() does not work on the latest firefox link.dispatchEvent( new MouseEvent("click", { bubbles: true, cancelable: true, view: window, }) ) setTimeout(() => { // For Firefox it is necessary to delay revoking the ObjectURL // window.URL.revokeObjectURL(base64) link.remove() }, 100) } export function mouseXY(ev: SyntheticEvent) { const mouseEvent = ev.nativeEvent as MouseEvent return { x: mouseEvent.offsetX, y: mouseEvent.offsetY } } export function drawLines( ctx: CanvasRenderingContext2D, lines: LineGroup, color = BRUSH_COLOR ) { ctx.strokeStyle = color ctx.lineCap = "round" ctx.lineJoin = "round" lines.forEach((line) => { if (!line?.pts.length || !line.size) { return } ctx.lineWidth = line.size ctx.beginPath() ctx.moveTo(line.pts[0].x, line.pts[0].y) line.pts.forEach((pt) => ctx.lineTo(pt.x, pt.y)) ctx.stroke() }) } export const generateMask = ( imageWidth: number, imageHeight: number, lineGroups: LineGroup[], maskImages: HTMLImageElement[] = [], lineGroupsColor: string = "white" ): HTMLCanvasElement => { const maskCanvas = document.createElement("canvas") maskCanvas.width = imageWidth maskCanvas.height = imageHeight const ctx = maskCanvas.getContext("2d") if (!ctx) { throw new Error("could not retrieve mask canvas") } maskImages.forEach((maskImage) => { ctx.drawImage(maskImage, 0, 0, imageWidth, imageHeight) }) lineGroups.forEach((lineGroup) => { drawLines(ctx, lineGroup, lineGroupsColor) }) return maskCanvas }