"use client"; import { useState, useEffect, useCallback, useMemo } from "react"; import COMMAND from "../../common/commands"; import { ReactCompareSlider } from "react-compare-slider"; import Header from "../components/Header"; import Footer from "../components/Footer"; import ProgressBar from "../components/upscayl-tab/view/ProgressBar"; import RightPaneInfo from "../components/upscayl-tab/view/RightPaneInfo"; import ImageOptions from "../components/upscayl-tab/view/ImageOptions"; import LeftPaneImageSteps from "../components/upscayl-tab/config/LeftPaneImageSteps"; import Tabs from "../components/Tabs"; import SettingsTab from "../components/settings-tab"; import { useAtom, useAtomValue } from "jotai"; import { logAtom } from "../atoms/logAtom"; import { modelsListAtom } from "../atoms/modelsListAtom"; import { batchModeAtom, lensSizeAtom, compressionAtom, dontShowCloudModalAtom, noImageProcessingAtom, savedOutputPathAtom, overwriteAtom, progressAtom, scaleAtom, viewTypeAtom, rememberOutputFolderAtom, showSidebarAtom, customWidthAtom, useCustomWidthAtom, tileSizeAtom, } from "../atoms/userSettingsAtom"; import useLog from "../components/hooks/useLog"; import { UpscaylCloudModal } from "../components/UpscaylCloudModal"; import { featureFlags } from "@common/feature-flags"; import { BatchUpscaylPayload, DoubleUpscaylPayload, ImageUpscaylPayload, } from "@common/types/types"; import { NewsModal } from "@/components/NewsModal"; import { newsAtom, showNewsModalAtom } from "@/atoms/newsAtom"; import matter from "gray-matter"; import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"; import { cn } from "@/lib/utils"; import { useToast } from "@/components/ui/use-toast"; import { ToastAction } from "@/components/ui/toast"; import Logo from "@/components/icons/Logo"; import { sanitizePath } from "@common/sanitize-path"; import getDirectoryFromPath from "@common/get-directory-from-path"; const Home = () => { const allowedFileTypes = ["png", "jpg", "jpeg", "webp"]; // LOCAL STATES const [os, setOs] = useState<"linux" | "mac" | "win" | undefined>(undefined); const [imagePath, setImagePath] = useState(""); const [upscaledImagePath, setUpscaledImagePath] = useState(""); const [model, setModel] = useState("realesrgan-x4plus"); const [version, setVersion] = useState(""); const [batchFolderPath, setBatchFolderPath] = useState(""); const [doubleUpscayl, setDoubleUpscayl] = useState(false); const overwrite = useAtomValue(overwriteAtom); const [upscaledBatchFolderPath, setUpscaledBatchFolderPath] = useState(""); const [doubleUpscaylCounter, setDoubleUpscaylCounter] = useState(0); const [gpuId, setGpuId] = useState(""); const [saveImageAs, setSaveImageAs] = useState("png"); const [zoomAmount, setZoomAmount] = useState("100"); const [backgroundPosition, setBackgroundPosition] = useState("0% 0%"); const [dimensions, setDimensions] = useState({ width: null, height: null, }); const [selectedTab, setSelectedTab] = useState(0); const [isLoading, setIsLoading] = useState(true); const [showCloudModal, setShowCloudModal] = useState(false); const [minSize, setMinSize] = useState(22); const [cursorPosition, setCursorPosition] = useState({ x: 0, y: 0 }); // ATOMIC STATES const [outputPath, setOutputPath] = useAtom(savedOutputPathAtom); const [compression, setCompression] = useAtom(compressionAtom); const [progress, setProgress] = useAtom(progressAtom); const [batchMode, setBatchMode] = useAtom(batchModeAtom); const [logData, setLogData] = useAtom(logAtom); const [modelOptions, setModelOptions] = useAtom(modelsListAtom); const [scale] = useAtom(scaleAtom); const [dontShowCloudModal, setDontShowCloudModal] = useAtom( dontShowCloudModalAtom, ); const noImageProcessing = useAtomValue(noImageProcessingAtom); const [news, setNews] = useAtom(newsAtom); const [showNewsModal, setShowNewsModal] = useAtom(showNewsModalAtom); const viewType = useAtomValue(viewTypeAtom); const lensSize = useAtomValue(lensSizeAtom); const rememberOutputFolder = useAtomValue(rememberOutputFolderAtom); const [showSidebar, setShowSidebar] = useAtom(showSidebarAtom); const customWidth = useAtomValue(customWidthAtom); const useCustomWidth = useAtomValue(useCustomWidthAtom); const tileSize = useAtomValue(tileSizeAtom); const { logit } = useLog(); const { toast } = useToast(); const sanitizedImagePath = useMemo( () => sanitizePath(imagePath), [imagePath], ); const sanitizedUpscaledImagePath = useMemo( () => sanitizePath(upscaledImagePath), [upscaledImagePath], ); const handleMouseMoveCompare = (e: React.MouseEvent) => { const { left, top, height, width } = e.currentTarget.getBoundingClientRect(); const x = e.clientX - left; const y = e.clientY - top; setCursorPosition({ x, y }); const xZoom = ((e.pageX - left) / width) * 100; const yZoom = ((e.pageY - top) / height) * 100; setBackgroundPosition(`${xZoom}% ${yZoom}%`); }; // SET CONFIG VARIABLES ON FIRST RUN useEffect(() => { // UPSCAYL VERSION const upscaylVersion = navigator?.userAgent?.match( /Upscayl\/([\d\.]+\d+)/, )[1]; setVersion(upscaylVersion); }, []); // ELECTRON EVENT LISTENERS useEffect(() => { const handleErrors = (data: string) => { if (data.includes("Invalid GPU")) { toast({ title: "GPU Error", description: `Ran into an issue with the GPU. Please read the docs for troubleshooting! (${data})`, action: (
{ navigator.clipboard.writeText(data); }} > Copy Error Troubleshoot
), }); resetImagePaths(); } else if (data.includes("write") || data.includes("read")) { if (batchMode) return; toast({ title: "Read/Write Error", description: `Make sure that the path is correct and you have proper read/write permissions \n(${data})`, action: (
{ navigator.clipboard.writeText(data); }} > Copy Error Troubleshoot
), }); resetImagePaths(); } else if (data.includes("tile size")) { toast({ title: "Error", description: `The tile size is wrong. Please change the tile size in the settings or set to 0 (${data})`, }); resetImagePaths(); } else if (data.includes("uncaughtException")) { toast({ title: "Exception Error", description: `Upscayl encountered an error. Possibly, the upscayl binary failed to execute the commands properly. Try checking the logs to see if you get any information. You can post an issue on Upscayl's GitHub repository for more help.`, }); resetImagePaths(); } }; // OS window.electron.on( COMMAND.OS, (_, data: "linux" | "mac" | "win" | undefined) => { if (data) { setOs(data); } }, ); // LOG window.electron.on(COMMAND.LOG, (_, data: string) => { logit(`๐ŸŽ’ BACKEND REPORTED: `, data); }); // SCALING AND CONVERTING window.electron.on(COMMAND.SCALING_AND_CONVERTING, (_, data: string) => { setProgress("Processing the image..."); }); // UPSCAYL ERROR window.electron.on(COMMAND.UPSCAYL_ERROR, (_, data: string) => { toast({ title: "Error", description: data, }); resetImagePaths(); }); // UPSCAYL PROGRESS window.electron.on(COMMAND.UPSCAYL_PROGRESS, (_, data: string) => { if (data.length > 0 && data.length < 10) { setProgress(data); } else if (data.includes("converting")) { setProgress("Scaling and converting image..."); } else if (data.includes("Successful")) { setProgress("Upscayl Successful!"); } handleErrors(data); logit(`๐Ÿšง UPSCAYL_PROGRESS: `, data); }); // FOLDER UPSCAYL PROGRESS window.electron.on(COMMAND.FOLDER_UPSCAYL_PROGRESS, (_, data: string) => { if (data.includes("Successful")) { setProgress("Upscayl Successful!"); } if (data.length > 0 && data.length < 10) { setProgress(data); } handleErrors(data); logit(`๐Ÿšง FOLDER_UPSCAYL_PROGRESS: `, data); }); // DOUBLE UPSCAYL PROGRESS window.electron.on(COMMAND.DOUBLE_UPSCAYL_PROGRESS, (_, data: string) => { if (data.length > 0 && data.length < 10) { if (data === "0.00%") { setDoubleUpscaylCounter(doubleUpscaylCounter + 1); } setProgress(data); } handleErrors(data); logit(`๐Ÿšง DOUBLE_UPSCAYL_PROGRESS: `, data); }); // UPSCAYL DONE window.electron.on(COMMAND.UPSCAYL_DONE, (_, data: string) => { setProgress(""); setUpscaledImagePath(data); logit("upscaledImagePath: ", data); logit(`๐Ÿ’ฏ UPSCAYL_DONE: `, data); }); // FOLDER UPSCAYL DONE window.electron.on(COMMAND.FOLDER_UPSCAYL_DONE, (_, data: string) => { setProgress(""); setUpscaledBatchFolderPath(data); logit(`๐Ÿ’ฏ FOLDER_UPSCAYL_DONE: `, data); }); // DOUBLE UPSCAYL DONE window.electron.on(COMMAND.DOUBLE_UPSCAYL_DONE, (_, data: string) => { setProgress(""); setTimeout(() => setUpscaledImagePath(data), 500); setDoubleUpscaylCounter(0); logit(`๐Ÿ’ฏ DOUBLE_UPSCAYL_DONE: `, data); }); // CUSTOM FOLDER LISTENER window.electron.on(COMMAND.CUSTOM_MODEL_FILES_LIST, (_, data: string[]) => { logit(`๐Ÿ“œ CUSTOM_MODEL_FILES_LIST: `, data); const newModelOptions = data.map((model) => { return { value: model, label: model, }; }); // Add newModelsList to modelOptions and remove duplicates const combinedModelOptions = [...modelOptions, ...newModelOptions]; const uniqueModelOptions = combinedModelOptions.filter( // Check if any model in the array appears more than once (model, index, array) => array.findIndex((t) => t.value === model.value) === index, ); setModelOptions(uniqueModelOptions); }); }, []); // FETCH CUSTOM MODELS FROM CUSTOM MODELS PATH useEffect(() => { const customModelsPath = JSON.parse( localStorage.getItem("customModelsPath"), ); if (customModelsPath !== null) { window.electron.send(COMMAND.GET_MODELS_LIST, customModelsPath); logit("๐ŸŽฏ GET_MODELS_LIST: ", customModelsPath); } }, []); // FETCH NEWS useEffect(() => { // TODO: ADD AN ABOUT TAB if (window && window.navigator.onLine === false) return; try { fetch("https://raw.githubusercontent.com/upscayl/upscayl/main/news.md", { cache: "no-cache", }) .then((res) => { return res.text(); }) .then((result) => { const newsData = result; if (!newsData) { console.log("๐Ÿ“ฐ Could not fetch news data"); return; } const markdownData = matter(newsData); if (!markdownData) return; if (markdownData && markdownData.data.dontShow) { return; } if ( markdownData && news && markdownData?.data?.version === news?.data?.version ) { console.log("๐Ÿ“ฐ News is up to date"); if (showNewsModal === false) { setShowNewsModal(false); } } else if (markdownData) { setNews(matter(newsData)); setShowNewsModal(true); } }); } catch (error) { console.log("Could not fetch Upscayl News"); } }, [news]); // LOADING STATE useEffect(() => { setIsLoading(false); }, []); // HANDLERS const resetImagePaths = () => { logit("๐Ÿ”„ Resetting image paths"); setDimensions({ width: null, height: null, }); setProgress(""); setImagePath(""); setUpscaledImagePath(""); setBatchFolderPath(""); setUpscaledBatchFolderPath(""); }; // UTILS // CHECK IF IMAGE IS VALID const validateImagePath = (path: string) => { if (path.length > 0) { logit("๐Ÿ–ผ imagePath: ", path); const extension = path.toLocaleLowerCase().split(".").pop(); logit("๐Ÿ”ค Extension: ", extension); if (!allowedFileTypes.includes(extension.toLowerCase())) { toast({ title: "Invalid Image", description: "Please select an image with a valid extension like PNG, JPG, JPEG, or WEBP.", }); resetImagePaths(); } } else { resetImagePaths(); } }; // HANDLERS const handleMouseMove = useCallback((e: any) => { const { left, top, width, height } = e.target.getBoundingClientRect(); const x = ((e.pageX - left) / width) * 100; const y = ((e.pageY - top) / height) * 100; setBackgroundPosition(`${x}% ${y}%`); }, []); const selectImageHandler = async () => { resetImagePaths(); var path = await window.electron.invoke(COMMAND.SELECT_FILE); if (path === null) return; logit("๐Ÿ–ผ Selected Image Path: ", path); setImagePath(path); var dirname = getDirectoryFromPath(path); logit("๐Ÿ“ Selected Image Directory: ", dirname); if (!featureFlags.APP_STORE_BUILD) { if (!rememberOutputFolder) { setOutputPath(dirname); } } validateImagePath(path); }; const selectFolderHandler = async () => { resetImagePaths(); var path = await window.electron.invoke(COMMAND.SELECT_FOLDER); if (path !== null) { logit("๐Ÿ–ผ Selected Folder Path: ", path); setBatchFolderPath(path); if (!rememberOutputFolder) { setOutputPath(path); } } else { logit("๐Ÿšซ Folder selection cancelled"); setBatchFolderPath(""); if (!rememberOutputFolder) { setOutputPath(""); } } }; const handleModelChange = (e: any) => { setModel(e.value); logit("๐Ÿ”€ Model changed: ", e.value); localStorage.setItem( "model", JSON.stringify({ label: e.label, value: e.value }), ); }; // DRAG AND DROP HANDLERS const handleDragEnter = (e) => { e.preventDefault(); console.log("drag enter"); }; const handleDragLeave = (e) => { e.preventDefault(); console.log("drag leave"); }; const handleDragOver = (e) => { e.preventDefault(); console.log("drag over"); }; const openFolderHandler = (e) => { logit("๐Ÿ“‚ OPEN_FOLDER: ", upscaledBatchFolderPath); window.electron.send(COMMAND.OPEN_FOLDER, upscaledBatchFolderPath); }; const handleDrop = (e) => { e.preventDefault(); resetImagePaths(); if ( e.dataTransfer.items.length === 0 || e.dataTransfer.files.length === 0 ) { logit("๐Ÿ‘Ž No valid files dropped"); toast({ title: "Invalid Image", description: "Please drag and drop an image", }); return; } const type = e.dataTransfer.items[0].type; const filePath = e.dataTransfer.files[0].path; const extension = e.dataTransfer.files[0].name.split(".").at(-1); logit("โคต๏ธ Dropped file: ", JSON.stringify({ type, filePath, extension })); if ( !type.includes("image") || !allowedFileTypes.includes(extension.toLowerCase()) ) { logit("๐Ÿšซ Invalid file dropped"); toast({ title: "Invalid Image", description: "Please drag and drop an image", }); } else { logit("๐Ÿ–ผ Setting image path: ", filePath); setImagePath(filePath); var dirname = getDirectoryFromPath(filePath); logit("๐Ÿ—‚ Setting output path: ", dirname); if (!featureFlags.APP_STORE_BUILD) { if (!rememberOutputFolder) { setOutputPath(dirname); } } validateImagePath(filePath); } }; const handlePaste = (e) => { resetImagePaths(); e.preventDefault(); const type = e.clipboardData.items[0].type; const filePath = e.clipboardData.files[0].path; const extension = e.clipboardData.files[0].name.split(".").at(-1); logit("๐Ÿ“‹ Pasted file: ", JSON.stringify({ type, filePath, extension })); if ( !type.includes("image") && !allowedFileTypes.includes(extension.toLowerCase()) ) { toast({ title: "Invalid Image", description: "Please drag and drop an image", }); } else { setImagePath(filePath); var dirname = getDirectoryFromPath(filePath); logit("๐Ÿ—‚ Setting output path: ", dirname); if (!rememberOutputFolder) { setOutputPath(dirname); } } }; const upscaylHandler = async () => { logit("๐Ÿ”„ Resetting Upscaled Image Path"); setUpscaledImagePath(""); setUpscaledBatchFolderPath(""); if (imagePath !== "" || batchFolderPath !== "") { setProgress("Hold on..."); // Double Upscayl if (doubleUpscayl) { window.electron.send(COMMAND.DOUBLE_UPSCAYL, { imagePath, outputPath, model, gpuId: gpuId.length === 0 ? null : gpuId, saveImageAs, scale, noImageProcessing, compression: compression.toString(), customWidth: customWidth > 0 ? customWidth.toString() : null, useCustomWidth, tileSize, }); logit("๐Ÿ DOUBLE_UPSCAYL"); } else if (batchMode) { // Batch Upscayl setDoubleUpscayl(false); window.electron.send(COMMAND.FOLDER_UPSCAYL, { batchFolderPath, outputPath, model, gpuId: gpuId.length === 0 ? null : gpuId, saveImageAs, scale, noImageProcessing, compression: compression.toString(), customWidth: customWidth > 0 ? customWidth.toString() : null, useCustomWidth, tileSize, }); logit("๐Ÿ FOLDER_UPSCAYL"); } else { // Single Image Upscayl window.electron.send(COMMAND.UPSCAYL, { imagePath, outputPath, model, gpuId: gpuId.length === 0 ? null : gpuId, saveImageAs, scale, overwrite, noImageProcessing, compression: compression.toString(), customWidth: customWidth > 0 ? customWidth.toString() : null, useCustomWidth, tileSize, }); logit("๐Ÿ UPSCAYL"); } } else { toast({ title: "No image selected", description: "Please select an image to upscale", }); logit("๐Ÿšซ No valid image selected"); } }; const stopHandler = () => { window.electron.send(COMMAND.STOP); logit("๐Ÿ›‘ Stopping Upscayl"); resetImagePaths(); }; if (isLoading) { return ( ); } return (
{/* TOP LOGO WHEN SIDEBAR IS HIDDEN */} {!showSidebar && (
Upscayl
)} {/* SIDEBAR BUTTON */} {/* LEFT PANE */}
{/* UPSCAYL CLOUD MODAL */} {featureFlags.SHOW_UPSCAYL_CLOUD_INFO && ( )} {/* MACOS TITLEBAR */} {window.electron.platform === "mac" && (
)} {/* HEADER */}
{!dontShowCloudModal && featureFlags.SHOW_UPSCAYL_CLOUD_INFO && ( )} {/* NEWS DIALOG */} { setShowNewsModal(val); setNews((prev) => ({ ...prev, seen: true })); }} news={news} /> {selectedTab === 0 && ( )} {selectedTab === 1 && ( )} {/* )} */}
{/* RIGHT PANE */}
handleDrop(e)} onDragOver={(e) => handleDragOver(e)} onDragEnter={(e) => handleDragEnter(e)} onDragLeave={(e) => handleDragLeave(e)} onDoubleClick={() => { if (batchMode) { selectFolderHandler(); } else { selectImageHandler(); } }} onPaste={(e) => handlePaste(e)} > {window.electron.platform === "mac" && (
)} {progress.length > 0 && upscaledImagePath.length === 0 && upscaledBatchFolderPath.length === 0 ? ( ) : null} {/* DEFAULT PANE INFO */} {((!batchMode && imagePath.length === 0 && upscaledImagePath.length === 0) || (batchMode && batchFolderPath.length === 0 && upscaledBatchFolderPath.length === 0)) && ( )} {/* SHOW SELECTED IMAGE */} {!batchMode && upscaledImagePath.length === 0 && imagePath.length > 0 && ( <> { setDimensions({ width: e.target.naturalWidth, height: e.target.naturalHeight, }); }} draggable="false" alt="" className="h-full w-full bg-gradient-to-br from-base-300 to-base-100 object-contain" /> )} {/* BATCH UPSCALE SHOW SELECTED FOLDER */} {batchMode && upscaledBatchFolderPath.length === 0 && batchFolderPath.length > 0 && (

Selected folder:{" "} {batchFolderPath}

)} {/* BATCH UPSCALE DONE INFO */} {batchMode && upscaledBatchFolderPath.length > 0 && (

All done!

)} {!batchMode && viewType === "lens" && upscaledImagePath && imagePath && (
)} {/* COMPARISON SLIDER */} {!batchMode && viewType === "slider" && imagePath.length > 0 && upscaledImagePath.length > 0 && ( <>

Original

Original } itemTwo={ <>

Upscayled

Upscayl } className="group h-screen" /> )}
); }; export default Home;