"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"; import { translationAtom } from "@/atoms/translations-atom"; const Home = () => { const allowedFileTypes = ["png", "jpg", "jpeg", "webp"]; const t = useAtomValue(translationAtom); // 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: t("APP.ERRORS.GPU_ERROR.TITLE"), description: t("APP.ERRORS.GPU_ERROR.DESC", { data }), action: (
{ navigator.clipboard.writeText(data); }} > {t("APP.ERRORS.COPY_ERROR.TITLE")} {t("APP.ERRORS.TROUBLESHOOT")}
), }); resetImagePaths(); } else if (data.includes("write") || data.includes("read")) { if (batchMode) return; toast({ title: t("APP.ERRORS.READ_WRITE_ERROR.TITLE"), description: t("APP.ERRORS.READ_WRITE_ERROR.DESC", { data }), action: (
{ navigator.clipboard.writeText(data); }} > {t("APP.ERRORS.COPY_ERROR.TITLE")} {t("APP.ERRORS.TROUBLESHOOT")}
), }); resetImagePaths(); } else if (data.includes("tile size")) { toast({ title: t("APP.ERRORS.TILE_SIZE_ERROR.TITLE"), description: t("APP.ERRORS.TILE_SIZE_ERROR.DESC", { data }), }); resetImagePaths(); } else if (data.includes("uncaughtException")) { toast({ title: t("APP.ERRORS.EXCEPTION_ERROR.TITLE"), description: t("APP.ERRORS.EXCEPTION_ERROR.DESC"), }); 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(t("APP.INFOS.IMAGE_PROCESSING.START")); }); // UPSCAYL ERROR window.electron.on(COMMAND.UPSCAYL_ERROR, (_, data: string) => { toast({ title: t("APP.ERRORS.GENERIC_ERROR.TITLE"), 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(t("APP.INFOS.IMAGE_PROCESSING.SCALE_CONVERT")); } else if (data.includes("Successful")) { setProgress(t("APP.INFOS.IMAGE_PROCESSING.SUCCESS")); } handleErrors(data); logit(`๐Ÿšง UPSCAYL_PROGRESS: `, data); }); // FOLDER UPSCAYL PROGRESS window.electron.on(COMMAND.FOLDER_UPSCAYL_PROGRESS, (_, data: string) => { if (data.includes("Successful")) { setProgress(t("APP.INFOS.IMAGE_PROCESSING.SUCCESS")); } 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: t("APP.ERRORS.INVALID_IMAGE_ERROR.TITLE"), description: t("APP.ERRORS.INVALID_IMAGE_ERROR.DESC"), }); 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: t("APP.ERRORS.INVALID_IMAGE_ERROR.TITLE"), description: t("APP.ERRORS.INVALID_IMAGE_ERROR.DRAG_DESC"), }); 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: t("APP.ERRORS.INVALID_IMAGE_ERROR.TITLE"), description: t("APP.ERRORS.INVALID_IMAGE_ERROR.DRAG_DESC"), }); } 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: t("APP.ERRORS.INVALID_IMAGE_ERROR.TITLE"), description: t("APP.ERRORS.INVALID_IMAGE_ERROR.DRAG_DESC"), }); } 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(t("APP.INFOS.IMAGE_PROCESSING.WAIT")); // 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: t("APP.ERRORS.NO_IMAGE_ERROR.TITLE"), description: t("APP.ERRORS.NO_IMAGE_ERROR.DESC"), }); 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 && (
{t("APP.TITLE")}
)} {/* 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 && (

{t("APP.INFOS.IMAGE_PROCESSING.BATCH.SELECT")} {" "} {batchFolderPath}

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

{t("APP.INFOS.IMAGE_PROCESSING.BATCH.DONE")}

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

{t("APP.INFOS.COMPARISION.SLIDER_ORIGINAL")}

{t("APP.INFOS.COMPARISION.SLIDER_ORIGINAL")} } itemTwo={ <>

{t("APP.INFOS.COMPARISION.SLIDER_PROCESSED")}

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