1
0
mirror of https://github.com/upscayl/upscayl.git synced 2024-11-28 01:10:52 +01:00

Converted to Typescript

This commit is contained in:
Feenix 2022-11-12 02:09:28 +05:30
parent 3e622c2158
commit a2e7cc16a7
25 changed files with 1389 additions and 590 deletions

View File

@ -4,15 +4,15 @@
We're putting resources/{os}/bin from project inside resources/bin of electron. Same for the models directory as well.
*/
const { join, dirname, resolve } = require("path");
const { getPlatform } = require("./getPlatform");
const isDev = require("electron-is-dev");
const { app } = require("electron");
import { join, dirname, resolve } from "path";
import { getPlatform } from "./getPlatform";
import isDev from "electron-is-dev";
import { app } from "electron";
const appRootDir = app.getAppPath();
const binariesPath = isDev
? join(appRootDir, "resources", getPlatform(), "bin")
? join(appRootDir, "resources", getPlatform()!, "bin")
: join(dirname(appRootDir), "bin");
const execPath = (execName) =>
@ -22,4 +22,4 @@ const modelsPath = isDev
? resolve(join(appRootDir, "resources", "models"))
: resolve(join(dirname(appRootDir), "models"));
module.exports = { execPath, modelsPath };
export { execPath, modelsPath };

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -14,4 +14,4 @@ const commands = {
OPEN_FOLDER: "Open Folder",
};
module.exports = commands;
export default commands;

View File

@ -1,6 +1,6 @@
"use strict";
const { platform } = require("os");
import { platform } from "os";
const getPlatform = () => {
switch (platform()) {
@ -18,4 +18,4 @@ const getPlatform = () => {
}
};
module.exports = { getPlatform };
export { getPlatform };

View File

@ -1,27 +1,28 @@
// Native
const { join, parse } = require("path");
const { format } = require("url");
const { spawn } = require("child_process");
const fs = require("fs");
const sizeOf = require("image-size");
const { autoUpdater } = require("electron-updater");
const { getPlatform } = require("./getPlatform");
import { join, parse } from "path";
import { format } from "url";
import { ChildProcessWithoutNullStreams, spawn } from "child_process";
import fs from "fs";
import sizeOf from "image-size";
import { autoUpdater } from "electron-updater";
import { getPlatform } from "./getPlatform";
const { execPath, modelsPath } = require("./binaries");
import { execPath, modelsPath } from "./binaries";
// Packages
const {
import {
BrowserWindow,
app,
ipcMain,
dialog,
ipcRenderer,
shell,
} = require("electron");
MessageBoxOptions,
} from "electron";
const isDev = require("electron-is-dev");
const prepareNext = require("electron-next");
const commands = require("./commands");
import isDev from "electron-is-dev";
import prepareNext from "electron-next";
import commands from "./commands";
// Prepare the renderer once the app is ready
let mainWindow;
@ -42,7 +43,6 @@ app.on("ready", async () => {
show: false,
backgroundColor: "#171717",
webPreferences: {
autoHideMenuBar: true,
nodeIntegration: true,
webSecurity: false,
preload: join(__dirname, "preload.js"),
@ -141,7 +141,7 @@ ipcMain.on(commands.DOUBLE_UPSCAYL, async (event, payload) => {
model,
],
{
cwd: null,
cwd: undefined,
detached: false,
}
);
@ -180,7 +180,7 @@ ipcMain.on(commands.DOUBLE_UPSCAYL, async (event, payload) => {
execPath("realesrgan"),
["-i", outFile, "-o", outFile, "-s", 4, "-m", modelsPath, "-n", model],
{
cwd: null,
cwd: undefined,
detached: false,
}
);
@ -251,7 +251,7 @@ ipcMain.on(commands.UPSCAYL, async (event, payload) => {
// If already upscayled, just output that file
mainWindow.webContents.send(commands.UPSCAYL_DONE, outFile);
} else {
let upscayl = null;
let upscayl: ChildProcessWithoutNullStreams | null = null;
switch (model) {
case "realesrgan-x4plus":
case "realesrgan-x4plus-anime":
@ -270,7 +270,7 @@ ipcMain.on(commands.UPSCAYL, async (event, payload) => {
model,
],
{
cwd: null,
cwd: undefined,
detached: false,
}
);
@ -290,7 +290,7 @@ ipcMain.on(commands.UPSCAYL, async (event, payload) => {
modelsPath + "/" + model,
],
{
cwd: null,
cwd: undefined,
detached: false,
}
);
@ -298,7 +298,7 @@ ipcMain.on(commands.UPSCAYL, async (event, payload) => {
}
let failed = false;
upscayl.stderr.on("data", (data) => {
upscayl?.stderr.on("data", (data) => {
console.log(
"🚀 => upscayl.stderr.on => stderr.toString()",
data.toString()
@ -310,14 +310,14 @@ ipcMain.on(commands.UPSCAYL, async (event, payload) => {
}
});
upscayl.on("error", (data) => {
upscayl?.on("error", (data) => {
mainWindow.webContents.send(commands.UPSCAYL_PROGRESS, data.toString());
failed = true;
return;
});
// Send done comamnd when
upscayl.on("close", (code) => {
upscayl?.on("close", (code) => {
if (failed !== true) {
console.log("Done upscaling");
mainWindow.webContents.send(commands.UPSCAYL_DONE, outFile);
@ -338,7 +338,7 @@ ipcMain.on(commands.FOLDER_UPSCAYL, async (event, payload) => {
fs.mkdirSync(outputDir, { recursive: true });
}
// UPSCALE
let upscayl = null;
let upscayl: ChildProcessWithoutNullStreams | null = null;
switch (model) {
case "realesrgan-x4plus":
case "realesrgan-x4plus-anime":
@ -357,7 +357,7 @@ ipcMain.on(commands.FOLDER_UPSCAYL, async (event, payload) => {
model,
],
{
cwd: null,
cwd: undefined,
detached: false,
}
);
@ -377,7 +377,7 @@ ipcMain.on(commands.FOLDER_UPSCAYL, async (event, payload) => {
modelsPath + "/" + model,
],
{
cwd: null,
cwd: undefined,
detached: false,
}
);
@ -385,7 +385,7 @@ ipcMain.on(commands.FOLDER_UPSCAYL, async (event, payload) => {
}
let failed = false;
upscayl.stderr.on("data", (data) => {
upscayl?.stderr.on("data", (data) => {
console.log(
"🚀 => upscayl.stderr.on => stderr.toString()",
data.toString()
@ -400,7 +400,7 @@ ipcMain.on(commands.FOLDER_UPSCAYL, async (event, payload) => {
}
});
upscayl.on("error", (data) => {
upscayl?.on("error", (data) => {
mainWindow.webContents.send(
commands.FOLDER_UPSCAYL_PROGRESS,
data.toString()
@ -410,7 +410,7 @@ ipcMain.on(commands.FOLDER_UPSCAYL, async (event, payload) => {
});
// Send done comamnd when
upscayl.on("close", (code) => {
upscayl?.on("close", (code) => {
if (failed !== true) {
console.log("Done upscaling");
mainWindow.webContents.send(commands.FOLDER_UPSCAYL_DONE, outputDir);
@ -425,22 +425,29 @@ ipcMain.on(commands.OPEN_FOLDER, async (event, payload) => {
//------------------------Auto-Update Code-----------------------------//
// ! AUTO UPDATE STUFF
autoUpdater.on("update-available", (_event, releaseNotes, releaseName) => {
autoUpdater.on("update-available", ({ releaseNotes, releaseName }) => {
const dialogOpts = {
type: "info",
buttons: ["Ok"],
title: "Application Update",
message: process.platform === "win32" ? releaseNotes : releaseName,
message:
process.platform === "win32"
? (releaseNotes as string)
: (releaseName as string),
detail: "A new version is being downloaded.",
};
dialog.showMessageBox(dialogOpts, (response) => {});
dialog.showMessageBox(dialogOpts).then((returnValue) => {});
});
autoUpdater.on("update-downloaded", (_event, releaseNotes, releaseName) => {
const dialogOpts = {
autoUpdater.on("update-downloaded", (event) => {
const dialogOpts: MessageBoxOptions = {
type: "info",
buttons: ["Restart", "Later"],
title: "Application Update",
message: process.platform === "win32" ? releaseNotes : releaseName,
message:
process.platform === "win32"
? (event.releaseNotes as string)
: (event.releaseName as string),
detail:
"A new version has been downloaded. Restart the application to apply the updates.",
};

View File

@ -1,4 +1,4 @@
const { ipcRenderer, contextBridge } = require("electron");
import { ipcRenderer, contextBridge } from "electron";
// 'ipcRenderer' will be available in index.js with the method 'window.electron'
contextBridge.exposeInMainWorld("electron", {

31
electron/utils.ts Normal file
View File

@ -0,0 +1,31 @@
import { spawn } from "child_process";
import { execPath } from "./binaries";
/**
*
* @param {*} inputFile
* @param {*} outFile
* @param {*} modelsPath
* @param {*} model
* @returns
*/
function upscaylImage(
inputFile: string,
outFile: string,
modelsPath: string,
model: string
) {
// UPSCALE
let upscayl = spawn(
execPath("realesrgan"),
["-i", inputFile, "-o", outFile, "-s", "4", "-m", modelsPath, "-n", model],
{
cwd: undefined,
detached: false,
}
);
return upscayl;
}
module.exports = { upscaylImage };

View File

@ -1,25 +0,0 @@
const { spawn } = require("child_process");
const { execPath } = require("./binaries");
/**
*
* @param {*} inputFile
* @param {*} outFile
* @param {*} modelsPath
* @param {*} model
* @returns
*/
function upscaylImage(inputFile, outFile, modelsPath, model) {
// UPSCALE
let upscayl = spawn(
execPath("realesrgan"),
["-i", inputFile, "-o", outFile, "-s", 4, "-m", modelsPath, "-n", model],
{
cwd: null,
detached: false,
}
);
return upscayl;
}
module.exports = { upscaylImage };

View File

@ -1,5 +1,11 @@
module.exports = {
/**
* @type {import('next').NextConfig}
**/
const nextConfig = {
images: {
unoptimized: true,
},
};
module.exports = nextConfig;

View File

@ -34,6 +34,7 @@
"clean": "rimraf dist renderer/.next renderer/out",
"start": "electron .",
"build": "next build renderer && next export renderer",
"tsc": "tsc",
"pack-app": "npm run build && electron-builder --dir",
"dist": "npm run build && DEBUG=* electron-builder",
"dist:appimage": "npm run build && DEBUG=* electron-builder build -l AppImage",
@ -123,30 +124,37 @@
]
},
"devDependencies": {
"autoprefixer": "^10.4.8",
"electron": "^20.0.2",
"electron-builder": "^23.3.3",
"next": "^12.3.1",
"postcss": "^8.4.16",
"@types/electron": "^1.6.10",
"@types/node": "^18.11.9",
"@types/react": "^18.0.25",
"@types/react-dom": "^18.0.8",
"autoprefixer": "^10.4.13",
"electron": "^21.2.2",
"electron-builder": "^23.6.0",
"next": "^13.0.2",
"postcss": "^8.4.18",
"prettier": "^2.7.1",
"prettier-plugin-tailwindcss": "^0.1.13",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"tailwindcss": "^3.1.8"
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tailwindcss": "^3.2.2",
"typescript": "^4.8.4"
},
"dependencies": {
"app-root-dir": "^1.0.2",
"electron-is-dev": "^2.0.0",
"electron-is-packaged": "^1.0.2",
"electron-next": "^3.1.5",
"electron-root-path": "^1.0.16",
"electron-updater": "^5.2.1",
"electron-root-path": "^1.1.0",
"electron-updater": "^5.3.0",
"image-size": "^1.0.2",
"react-compare-slider": "^2.2.0",
"react-dropzone": "^14.2.2",
"react-select": "^5.4.0",
"react-tooltip": "^4.2.21",
"tailwind-scrollbar": "^1.3.1"
"react-dropzone": "^14.2.3",
"react-image-zoom": "^1.3.1",
"react-magnifier": "^3.0.4",
"react-select": "^5.6.0",
"react-tooltip": "^4.4.3",
"tailwind-scrollbar": "^2.0.1"
},
"volta": {
"node": "16.17.0"

View File

@ -0,0 +1,38 @@
import React from "react";
function Footer() {
return (
<div className="p-2 text-center text-sm text-neutral-500">
<p>
Copyright © 2022 -{" "}
<a
className="font-bold"
href="https://github.com/upscayl/upscayl"
target="_blank"
>
Upscayl
</a>
</p>
<p>
By{" "}
<a
href="https://github.com/TGS963"
className="font-bold"
target="_blank"
>
TGS963
</a>{" "}
and{" "}
<a
href="https://github.com/NayamAmarshe"
className="font-bold"
target="_blank"
>
Nayam Amarshe
</a>
</p>
</div>
);
}
export default Footer;

View File

@ -0,0 +1,24 @@
import React from "react";
export default function Header() {
return (
<a
href="https://github.com/upscayl/upscayl"
target="_blank"
className="outline-none focus-visible:ring-2"
>
<div className="flex items-center gap-3 px-5 py-5">
<img
src="icon.png"
className="inline-block w-14"
alt="Upscayl Logo"
data-tip="Star us on GitHub 😁"
/>
<div className="flex flex-col justify-center">
<h1 className="text-3xl font-bold text-neutral-50">Upscayl</h1>
<p className="text-neutral-400">AI Image Upscaler</p>
</div>
</div>
</a>
);
}

View File

@ -0,0 +1,225 @@
import React from "react";
import Select from "react-select";
import ReactTooltip from "react-tooltip";
function LeftPaneSteps(props) {
const handleBatchMode = () => {
props.setBatchMode((oldValue) => !oldValue);
};
const customStyles = {
option: (provided, state) => ({
...provided,
borderBottom: "1px dotted pink",
color: state.isSelected ? "red" : "blue",
padding: 20,
}),
control: () => ({
// none of react-select's styles are passed to <Control />
width: 200,
}),
singleValue: (provided, state) => {
const opacity = state.isDisabled ? 0.5 : 1;
const transition = "opacity 300ms";
return { ...provided, opacity, transition };
},
};
const modelOptions = [
{ label: "General Photo", value: "realesrgan-x4plus" },
{ label: "Digital Art", value: "realesrgan-x4plus-anime" },
{ label: "Sharpen Image", value: "models-DF2K" },
];
return (
<div className="animate-step-in animate flex h-screen flex-col gap-7 overflow-auto p-5">
{/* BATCH OPTION */}
<div className="flex flex-row items-end">
<p
className="mr-1 inline-block cursor-help text-sm text-white/70"
data-tip="This will let you upscale all files in a folder at once"
>
Batch Upscale:
</p>
<button
className={`animate relative inline-block h-5 w-8 cursor-pointer rounded-full outline-none focus-visible:shadow-lg focus-visible:shadow-purple-500 ${
props.batchMode ? "bg-gradient-purple" : "bg-neutral-500"
}`}
onClick={handleBatchMode}
>
<div
className={`${
props.batchMode ? "translate-x-4" : "translate-x-1"
} animate absolute top-1/2 h-3 w-3 -translate-y-1/2 rounded-full bg-neutral-100`}
></div>
</button>
</div>
{/* ADVANCED OPTION */}
<div className="flex flex-row items-end">
<p
className="mr-1 inline-block cursor-help text-sm text-white/70"
data-tip="This will let you upscale all files in a folder at once"
>
Advanced Options:
</p>
<button
className={`animate relative inline-block h-5 w-8 cursor-pointer rounded-full outline-none focus-visible:shadow-lg focus-visible:shadow-purple-500 ${
props.batchMode ? "bg-gradient-purple" : "bg-neutral-500"
}`}
onClick={handleBatchMode}
>
<div
className={`${
props.batchMode ? "translate-x-4" : "translate-x-1"
} animate absolute top-1/2 h-3 w-3 -translate-y-1/2 rounded-full bg-neutral-100`}
></div>
</button>
</div>
{/* STEP 1 */}
<div data-tip={props.imagePath}>
<p className="step-heading">Step 1</p>
<button
className="animate bg-gradient-red rounded-lg p-3 font-medium text-white/90 outline-none transition-colors focus-visible:shadow-lg focus-visible:shadow-red-500"
onClick={
!props.batchMode
? props.selectImageHandler
: props.selectFolderHandler
}
>
Select {props.batchMode ? "Folder" : "Image"}
</button>
</div>
{/* STEP 2 */}
<div className="animate-step-in">
<p className="step-heading">Step 2</p>
<p className="mb-2 text-sm text-white/60">Select Upscaling Type</p>
<Select
options={modelOptions}
components={{
IndicatorSeparator: () => null,
DropdownIndicator: () => null,
}}
onChange={props.handleModelChange}
className="react-select-container"
classNamePrefix="react-select"
defaultValue={modelOptions[0]}
theme={(theme) => ({
...theme,
colors: {
...theme.colors,
primary: "rgb(71 85 105)",
primary25: "#f5f5f5",
primary50: "#f5f5f5",
},
})}
styles={{
input: (provided, state) => ({
...provided,
color: "rgb(100 100 100)",
}),
dropdownIndicator: (provided, state) => ({
...provided,
color: "rgb(15 15 15)",
}),
placeholder: (provided, state) => ({
...provided,
color: "rgb(15 15 15)",
fontWeight: "500",
}),
singleValue: (provided, state) => ({
...provided,
color: "rgb(15 15 15)",
fontWeight: "500",
}),
menu: (provided, state) => ({
...provided,
background: "#f5f5f5",
}),
}}
/>
{/* <select
name="select-model"
onDrop={(e) => props.handleDrop(e)}
className="animate bg-gradient-white block cursor-pointer rounded-lg p-3 font-medium text-black/90 outline-none hover:bg-slate-200 focus-visible:ring-2 focus-visible:ring-slate-400"
onChange={props.handleModelChange}
>
<option value="realesrgan-x4plus">General Photo</option>
<option value="realesrgan-x4plus-anime">Digital Art</option>
<option value="models-DF2K">Sharpen Image</option>
</select> */}
{props.model !== "models-DF2K" && !props.batchMode && (
<div className="mt-2 flex items-center gap-1">
<input
type="checkbox"
className="checked:bg-gradient-white h-4 w-4 cursor-pointer appearance-none rounded-full bg-white/30 transition duration-200 focus:outline-none focus-visible:border focus-visible:shadow-lg focus-visible:shadow-white/40"
checked={props.doubleUpscayl}
onChange={(e) => {
if (e.target.checked) {
props.setDoubleUpscayl(true);
} else {
props.setDoubleUpscayl(false);
}
}}
/>
<p
className={`inline-block cursor-pointer select-none rounded-full text-sm font-medium ${
props.doubleUpscayl
? "bg-gradient-white px-2 text-black/90"
: "text-white/50"
}`}
onClick={(e) => {
props.setDoubleUpscayl(!props.doubleUpscayl);
}}
>
Double Upscayl
</p>
<span
className="cursor-help rounded-full bg-white/20 px-3 text-center font-bold text-white/40"
data-tip="Enable this option to get an 8x upscayl. Note that this may not always work properly with all images, for example, images with really large resolutions."
>
i
</span>
</div>
)}
</div>
{/* STEP 3 */}
<div className="animate-step-in" data-tip={props.outputPath}>
<p className="step-heading">Step 3</p>
<p className="mb-2 text-sm text-white/60">Defaults to Image's path</p>
<button
className="animate bg-gradient mt-1 rounded-lg p-3 font-medium text-black/90 outline-none transition-colors focus-visible:shadow-lg focus-visible:shadow-green-500"
onClick={props.outputHandler}
>
Set Output Folder
</button>
</div>
{/* STEP 4 */}
<div className="animate-step-in">
<p className="step-heading">Step 4</p>
<button
className="animate bg-gradient-upscayl rounded-lg p-3 font-medium text-white/90 outline-none transition-colors focus-visible:shadow-lg focus-visible:shadow-violet-500"
onClick={props.upscaylHandler}
disabled={props.progress.length > 0}
>
{props.progress.length > 0 ? "Upscayling⏳" : "Upscayl"}
</button>
</div>
<ReactTooltip
className="max-w-md break-words bg-neutral-900 text-neutral-50"
place="top"
/>
</div>
);
}
export default LeftPaneSteps;

View File

@ -0,0 +1,20 @@
import React from "react";
import Animated from "../public/loading.svg";
import Image from "next/image";
function ProgressBar(props) {
console.log(props.sharpening);
return (
<div className="absolute flex h-full w-full flex-col items-center justify-center bg-black/50 backdrop-blur-lg">
<div className="flex flex-col items-center gap-2">
<Image src={Animated} alt="Progress Bar" />
<p className="font-bold text-neutral-50">{props.progress}</p>
<p className="text-sm font-medium text-neutral-200">
Doing the Upscayl magic...
</p>
</div>
</div>
);
}
export default ProgressBar;

View File

@ -0,0 +1,14 @@
import React from "react";
function ResetButton(props) {
return (
<button
className="animate bg-gradient-blue absolute top-1 right-1 z-10 rounded-full py-2 px-4 text-white opacity-30 hover:opacity-100"
onClick={props.resetImagePaths}
>
Reset
</button>
);
}
export default ResetButton;

View File

@ -0,0 +1,20 @@
import React from "react";
function RightPaneInfo({ version, batchMode }) {
return (
<>
<p className="p-5 pb-0 text-lg font-medium text-neutral-400">
Select {batchMode ? "a Folder" : "an Image"} to Upscale
</p>
{batchMode && (
<p className="w-full py-5 text-center text-neutral-500 md:w-96">
Make sure that the folder doesn't contain anything except PNG, JPG,
JPEG & WEBP images.
</p>
)}
<p className="text-neutral-600">Upscayl v{version}</p>
</>
);
}
export default RightPaneInfo;

5
renderer/next-env.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

16
renderer/pages/_app.tsx Normal file
View File

@ -0,0 +1,16 @@
import "../styles/globals.css";
import Head from "next/head";
import { AppProps } from "next/app";
const MyApp = ({ Component, pageProps }: AppProps) => {
return (
<>
<Head>
<title>Upscayl</title>
</Head>
<Component {...pageProps} />
</>
);
};
export default MyApp;

411
renderer/pages/index.tsx Normal file
View File

@ -0,0 +1,411 @@
import { useState, useEffect, useRef, useCallback } from "react";
import commands from "../../electron/commands";
import {
ReactCompareSlider,
ReactCompareSliderImage,
} from "react-compare-slider";
import Header from "../components/Header";
import Footer from "../components/Footer";
import ProgressBar from "../components/ProgressBar";
import ResetButton from "../components/ResetButton";
import LeftPaneSteps from "../components/LeftPaneSteps";
import RightPaneInfo from "../components/RightPaneInfo";
const Home = () => {
const [imagePath, SetImagePath] = useState("");
const [upscaledImagePath, setUpscaledImagePath] = useState("");
const [outputPath, SetOutputPath] = useState("");
const [scaleFactor, setScaleFactor] = useState(4);
const [progress, setProgress] = useState("");
const [model, setModel] = useState("realesrgan-x4plus");
const [loaded, setLoaded] = useState(false);
const [version, setVersion] = useState("");
const [batchMode, setBatchMode] = useState(false);
const [batchFolderPath, setBatchFolderPath] = useState("");
const [upscaledBatchFolderPath, setUpscaledBatchFolderPath] = useState("");
const [doubleUpscayl, setDoubleUpscayl] = useState(false);
const resetImagePaths = () => {
setProgress("");
SetImagePath("");
setUpscaledImagePath("");
setBatchFolderPath("");
setUpscaledBatchFolderPath("");
};
useEffect(() => {
setLoaded(true);
const prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)");
setVersion(navigator?.userAgent?.match(/Upscayl\/([\d\.]+\d+)/)[1]);
const handleErrors = (data) => {
if (data.includes("invalid gpu")) {
alert(
"Error. Please make sure you have a Vulkan compatible GPU (Most modern GPUs support Vulkan). Upscayl does not work with CPU or iGPU sadly."
);
resetImagePaths();
} else if (data.includes("failed")) {
if (batchMode) return;
alert(
data.includes("encode")
? "ENCODING ERROR => "
: "DECODING ERROR => " +
"This image is possibly corrupt or not supported by Upscayl. You could try converting the image into another format and upscaling again. Otherwise, make sure that the output path is correct and you have the proper write permissions for the directory. If not, then unfortuantely this image is not supported by Upscayl, sorry."
);
resetImagePaths();
} else if (data.includes("uncaughtException")) {
alert(
"Upscayl encountered an error. Possibly, the upscayl binary failed to execute the commands properly. Try launching Upscayl using commandline through Terminal and see if you get any information. You can post an issue on Upscayl's GitHub repository for more help."
);
resetImagePaths();
}
};
window.electron.on(commands.UPSCAYL_PROGRESS, (_, data) => {
console.log(
"🚀 => file: index.jsx => line 61 => window.electron.on => data",
data
);
if (data.length > 0 && data.length < 10) {
setProgress(data);
}
handleErrors(data);
});
window.electron.on(commands.FOLDER_UPSCAYL_PROGRESS, (_, data) => {
if (data.length > 0 && data.length < 10) {
setProgress(data);
}
handleErrors(data);
});
window.electron.on(commands.DOUBLE_UPSCAYL_PROGRESS, (_, data) => {
if (data.length > 0 && data.length < 10) {
setProgress(data);
}
handleErrors(data);
});
window.electron.on(commands.UPSCAYL_DONE, (_, data) => {
setProgress("");
setUpscaledImagePath(data);
});
window.electron.on(commands.FOLDER_UPSCAYL_DONE, (_, data) => {
setProgress("");
setUpscaledBatchFolderPath(data);
});
window.electron.on(commands.DOUBLE_UPSCAYL_DONE, (_, data) => {
setUpscaledImagePath(data);
});
}, []);
useEffect(() => {
setProgress("");
}, [batchMode]);
useEffect(() => {
if (imagePath.length > 0) {
const filePath = imagePath;
console.log(
"🚀 => file: index.jsx => line 109 => useEffect => filePath",
filePath
);
const extension = imagePath.toLocaleLowerCase().split(".").pop();
console.log(
"🚀 => file: index.jsx => line 111 => useEffect => extension",
extension
);
if (!allowedFileTypes.includes(extension.toLowerCase())) {
alert("Please select an image");
resetImagePaths();
}
}
}, [imagePath]);
const selectImageHandler = async () => {
resetImagePaths();
var path = await window.electron.invoke(commands.SELECT_FILE);
if (path !== "cancelled") {
SetImagePath(path);
var dirname = path.match(/(.*)[\/\\]/)[1] || "";
SetOutputPath(dirname);
}
};
const selectFolderHandler = async () => {
resetImagePaths();
var path = await window.electron.invoke(commands.SELECT_FOLDER);
if (path !== "cancelled") {
setBatchFolderPath(path);
SetOutputPath(path + "_upscayled");
}
};
const handleModelChange = (e) => {
setModel(e.value);
if (e.value === "models-DF2K") {
setDoubleUpscayl(false);
}
};
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) => {
window.electron.send(commands.OPEN_FOLDER, upscaledBatchFolderPath);
};
const allowedFileTypes = ["png", "jpg", "jpeg", "webp"];
const handleDrop = (e) => {
e.preventDefault();
resetImagePaths();
const type = e.dataTransfer.items[0].type;
console.log("🚀 => handleDrop => type", type);
const filePath = e.dataTransfer.files[0].path;
console.log("🚀 => handleDrop => filePath", filePath);
const extension = e.dataTransfer.files[0].name.split(".").at(-1);
console.log("🚀 => handleDrop => extension", extension);
if (
!type.includes("image") ||
!allowedFileTypes.includes(extension.toLowerCase())
) {
alert("Please drag and drop an image");
} else {
SetImagePath(filePath);
var dirname = filePath.match(/(.*)[\/\\]/)[1] || "";
console.log("🚀 => handleDrop => dirname", dirname);
SetOutputPath(dirname);
}
};
const handlePaste = (e) => {
console.log(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);
if (
!type.includes("image") &&
!allowedFileTypes.includes(extension.toLowerCase())
) {
alert("Please drag and drop an image");
} else {
SetImagePath(filePath);
var dirname = filePath.match(/(.*)[\/\\]/)[1] || "";
SetOutputPath(dirname);
}
};
const outputHandler = async () => {
var path = await window.electron.invoke(commands.SELECT_FOLDER);
if (path !== "cancelled") {
SetOutputPath(path);
} else {
console.log("Getting output path from input file");
}
};
const upscaylHandler = async () => {
setUpscaledImagePath("");
if (imagePath !== "" || batchFolderPath !== "") {
setProgress("Hold on...");
if (model === "models-DF2K") {
setDoubleUpscayl(false);
}
if (doubleUpscayl) {
await window.electron.send(commands.DOUBLE_UPSCAYL, {
imagePath,
outputPath,
model,
});
} else if (batchMode) {
setDoubleUpscayl(false);
await window.electron.send(commands.FOLDER_UPSCAYL, {
scaleFactor,
batchFolderPath,
outputPath,
model,
});
} else {
await window.electron.send(commands.UPSCAYL, {
scaleFactor,
imagePath,
outputPath,
model,
});
}
} else {
alert("Please select an image to upscale");
}
};
return (
<div className="flex h-screen w-screen flex-row overflow-hidden bg-[#1d1c23]">
<div className="flex h-screen w-96 flex-col bg-[#26222c]">
{((!batchMode && imagePath.length > 0) ||
(batchMode && batchFolderPath.length > 0)) && (
<ResetButton resetImagePaths={resetImagePaths} />
)}
{/* HEADER */}
<Header />
{/* LEFT PANE */}
<LeftPaneSteps
progress={progress}
selectImageHandler={selectImageHandler}
selectFolderHandler={selectFolderHandler}
handleModelChange={handleModelChange}
handleDrop={handleDrop}
outputHandler={outputHandler}
upscaylHandler={upscaylHandler}
batchMode={batchMode}
setBatchMode={setBatchMode}
imagePath={imagePath}
outputPath={outputPath}
doubleUpscayl={doubleUpscayl}
setDoubleUpscayl={setDoubleUpscayl}
model={model}
/>
<Footer />
</div>
{/* RIGHT PANE */}
<div
className="relative flex h-screen w-full flex-col items-center justify-center"
onDrop={(e) => handleDrop(e)}
onDragOver={(e) => handleDragOver(e)}
onDragEnter={(e) => handleDragEnter(e)}
onDragLeave={(e) => handleDragLeave(e)}
onPaste={(e) => handlePaste(e)}
>
{progress.length > 0 &&
upscaledImagePath.length === 0 &&
upscaledBatchFolderPath.length === 0 ? (
<ProgressBar progress={progress} />
) : null}
{((!batchMode &&
imagePath.length === 0 &&
upscaledImagePath.length === 0) ||
(batchMode &&
batchFolderPath.length === 0 &&
upscaledBatchFolderPath.length === 0)) && (
<RightPaneInfo version={version} batchMode={batchMode} />
)}
{!batchMode &&
upscaledImagePath.length === 0 &&
imagePath.length > 0 && (
<img
className="h-full w-full object-contain"
src={
"file://" +
`${upscaledImagePath ? upscaledImagePath : imagePath}`
}
draggable="false"
alt=""
/>
)}
{batchMode &&
upscaledBatchFolderPath.length === 0 &&
batchFolderPath.length > 0 && (
<p className="select-none font-bold text-neutral-50">
Selected folder: {batchFolderPath}
</p>
)}
{batchMode && upscaledBatchFolderPath.length > 0 && (
<>
<p className="select-none py-4 font-bold text-neutral-50">
All done!
</p>
<button
className="bg-gradient-blue rounded-lg p-3 font-medium text-white/90 transition-colors"
onClick={openFolderHandler}
>
Open Upscayled Folder
</button>
</>
)}
{!batchMode && imagePath.length > 0 && upscaledImagePath.length > 0 && (
<ReactCompareSlider
itemOne={
<>
<p className="absolute bottom-1 left-1 rounded-md bg-black p-1 text-sm font-medium text-white opacity-30">
Original
</p>
<ReactCompareSliderImage
src={"file://" + imagePath}
alt="Original"
style={{
objectFit: "contain",
}}
className="bg-[#1d1c23]"
/>
</>
}
itemTwo={
<>
<p className="absolute bottom-1 right-1 rounded-md bg-black p-1 text-sm font-medium text-white opacity-30">
Upscayled
</p>
<ReactCompareSliderImage
src={"file://" + upscaledImagePath}
alt="Upscayl"
style={{
objectFit: "contain",
}}
className="origin-bottom scale-[200%] bg-[#1d1c23]"
/>
</>
}
className="h-screen"
/>
)}
{/* {imagePath.length === 0 && batchFolderPath.length === 0 ? (
<RightPaneInfo version={version} />
) : upscaledImagePath.length === 0 &&
upscaledBatchFolderPath.length === 0 ? (
!batchMode ? (
) : (
)
) : !batchMode ? (
) : (
)} */}
</div>
</div>
);
};
export default Home;

13
renderer/renderer.d.ts vendored Normal file
View File

@ -0,0 +1,13 @@
import { IpcRenderer } from "electron";
export interface IElectronAPI {
on: (command, func?) => IpcRenderer;
send: (command, func?) => IpcRenderer;
invoke: (command, func?) => any;
}
declare global {
interface Window {
electron: IElectronAPI;
}
}

20
renderer/tsconfig.json Normal file
View File

@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"incremental": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve"
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "../app.module.ts"],
"exclude": ["node_modules"]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

107
tsconfig.json Normal file
View File

@ -0,0 +1,107 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
"jsx": "preserve" /* Specify what JSX code is generated. */,
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "commonjs" /* Specify what module code is generated. */,
"rootDir": "./electron" /* Specify the root folder within your source files. */,
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
"rootDirs": [
"./electron",
"./renderer"
] /* Allow multiple folders to be treated as one when resolving modules. */,
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "resolveJsonModule": true, /* Enable importing .json files. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
"outDir": "./main" /* Specify an output folder for all emitted files. */,
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
/* Type Checking */
"strict": true /* Enable all strict type-checking options. */,
"noImplicitAny": false /* Enable error reporting for expressions and declarations with an implied 'any' type. */,
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
"exclude": ["node_modules", "public", "renderer"]
}

871
yarn.lock

File diff suppressed because it is too large Load Diff