diff --git a/common/commands.ts b/common/electron-commands.ts similarity index 94% rename from common/commands.ts rename to common/electron-commands.ts index 380599e..f1ebcb7 100644 --- a/common/commands.ts +++ b/common/electron-commands.ts @@ -1,4 +1,4 @@ -const COMMAND = { +const ELECTRON_COMMANDS = { SELECT_FILE: "Select a File", SELECT_FOLDER: "Select a Folder", UPSCAYL: "Upscale the Image", @@ -25,6 +25,6 @@ const COMMAND = { OS: "Get OS", SCALING_AND_CONVERTING: "Adding some finishing touches", UPSCAYL_ERROR: "Upscaling Error", -}; +} as const; -export default COMMAND; +export { ELECTRON_COMMANDS }; diff --git a/common/feature-flags.ts b/common/feature-flags.ts index 39e5241..2e4ea02 100644 --- a/common/feature-flags.ts +++ b/common/feature-flags.ts @@ -3,7 +3,7 @@ type FeatureFlags = { SHOW_UPSCAYL_CLOUD_INFO: boolean; }; -export const featureFlags: FeatureFlags = { +export const FEATURE_FLAGS: FeatureFlags = { APP_STORE_BUILD: false, SHOW_UPSCAYL_CLOUD_INFO: false, }; diff --git a/electron/commands/batch-upscayl.ts b/electron/commands/batch-upscayl.ts index 5065548..584552f 100644 --- a/electron/commands/batch-upscayl.ts +++ b/electron/commands/batch-upscayl.ts @@ -11,7 +11,7 @@ import { spawnUpscayl } from "../utils/spawn-upscayl"; import { getBatchArguments } from "../utils/get-arguments"; import slash from "../utils/slash"; import { modelsPath } from "../utils/get-resource-paths"; -import COMMAND from "../../common/commands"; +import { ELECTRON_COMMANDS } from "@common/electron-commands"; import { BatchUpscaylPayload } from "../../common/types/types"; import showNotification from "../utils/show-notification"; import { DEFAULT_MODELS } from "../../common/models-list"; @@ -72,28 +72,28 @@ const batchUpscayl = async (event, payload: BatchUpscaylPayload) => { if (!mainWindow) return; data = data.toString(); mainWindow.webContents.send( - COMMAND.FOLDER_UPSCAYL_PROGRESS, + ELECTRON_COMMANDS.FOLDER_UPSCAYL_PROGRESS, data.toString(), ); if ((data as string).includes("Error")) { logit("❌ ", data); encounteredError = true; } else if (data.includes("Resizing")) { - mainWindow.webContents.send(COMMAND.SCALING_AND_CONVERTING); + mainWindow.webContents.send(ELECTRON_COMMANDS.SCALING_AND_CONVERTING); } }; const onError = (data: any) => { if (!mainWindow) return; mainWindow.setProgressBar(-1); mainWindow.webContents.send( - COMMAND.FOLDER_UPSCAYL_PROGRESS, + ELECTRON_COMMANDS.FOLDER_UPSCAYL_PROGRESS, data.toString(), ); failed = true; upscayl.kill(); mainWindow && mainWindow.webContents.send( - COMMAND.UPSCAYL_ERROR, + ELECTRON_COMMANDS.UPSCAYL_ERROR, `Error upscaling images! ${data}`, ); return; @@ -104,7 +104,7 @@ const batchUpscayl = async (event, payload: BatchUpscaylPayload) => { logit("💯 Done upscaling"); upscayl.kill(); mainWindow.webContents.send( - COMMAND.FOLDER_UPSCAYL_DONE, + ELECTRON_COMMANDS.FOLDER_UPSCAYL_DONE, outputFolderPath, ); if (!encounteredError) { diff --git a/electron/commands/custom-models-select.ts b/electron/commands/custom-models-select.ts index bd2edd5..a0ab7d0 100644 --- a/electron/commands/custom-models-select.ts +++ b/electron/commands/custom-models-select.ts @@ -5,11 +5,11 @@ import { } from "../utils/config-variables"; import logit from "../utils/logit"; import slash from "../utils/slash"; -import COMMAND from "../../common/commands"; +import { ELECTRON_COMMANDS } from "@common/electron-commands"; import getModels from "../utils/get-models"; import { getMainWindow } from "../main-window"; import settings from "electron-settings"; -import { featureFlags } from "../../common/feature-flags"; +import { FEATURE_FLAGS } from "../../common/feature-flags"; const customModelsSelect = async (event, message) => { const mainWindow = getMainWindow(); @@ -27,7 +27,7 @@ const customModelsSelect = async (event, message) => { message: "Select Custom Models Folder that is named 'models'", }); - if (featureFlags.APP_STORE_BUILD && bookmarks && bookmarks.length > 0) { + if (FEATURE_FLAGS.APP_STORE_BUILD && bookmarks && bookmarks.length > 0) { console.log("🚨 Setting Bookmark: ", bookmarks); await settings.set("custom-models-bookmarks", bookmarks[0]); } @@ -55,7 +55,10 @@ const customModelsSelect = async (event, message) => { } const models = await getModels(savedCustomModelsPath); - mainWindow.webContents.send(COMMAND.CUSTOM_MODEL_FILES_LIST, models); + mainWindow.webContents.send( + ELECTRON_COMMANDS.CUSTOM_MODEL_FILES_LIST, + models, + ); logit("📁 Custom Folder Path: ", savedCustomModelsPath); return savedCustomModelsPath; diff --git a/electron/commands/double-upscayl.ts b/electron/commands/double-upscayl.ts index a69cedb..34a81e3 100644 --- a/electron/commands/double-upscayl.ts +++ b/electron/commands/double-upscayl.ts @@ -14,7 +14,7 @@ import { } from "../utils/get-arguments"; import { modelsPath } from "../utils/get-resource-paths"; import logit from "../utils/logit"; -import COMMAND from "../../common/commands"; +import { ELECTRON_COMMANDS } from "@common/electron-commands"; import { DoubleUpscaylPayload } from "../../common/types/types"; import { ImageFormat } from "../types/types"; import showNotification from "../utils/show-notification"; @@ -87,12 +87,15 @@ const doubleUpscayl = async (event, payload: DoubleUpscaylPayload) => { if (!mainWindow) return; data.toString(); // SEND UPSCAYL PROGRESS TO RENDERER - mainWindow.webContents.send(COMMAND.DOUBLE_UPSCAYL_PROGRESS, data); + mainWindow.webContents.send( + ELECTRON_COMMANDS.DOUBLE_UPSCAYL_PROGRESS, + data, + ); // SET FAILED TO TRUE failed2 = true; mainWindow && mainWindow.webContents.send( - COMMAND.UPSCAYL_ERROR, + ELECTRON_COMMANDS.UPSCAYL_ERROR, "Error upscaling image. Error: " + data, ); showNotification("Upscayl Failure", "Failed to upscale image!"); @@ -105,13 +108,16 @@ const doubleUpscayl = async (event, payload: DoubleUpscaylPayload) => { // CONVERT DATA TO STRING data = data.toString(); // SEND UPSCAYL PROGRESS TO RENDERER - mainWindow.webContents.send(COMMAND.DOUBLE_UPSCAYL_PROGRESS, data); + mainWindow.webContents.send( + ELECTRON_COMMANDS.DOUBLE_UPSCAYL_PROGRESS, + data, + ); // IF PROGRESS HAS ERROR, UPSCAYL FAILED if (data.includes("Error")) { upscayl2.kill(); failed2 = true; } else if (data.includes("Resizing")) { - mainWindow.webContents.send(COMMAND.SCALING_AND_CONVERTING); + mainWindow.webContents.send(ELECTRON_COMMANDS.SCALING_AND_CONVERTING); } }; @@ -121,7 +127,10 @@ const doubleUpscayl = async (event, payload: DoubleUpscaylPayload) => { logit("💯 Done upscaling"); mainWindow.setProgressBar(-1); - mainWindow.webContents.send(COMMAND.DOUBLE_UPSCAYL_DONE, outFile); + mainWindow.webContents.send( + ELECTRON_COMMANDS.DOUBLE_UPSCAYL_DONE, + outFile, + ); showNotification("Upscayled", "Image upscayled successfully!"); } }; @@ -132,12 +141,15 @@ const doubleUpscayl = async (event, payload: DoubleUpscaylPayload) => { mainWindow.setProgressBar(-1); data.toString(); // SEND UPSCAYL PROGRESS TO RENDERER - mainWindow.webContents.send(COMMAND.DOUBLE_UPSCAYL_PROGRESS, data); + mainWindow.webContents.send( + ELECTRON_COMMANDS.DOUBLE_UPSCAYL_PROGRESS, + data, + ); // SET FAILED TO TRUE failed = true; mainWindow && mainWindow.webContents.send( - COMMAND.UPSCAYL_ERROR, + ELECTRON_COMMANDS.UPSCAYL_ERROR, "Error upscaling image. Error: " + data, ); showNotification("Upscayl Failure", "Failed to upscale image!"); @@ -150,13 +162,16 @@ const doubleUpscayl = async (event, payload: DoubleUpscaylPayload) => { // CONVERT DATA TO STRING data = data.toString(); // SEND UPSCAYL PROGRESS TO RENDERER - mainWindow.webContents.send(COMMAND.DOUBLE_UPSCAYL_PROGRESS, data); + mainWindow.webContents.send( + ELECTRON_COMMANDS.DOUBLE_UPSCAYL_PROGRESS, + data, + ); // IF PROGRESS HAS ERROR, UPSCAYL FAILED if (data.includes("Error") || data.includes("failed")) { upscayl.kill(); failed = true; } else if (data.includes("Resizing")) { - mainWindow.webContents.send(COMMAND.SCALING_AND_CONVERTING); + mainWindow.webContents.send(ELECTRON_COMMANDS.SCALING_AND_CONVERTING); } }; diff --git a/electron/commands/get-models-list.ts b/electron/commands/get-models-list.ts index dd53bdf..cce3e37 100644 --- a/electron/commands/get-models-list.ts +++ b/electron/commands/get-models-list.ts @@ -1,4 +1,4 @@ -import COMMAND from "../../common/commands"; +import { ELECTRON_COMMANDS } from "@common/electron-commands"; import { getMainWindow } from "../main-window"; import { savedCustomModelsPath, @@ -17,7 +17,10 @@ const getModelsList = async (event, payload) => { logit("📁 Custom Models Folder Path: ", savedCustomModelsPath); const models = await getModels(payload); - mainWindow.webContents.send(COMMAND.CUSTOM_MODEL_FILES_LIST, models); + mainWindow.webContents.send( + ELECTRON_COMMANDS.CUSTOM_MODEL_FILES_LIST, + models, + ); } }; diff --git a/electron/commands/image-upscayl.ts b/electron/commands/image-upscayl.ts index c0fff02..9a7ce50 100644 --- a/electron/commands/image-upscayl.ts +++ b/electron/commands/image-upscayl.ts @@ -1,6 +1,6 @@ import fs from "fs"; import { modelsPath } from "../utils/get-resource-paths"; -import COMMAND from "../../common/commands"; +import { ELECTRON_COMMANDS } from "@common/electron-commands"; import { savedCustomModelsPath, setChildProcesses, @@ -60,14 +60,17 @@ const imageUpscayl = async (event, payload: ImageUpscaylPayload) => { // Check if windows can write the new filename to the file system if (outFile.length >= 255) { logit("Filename too long for Windows."); - mainWindow.webContents.send(COMMAND.UPSCAYL_ERROR, "The filename exceeds the maximum path length allowed by Windows. Please shorten the filename or choose a different save location."); + mainWindow.webContents.send( + ELECTRON_COMMANDS.UPSCAYL_ERROR, + "The filename exceeds the maximum path length allowed by Windows. Please shorten the filename or choose a different save location.", + ); } - + // UPSCALE if (fs.existsSync(outFile) && !overwrite) { // If already upscayled, just output that file logit("✅ Already upscayled at: ", outFile); - mainWindow.webContents.send(COMMAND.UPSCAYL_DONE, outFile); + mainWindow.webContents.send(ELECTRON_COMMANDS.UPSCAYL_DONE, outFile); } else { logit( "✅ Upscayl Variables: ", @@ -115,18 +118,24 @@ const imageUpscayl = async (event, payload: ImageUpscaylPayload) => { logit(data.toString()); mainWindow.setProgressBar(parseFloat(data.slice(0, data.length)) / 100); data = data.toString(); - mainWindow.webContents.send(COMMAND.UPSCAYL_PROGRESS, data.toString()); + mainWindow.webContents.send( + ELECTRON_COMMANDS.UPSCAYL_PROGRESS, + data.toString(), + ); if (data.includes("Error")) { upscayl.kill(); failed = true; } else if (data.includes("Resizing")) { - mainWindow.webContents.send(COMMAND.SCALING_AND_CONVERTING); + mainWindow.webContents.send(ELECTRON_COMMANDS.SCALING_AND_CONVERTING); } }; const onError = (data) => { if (!mainWindow) return; mainWindow.setProgressBar(-1); - mainWindow.webContents.send(COMMAND.UPSCAYL_ERROR, data.toString()); + mainWindow.webContents.send( + ELECTRON_COMMANDS.UPSCAYL_ERROR, + data.toString(), + ); failed = true; upscayl.kill(); return; @@ -137,7 +146,7 @@ const imageUpscayl = async (event, payload: ImageUpscaylPayload) => { // Free up memory upscayl.kill(); mainWindow.setProgressBar(-1); - mainWindow.webContents.send(COMMAND.UPSCAYL_DONE, outFile); + mainWindow.webContents.send(ELECTRON_COMMANDS.UPSCAYL_DONE, outFile); showNotification("Upscayl", "Image upscayled successfully!"); } }; diff --git a/electron/commands/select-file.ts b/electron/commands/select-file.ts index e812958..84858ca 100644 --- a/electron/commands/select-file.ts +++ b/electron/commands/select-file.ts @@ -3,7 +3,7 @@ import { getMainWindow } from "../main-window"; import { savedImagePath, setSavedImagePath } from "../utils/config-variables"; import logit from "../utils/logit"; import settings from "electron-settings"; -import { featureFlags } from "../../common/feature-flags"; +import { FEATURE_FLAGS } from "../../common/feature-flags"; const selectFile = async () => { const mainWindow = getMainWindow(); @@ -33,7 +33,7 @@ const selectFile = async () => { ], }); - if (featureFlags.APP_STORE_BUILD && bookmarks && bookmarks.length > 0) { + if (FEATURE_FLAGS.APP_STORE_BUILD && bookmarks && bookmarks.length > 0) { console.log("🚨 Setting Bookmark: ", bookmarks); settings.set("file-bookmarks", bookmarks[0]); } diff --git a/electron/commands/select-folder.ts b/electron/commands/select-folder.ts index 96bb77f..c3dc603 100644 --- a/electron/commands/select-folder.ts +++ b/electron/commands/select-folder.ts @@ -5,12 +5,12 @@ import { } from "../utils/config-variables"; import logit from "../utils/logit"; import settings from "electron-settings"; -import { featureFlags } from "../../common/feature-flags"; +import { FEATURE_FLAGS } from "../../common/feature-flags"; const selectFolder = async (event, message) => { let closeAccess; const folderBookmarks = await settings.get("folder-bookmarks"); - if (featureFlags.APP_STORE_BUILD && folderBookmarks) { + if (FEATURE_FLAGS.APP_STORE_BUILD && folderBookmarks) { logit("🚨 Folder Bookmarks: ", folderBookmarks); try { closeAccess = app.startAccessingSecurityScopedResource( @@ -31,7 +31,7 @@ const selectFolder = async (event, message) => { securityScopedBookmarks: true, }); - if (featureFlags.APP_STORE_BUILD && bookmarks && bookmarks.length > 0) { + if (FEATURE_FLAGS.APP_STORE_BUILD && bookmarks && bookmarks.length > 0) { console.log("🚨 Setting folder Bookmark: ", bookmarks); await settings.set("folder-bookmarks", bookmarks[0]); } diff --git a/electron/index.ts b/electron/index.ts index 6626e65..0bc3ebf 100644 --- a/electron/index.ts +++ b/electron/index.ts @@ -2,7 +2,7 @@ import prepareNext from "electron-next"; import { autoUpdater } from "electron-updater"; import log from "electron-log"; import { app, ipcMain, protocol } from "electron"; -import COMMAND from "../common/commands"; +import { ELECTRON_COMMANDS } from "../common/electron-commands"; import logit from "./utils/logit"; import openFolder from "./commands/open-folder"; import stop from "./commands/stop"; @@ -17,7 +17,7 @@ import { execPath, modelsPath } from "./utils/get-resource-paths"; import batchUpscayl from "./commands/batch-upscayl"; import doubleUpscayl from "./commands/double-upscayl"; import autoUpdate from "./commands/auto-update"; -import { featureFlags } from "../common/feature-flags"; +import { FEATURE_FLAGS } from "../common/feature-flags"; import settings from "electron-settings"; // INITIALIZATION @@ -43,14 +43,14 @@ app.on("ready", async () => { log.info( "🆙 Upscayl version:", app.getVersion(), - featureFlags.APP_STORE_BUILD && "MAC-APP-STORE", + FEATURE_FLAGS.APP_STORE_BUILD && "MAC-APP-STORE", ); log.info("🚀 UPSCAYL EXEC PATH: ", execPath); log.info("🚀 MODELS PATH: ", modelsPath); let closeAccess; const folderBookmarks = await settings.get("folder-bookmarks"); - if (featureFlags.APP_STORE_BUILD && folderBookmarks) { + if (FEATURE_FLAGS.APP_STORE_BUILD && folderBookmarks) { logit("🚨 Folder Bookmarks: ", folderBookmarks); try { closeAccess = app.startAccessingSecurityScopedResource( @@ -68,29 +68,32 @@ app.on("window-all-closed", () => { }); // ! ENABLE THIS FOR MACOS APP STORE BUILD -if (featureFlags.APP_STORE_BUILD) { +if (FEATURE_FLAGS.APP_STORE_BUILD) { logit("🚀 APP STORE BUILD ENABLED"); app.commandLine.appendSwitch("in-process-gpu"); } -ipcMain.on(COMMAND.STOP, stop); +ipcMain.on(ELECTRON_COMMANDS.STOP, stop); -ipcMain.on(COMMAND.OPEN_FOLDER, openFolder); +ipcMain.on(ELECTRON_COMMANDS.OPEN_FOLDER, openFolder); -ipcMain.handle(COMMAND.SELECT_FOLDER, selectFolder); +ipcMain.handle(ELECTRON_COMMANDS.SELECT_FOLDER, selectFolder); -ipcMain.handle(COMMAND.SELECT_FILE, selectFile); +ipcMain.handle(ELECTRON_COMMANDS.SELECT_FILE, selectFile); -ipcMain.on(COMMAND.GET_MODELS_LIST, getModelsList); +ipcMain.on(ELECTRON_COMMANDS.GET_MODELS_LIST, getModelsList); -ipcMain.handle(COMMAND.SELECT_CUSTOM_MODEL_FOLDER, customModelsSelect); +ipcMain.handle( + ELECTRON_COMMANDS.SELECT_CUSTOM_MODEL_FOLDER, + customModelsSelect, +); -ipcMain.on(COMMAND.UPSCAYL, imageUpscayl); +ipcMain.on(ELECTRON_COMMANDS.UPSCAYL, imageUpscayl); -ipcMain.on(COMMAND.FOLDER_UPSCAYL, batchUpscayl); +ipcMain.on(ELECTRON_COMMANDS.FOLDER_UPSCAYL, batchUpscayl); -ipcMain.on(COMMAND.DOUBLE_UPSCAYL, doubleUpscayl); +ipcMain.on(ELECTRON_COMMANDS.DOUBLE_UPSCAYL, doubleUpscayl); -if (!featureFlags.APP_STORE_BUILD) { +if (!FEATURE_FLAGS.APP_STORE_BUILD) { autoUpdater.on("update-downloaded", autoUpdate); } diff --git a/electron/main-window.ts b/electron/main-window.ts index 672eec0..0c824e7 100644 --- a/electron/main-window.ts +++ b/electron/main-window.ts @@ -1,7 +1,7 @@ import { BrowserWindow, shell } from "electron"; import { getPlatform } from "./utils/get-device-specs"; import { join } from "path"; -import COMMAND from "../common/commands"; +import { ELECTRON_COMMANDS } from "../common/electron-commands"; import { fetchLocalStorage } from "./utils/config-variables"; import electronIsDev from "electron-is-dev"; import { format } from "url"; @@ -49,7 +49,7 @@ const createMainWindow = () => { fetchLocalStorage(); - mainWindow.webContents.send(COMMAND.OS, getPlatform()); + mainWindow.webContents.send(ELECTRON_COMMANDS.OS, getPlatform()); mainWindow.setMenuBarVisibility(false); }; diff --git a/electron/utils/get-models.ts b/electron/utils/get-models.ts index 4905b79..e9b2d80 100644 --- a/electron/utils/get-models.ts +++ b/electron/utils/get-models.ts @@ -2,7 +2,7 @@ import fs from "fs"; import logit from "./logit"; import { MessageBoxOptions, app, dialog } from "electron"; import settings from "electron-settings"; -import { featureFlags } from "../../common/feature-flags"; +import { FEATURE_FLAGS } from "../../common/feature-flags"; const getModels = async (folderPath: string | undefined) => { let models: string[] = []; @@ -11,14 +11,14 @@ const getModels = async (folderPath: string | undefined) => { // SECURITY SCOPED BOOKMARKS let closeAccess; const customModelsBookmarks = await settings.get("custom-models-bookmarks"); - if (featureFlags.APP_STORE_BUILD && customModelsBookmarks) { + if (FEATURE_FLAGS.APP_STORE_BUILD && customModelsBookmarks) { console.log( "🚀 => file: get-models.ts:18 => customModelsBookmarks:", - customModelsBookmarks + customModelsBookmarks, ); try { closeAccess = app.startAccessingSecurityScopedResource( - customModelsBookmarks as string + customModelsBookmarks as string, ); } catch (error) { logit("📁 Custom Models Bookmarks Error: ", error); diff --git a/electron/utils/logit.ts b/electron/utils/logit.ts index a91a33f..b80a8f7 100644 --- a/electron/utils/logit.ts +++ b/electron/utils/logit.ts @@ -1,5 +1,5 @@ import log from "electron-log"; -import COMMAND from "../../common/commands"; +import { ELECTRON_COMMANDS } from "@common/electron-commands"; import { getMainWindow } from "../main-window"; const logit = (...args: any) => { @@ -9,7 +9,7 @@ const logit = (...args: any) => { if (process.env.NODE_ENV === "development") { return; } - mainWindow.webContents.send(COMMAND.LOG, args.join(" ")); + mainWindow.webContents.send(ELECTRON_COMMANDS.LOG, args.join(" ")); }; export default logit; diff --git a/renderer/atoms/logAtom.ts b/renderer/atoms/log-atom.ts similarity index 100% rename from renderer/atoms/logAtom.ts rename to renderer/atoms/log-atom.ts diff --git a/renderer/atoms/modelsListAtom.ts b/renderer/atoms/models-list-atom.ts similarity index 100% rename from renderer/atoms/modelsListAtom.ts rename to renderer/atoms/models-list-atom.ts diff --git a/renderer/atoms/newsAtom.ts b/renderer/atoms/news-atom.ts similarity index 100% rename from renderer/atoms/newsAtom.ts rename to renderer/atoms/news-atom.ts diff --git a/renderer/atoms/translations-atom.ts b/renderer/atoms/translations-atom.ts index 8d7e9fc..67f45ba 100644 --- a/renderer/atoms/translations-atom.ts +++ b/renderer/atoms/translations-atom.ts @@ -11,11 +11,40 @@ import { atomWithStorage } from "jotai/utils"; type Translations = typeof en; type Locales = "en" | "ru" | "ja" | "zh" | "es" | "fr"; +const translations: Record = { + en, + ru, + ja, + zh, + es, + fr, +}; + +// Create a type for nested key paths +type NestedKeyOf = Object extends object + ? { + [Key in keyof Object]: Key extends string | number + ? Key | `${Key}.${NestedKeyOf}` + : never; + }[keyof Object] + : never; + // Utility function to access nested translation keys -const getNestedTranslation = (obj: Translations, key: string): string => { - return ( - key.split(".").reduce((acc, part) => acc && (acc as any)[part], obj) || key - ); +const getNestedTranslation = ( + obj: Translations, + key: NestedKeyOf, +): string => { + // Split the key into an array of nested parts + const keyParts = key.split("."); + + // Traverse the object using the key parts + const result = keyParts.reduce((currentObj, part) => { + // If currentObj is falsy or doesn't have the property, return undefined + return currentObj && currentObj[part]; + }, obj); + + // Return the found translation or the original key if not found + return result || key; }; // Atom to store the current locale @@ -24,16 +53,11 @@ export const localeAtom = atomWithStorage("language", "en"); // Atom to get the translation function based on the current locale export const translationAtom = atom((get) => { const locale = get(localeAtom); - const translations: Record = { - en, - ru, - ja, - zh, - es, - fr, - }; - return (key: string, params: Record = {}): string => { + return ( + key: NestedKeyOf, + params: Record = {}, + ): string => { const template = getNestedTranslation(translations[locale], key); // Replace placeholders with parameters, e.g., {name} => John diff --git a/renderer/atoms/userSettingsAtom.ts b/renderer/atoms/user-settings-atom.ts similarity index 100% rename from renderer/atoms/userSettingsAtom.ts rename to renderer/atoms/user-settings-atom.ts diff --git a/renderer/components/Footer.tsx b/renderer/components/Footer.tsx index 3f03a9e..664456e 100644 --- a/renderer/components/Footer.tsx +++ b/renderer/components/Footer.tsx @@ -1,4 +1,4 @@ -import { newsAtom, showNewsModalAtom } from "@/atoms/newsAtom"; +import { newsAtom, showNewsModalAtom } from "@/atoms/news-atom"; import { translationAtom } from "@/atoms/translations-atom"; import { useAtomValue, useSetAtom } from "jotai"; import React from "react"; diff --git a/renderer/components/Header.tsx b/renderer/components/Header.tsx index ba380ce..71c8047 100644 --- a/renderer/components/Header.tsx +++ b/renderer/components/Header.tsx @@ -1,6 +1,6 @@ -import { featureFlags } from "@common/feature-flags"; +import { FEATURE_FLAGS } from "@common/feature-flags"; import React from "react"; -import Logo from "./icons/Logo"; +import UpscaylSVGLogo from "@/components/icons/upscayl-logo-svg"; import { useAtomValue } from "jotai"; import { translationAtom } from "@/atoms/translations-atom"; @@ -16,12 +16,12 @@ export default function Header({ version }: { version: string }) { data-tooltip-content={t("HEADER.GITHUB_BUTTON_TITLE")} >
- +

{t("TITLE")}{" "} - {version} {featureFlags.APP_STORE_BUILD && "Mac"} + {version} {FEATURE_FLAGS.APP_STORE_BUILD && "Mac"}

{t("HEADER.DESCRIPTION")}

diff --git a/renderer/components/NewsModal.tsx b/renderer/components/NewsModal.tsx deleted file mode 100644 index 12074ac..0000000 --- a/renderer/components/NewsModal.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { translationAtom } from "@/atoms/translations-atom"; -import { GrayMatterFile } from "gray-matter"; -import { useAtomValue } from "jotai"; -import React from "react"; -import Markdown from "react-markdown"; -import remarkGfm from "remark-gfm"; - -export const NewsModal = ({ - show, - setShow, - news, -}: { - show: boolean; - setShow: React.Dispatch>; - news: GrayMatterFile; -}) => { - const t = useAtomValue(translationAtom); - - return ( - -
- - -
- {news && ( - - {news.content} - - )} -
-
- -
- -
-
- ); -}; diff --git a/renderer/components/hooks/use-custom-models.ts b/renderer/components/hooks/use-custom-models.ts new file mode 100644 index 0000000..9900916 --- /dev/null +++ b/renderer/components/hooks/use-custom-models.ts @@ -0,0 +1,17 @@ +import { ELECTRON_COMMANDS } from "@common/electron-commands"; +import { useEffect } from "react"; +import useLogger from "./use-logger"; + +export const initCustomModels = () => { + const logit = useLogger(); + + useEffect(() => { + const customModelsPath = JSON.parse( + localStorage.getItem("customModelsPath"), + ); + if (customModelsPath !== null) { + window.electron.send(ELECTRON_COMMANDS.GET_MODELS_LIST, customModelsPath); + logit("🎯 GET_MODELS_LIST: ", customModelsPath); + } + }, []); +}; diff --git a/renderer/components/hooks/use-electron.ts b/renderer/components/hooks/use-electron.ts new file mode 100644 index 0000000..ef68ba3 --- /dev/null +++ b/renderer/components/hooks/use-electron.ts @@ -0,0 +1,17 @@ +import { useEffect } from "react"; +import { ELECTRON_COMMANDS } from "@common/electron-commands"; + +const useElectron = ({ + command, + func, +}: { + command: (typeof ELECTRON_COMMANDS)[keyof typeof ELECTRON_COMMANDS]; + func: (...args: any[]) => void; +}) => { + useEffect(() => { + window.electron.on(command, func); + return () => { + window.electron.off(command, func); + }; + }, []); +}; diff --git a/renderer/components/hooks/useLog.tsx b/renderer/components/hooks/use-logger.ts similarity index 72% rename from renderer/components/hooks/useLog.tsx rename to renderer/components/hooks/use-logger.ts index 3ddeb66..0b5d7ca 100644 --- a/renderer/components/hooks/useLog.tsx +++ b/renderer/components/hooks/use-logger.ts @@ -1,9 +1,9 @@ -import { logAtom } from "../../atoms/logAtom"; +import { logAtom } from "../../atoms/log-atom"; import log from "electron-log/renderer"; import { useSetAtom } from "jotai"; import React from "react"; -const useLog = () => { +const useLogger = () => { const setLogData = useSetAtom(logAtom); const logit = (...args: any) => { @@ -13,9 +13,7 @@ const useLog = () => { setLogData((prevLogData) => [...prevLogData, data]); }; - return { - logit, - }; + return logit; }; -export default useLog; +export default useLogger; diff --git a/renderer/components/hooks/use-translation.ts b/renderer/components/hooks/use-translation.ts new file mode 100644 index 0000000..ad3e7c0 --- /dev/null +++ b/renderer/components/hooks/use-translation.ts @@ -0,0 +1,9 @@ +import { translationAtom } from "@/atoms/translations-atom"; +import { useAtomValue } from "jotai"; + +const useTranslation = () => { + const t = useAtomValue(translationAtom); + return t; +}; + +export default useTranslation; diff --git a/renderer/components/hooks/use-upscayl-version.ts b/renderer/components/hooks/use-upscayl-version.ts new file mode 100644 index 0000000..9c7d4a2 --- /dev/null +++ b/renderer/components/hooks/use-upscayl-version.ts @@ -0,0 +1,16 @@ +import { useState, useEffect } from "react"; + +const useUpscaylVersion = () => { + const [version, setVersion] = useState(null); + + useEffect(() => { + const upscaylVersion = navigator?.userAgent?.match( + /Upscayl\/([\d\.]+\d+)/, + )?.[1]; + setVersion(upscaylVersion); + }, []); + + return version; +}; + +export default useUpscaylVersion; diff --git a/renderer/components/icons/SidebarClosed.tsx b/renderer/components/icons/sidebar-closed-svg.tsx similarity index 100% rename from renderer/components/icons/SidebarClosed.tsx rename to renderer/components/icons/sidebar-closed-svg.tsx diff --git a/renderer/components/icons/SidebarOpened.tsx b/renderer/components/icons/sidebar-opened-svg.tsx similarity index 100% rename from renderer/components/icons/SidebarOpened.tsx rename to renderer/components/icons/sidebar-opened-svg.tsx diff --git a/renderer/components/icons/Spinner.tsx b/renderer/components/icons/spinner-svg.tsx similarity index 91% rename from renderer/components/icons/Spinner.tsx rename to renderer/components/icons/spinner-svg.tsx index 7e71d5d..e6490ec 100644 --- a/renderer/components/icons/Spinner.tsx +++ b/renderer/components/icons/spinner-svg.tsx @@ -1,7 +1,3 @@ -// React SVG Component - -import React from "react"; - function Spinner() { return ( // By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL @@ -10,7 +6,8 @@ function Spinner() { fill="currentColor" stroke="currentColor" xmlns="http://www.w3.org/2000/svg" - className="h-16 w-16 rounded-full bg-base-300 p-2 text-base-content"> + className="h-16 w-16 rounded-full bg-base-300 p-2 text-base-content" + > + fill="currentColor" + > + fill="currentColor" + > + fill="currentColor" + > + fill="currentColor" + > + fill="currentColor" + > + fill="currentColor" + > + fill="currentColor" + > { +const UpscaylSVGLogo = ({ ...rest }) => { return ( { ); }; -export default Logo; +export default UpscaylSVGLogo; diff --git a/renderer/components/upscayl-tab/view/ImageOptions.tsx b/renderer/components/main-content/image-view-settings.tsx similarity index 95% rename from renderer/components/upscayl-tab/view/ImageOptions.tsx rename to renderer/components/main-content/image-view-settings.tsx index 9e7f51c..ddcda52 100644 --- a/renderer/components/upscayl-tab/view/ImageOptions.tsx +++ b/renderer/components/main-content/image-view-settings.tsx @@ -1,20 +1,18 @@ import { translationAtom } from "@/atoms/translations-atom"; -import { lensSizeAtom, viewTypeAtom } from "@/atoms/userSettingsAtom"; +import { lensSizeAtom, viewTypeAtom } from "@/atoms/user-settings-atom"; import { cn } from "@/lib/utils"; import { useAtom, useAtomValue } from "jotai"; import { WrenchIcon } from "lucide-react"; import { useEffect, useState } from "react"; -const ImageOptions = ({ +const ImageViewSettings = ({ zoomAmount, setZoomAmount, resetImagePaths, - hideZoomOptions, }: { zoomAmount: string; setZoomAmount: (arg: any) => void; resetImagePaths: () => void; - hideZoomOptions?: boolean; }) => { const [openSidebar, setOpenSidebar] = useState(false); const [viewType, setViewType] = useAtom(viewTypeAtom); @@ -113,4 +111,4 @@ const ImageOptions = ({ ); }; -export default ImageOptions; +export default ImageViewSettings; diff --git a/renderer/components/main-content/image-viewer.tsx b/renderer/components/main-content/image-viewer.tsx new file mode 100644 index 0000000..ecbd493 --- /dev/null +++ b/renderer/components/main-content/image-viewer.tsx @@ -0,0 +1,26 @@ +import { sanitizePath } from "@common/sanitize-path"; + +const ImageViewer = ({ + imagePath, + setDimensions, +}: { + imagePath: string; + setDimensions: (dimensions: { width: number; height: number }) => void; +}) => { + return ( + ) => { + setDimensions({ + width: e.currentTarget.naturalWidth, + height: e.currentTarget.naturalHeight, + }); + }} + draggable="false" + alt="" + className="h-full w-full bg-gradient-to-br from-base-300 to-base-100 object-contain" + /> + ); +}; + +export default ImageViewer; diff --git a/renderer/components/main-content/index.tsx b/renderer/components/main-content/index.tsx new file mode 100644 index 0000000..44298b9 --- /dev/null +++ b/renderer/components/main-content/index.tsx @@ -0,0 +1,292 @@ +"use client"; +import useLogger from "../hooks/use-logger"; +import { useState, useMemo } from "react"; +import { ELECTRON_COMMANDS } from "@common/electron-commands"; +import { useAtomValue, useSetAtom } from "jotai"; +import { + batchModeAtom, + lensSizeAtom, + savedOutputPathAtom, + progressAtom, + viewTypeAtom, + rememberOutputFolderAtom, +} from "../../atoms/user-settings-atom"; +import { useToast } from "@/components/ui/use-toast"; +import { sanitizePath } from "@common/sanitize-path"; +import getDirectoryFromPath from "@common/get-directory-from-path"; +import { FEATURE_FLAGS } from "@common/feature-flags"; +import { VALID_IMAGE_FORMATS } from "@/lib/valid-formats"; +import ProgressBar from "./progress-bar"; +import InstructionsCard from "./instructions-card"; +import ImageViewSettings from "./image-view-settings"; +import useUpscaylVersion from "../hooks/use-upscayl-version"; +import MacTitlebarDragRegion from "./mac-titlebar-drag-region"; +import LensViewer from "./lens-view"; +import ImageViewer from "./image-viewer"; +import useTranslation from "../hooks/use-translation"; +import SliderView from "./slider-view"; + +type MainContentProps = { + imagePath: string; + resetImagePaths: () => void; + upscaledBatchFolderPath: string; + setImagePath: React.Dispatch>; + validateImagePath: (path: string) => void; + selectFolderHandler: () => void; + selectImageHandler: () => void; + upscaledImagePath: string; + batchFolderPath: string; + doubleUpscaylCounter: number; + setDimensions: React.Dispatch< + React.SetStateAction<{ + width: number; + height: number; + }> + >; +}; + +const MainContent = ({ + imagePath, + resetImagePaths, + upscaledBatchFolderPath, + setImagePath, + validateImagePath, + selectFolderHandler, + selectImageHandler, + upscaledImagePath, + batchFolderPath, + doubleUpscaylCounter, + setDimensions, +}: MainContentProps) => { + const t = useTranslation(); + const logit = useLogger(); + const { toast } = useToast(); + const version = useUpscaylVersion(); + + const setOutputPath = useSetAtom(savedOutputPathAtom); + const progress = useAtomValue(progressAtom); + const batchMode = useAtomValue(batchModeAtom); + + const viewType = useAtomValue(viewTypeAtom); + const lensSize = useAtomValue(lensSizeAtom); + const rememberOutputFolder = useAtomValue(rememberOutputFolderAtom); + const [zoomAmount, setZoomAmount] = useState("100"); + + const sanitizedUpscaledImagePath = useMemo( + () => sanitizePath(upscaledImagePath), + [upscaledImagePath], + ); + + const showInformationCard = useMemo(() => { + if (!batchMode) { + return imagePath.length === 0 && upscaledImagePath.length === 0; + } else { + return ( + batchFolderPath.length === 0 && upscaledBatchFolderPath.length === 0 + ); + } + }, [ + batchMode, + imagePath, + upscaledImagePath, + batchFolderPath, + upscaledBatchFolderPath, + ]); + + // 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) => { + const logit = useLogger(); + logit("📂 OPEN_FOLDER: ", upscaledBatchFolderPath); + window.electron.send( + ELECTRON_COMMANDS.OPEN_FOLDER, + upscaledBatchFolderPath, + ); + }; + + const sanitizedImagePath = useMemo( + () => sanitizePath(imagePath), + [imagePath], + ); + + 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("ERRORS.INVALID_IMAGE_ERROR.TITLE"), + description: t("ERRORS.INVALID_IMAGE_ERROR.ADDITIONAL_DESCRIPTION"), + }); + 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") || + !VALID_IMAGE_FORMATS.includes(extension.toLowerCase()) + ) { + logit("🚫 Invalid file dropped"); + toast({ + title: t("ERRORS.INVALID_IMAGE_ERROR.TITLE"), + description: t("ERRORS.INVALID_IMAGE_ERROR.ADDITIONAL_DESCRIPTION"), + }); + } else { + logit("🖼 Setting image path: ", filePath); + setImagePath(filePath); + const dirname = getDirectoryFromPath(filePath); + logit("🗂 Setting output path: ", dirname); + if (!FEATURE_FLAGS.APP_STORE_BUILD) { + if (!rememberOutputFolder) { + setOutputPath(dirname); + } + } + validateImagePath(filePath); + } + }; + + const handlePaste = (e: React.ClipboardEvent) => { + resetImagePaths(); + e.preventDefault(); + const items = e.clipboardData.items; + const files = e.clipboardData.files; + if (items.length === 0 || files.length === 0) { + toast({ + title: t("ERRORS.INVALID_IMAGE_ERROR.TITLE"), + description: t("ERRORS.INVALID_IMAGE_ERROR.ADDITIONAL_DESCRIPTION"), + }); + return; + } + const type = items[0].type; + const filePath = files[0].path; + const extension = files[0].name.split(".").at(-1); + logit("📋 Pasted file: ", JSON.stringify({ type, filePath, extension })); + if ( + !type.includes("image") && + !VALID_IMAGE_FORMATS.includes(extension.toLowerCase()) + ) { + toast({ + title: t("ERRORS.INVALID_IMAGE_ERROR.TITLE"), + description: t("ERRORS.INVALID_IMAGE_ERROR.ADDITIONAL_DESCRIPTION"), + }); + } else { + setImagePath(filePath); + const dirname = getDirectoryFromPath(filePath); + logit("🗂 Setting output path: ", dirname); + if (!FEATURE_FLAGS.APP_STORE_BUILD) { + if (!rememberOutputFolder) { + setOutputPath(dirname); + } + } + validateImagePath(filePath); + } + }; + + return ( +
+ + + {progress.length > 0 && + upscaledImagePath.length === 0 && + upscaledBatchFolderPath.length === 0 && ( + + )} + + {/* DEFAULT PANE INFO */} + {showInformationCard && ( + + )} + + + + {/* SHOW SELECTED IMAGE */} + {!batchMode && upscaledImagePath.length === 0 && imagePath.length > 0 && ( + + )} + + {/* BATCH UPSCALE SHOW SELECTED FOLDER */} + {batchMode && + upscaledBatchFolderPath.length === 0 && + batchFolderPath.length > 0 && ( +

+ + {t("APP.PROGRESS.BATCH.SELECTED_FOLDER_TITLE")} + {" "} + {batchFolderPath} +

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

+ {t("APP.PROGRESS.BATCH.DONE_TITLE")} +

+ +
+ )} + + {!batchMode && viewType === "lens" && upscaledImagePath && imagePath && ( + + )} + + {/* COMPARISON SLIDER */} + {!batchMode && + viewType === "slider" && + imagePath.length > 0 && + upscaledImagePath.length > 0 && ( + + )} +
+ ); +}; + +export default MainContent; diff --git a/renderer/components/upscayl-tab/view/RightPaneInfo.tsx b/renderer/components/main-content/instructions-card.tsx similarity index 91% rename from renderer/components/upscayl-tab/view/RightPaneInfo.tsx rename to renderer/components/main-content/instructions-card.tsx index 18511c4..0283035 100644 --- a/renderer/components/upscayl-tab/view/RightPaneInfo.tsx +++ b/renderer/components/main-content/instructions-card.tsx @@ -2,7 +2,7 @@ import { translationAtom } from "@/atoms/translations-atom"; import { useAtomValue } from "jotai"; import React from "react"; -function RightPaneInfo({ version, batchMode }) { +function InstructionsCard({ version, batchMode }) { const t = useAtomValue(translationAtom); return ( @@ -26,4 +26,4 @@ function RightPaneInfo({ version, batchMode }) { ); } -export default RightPaneInfo; +export default InstructionsCard; diff --git a/renderer/components/main-content/lens-view.tsx b/renderer/components/main-content/lens-view.tsx new file mode 100644 index 0000000..ac27949 --- /dev/null +++ b/renderer/components/main-content/lens-view.tsx @@ -0,0 +1,104 @@ +import React, { useRef, useState } from "react"; + +const LensImage = ({ + src, + alt, + lensPosition, + zoomAmount, +}: { + src: string; + alt: string; + lensPosition: { x: number; y: number }; + zoomAmount: number; +}) => ( +
+ {alt} +
+); + +const LensViewer = ({ + zoomAmount, + lensSize, + sanitizedImagePath, + sanitizedUpscaledImagePath, +}: { + zoomAmount: string; + lensSize: number; + sanitizedImagePath: string; + sanitizedUpscaledImagePath: string; +}) => { + const upscaledImageRef = useRef(null); + + const [lensPosition, setLensPosition] = useState({ x: 0, y: 0 }); + + const handleMouseMoveCompare = (e: React.MouseEvent) => { + if (upscaledImageRef.current) { + const { left, top, width, height } = + upscaledImageRef.current.getBoundingClientRect(); + const x = e.clientX - left; + const y = e.clientY - top; + setLensPosition({ + x: Math.max(0, Math.min(x - lensSize, width - lensSize * 2)), + y: Math.max(0, Math.min(y - lensSize / 2, height - lensSize)), + }); + } + }; + + return ( +
+ {/* UPSCALED IMAGE */} + Upscaled + {/* LENS */} +
+
+ + +
+
+ Original + Upscayl +
+
+
+ ); +}; + +export default LensViewer; diff --git a/renderer/components/main-content/mac-titlebar-drag-region.tsx b/renderer/components/main-content/mac-titlebar-drag-region.tsx new file mode 100644 index 0000000..97995aa --- /dev/null +++ b/renderer/components/main-content/mac-titlebar-drag-region.tsx @@ -0,0 +1,7 @@ +const MacTitlebarDragRegion = () => { + return window.electron.platform === "mac" ? ( +
+ ) : null; +}; + +export default MacTitlebarDragRegion; diff --git a/renderer/components/upscayl-tab/view/ProgressBar.tsx b/renderer/components/main-content/progress-bar.tsx similarity index 81% rename from renderer/components/upscayl-tab/view/ProgressBar.tsx rename to renderer/components/main-content/progress-bar.tsx index 4b500ca..f3b7f33 100644 --- a/renderer/components/upscayl-tab/view/ProgressBar.tsx +++ b/renderer/components/main-content/progress-bar.tsx @@ -1,22 +1,24 @@ -import React, { CSSProperties, useEffect, useMemo } from "react"; -import Spinner from "../../icons/Spinner"; -import Logo from "@/components/icons/Logo"; +import React, { useEffect } from "react"; +import UpscaylSVGLogo from "@/components/icons/upscayl-logo-svg"; import { useAtomValue } from "jotai"; import { translationAtom } from "@/atoms/translations-atom"; +import { ELECTRON_COMMANDS } from "@common/electron-commands"; +import useLogger from "../hooks/use-logger"; function ProgressBar({ progress, doubleUpscaylCounter, - stopHandler, batchMode, + resetImagePaths, }: { progress: string; doubleUpscaylCounter: number; - stopHandler: () => void; batchMode: boolean; + resetImagePaths: () => void; }) { const [batchProgress, setBatchProgress] = React.useState(0); const t = useAtomValue(translationAtom); + const logit = useLogger(); useEffect(() => { const progressString = progress.trim().replace(/\n/g, ""); @@ -26,6 +28,12 @@ function ProgressBar({ } }, [progress]); + const stopHandler = () => { + window.electron.send(ELECTRON_COMMANDS.STOP); + logit("🛑 Stopping Upscayl"); + resetImagePaths(); + }; + // const progressStyle = useMemo(() => { // if (progress.includes("%")) { // return { @@ -44,11 +52,13 @@ function ProgressBar({ return (
- + +

{batchMode && `${t("APP.PROGRESS_BAR.BATCH_UPSCAYL_IN_PROGRESS_TITLE")} ${batchProgress}`}

+
{progress !== "Hold on..." ? (

@@ -60,10 +70,12 @@ function ProgressBar({ ) : (

{progress}

)} +

{t("APP.PROGRESS_BAR.IN_PROGRESS_TITLE")}

+ diff --git a/renderer/components/upscayl-tab/view/ResetButton.tsx b/renderer/components/main-content/reset-button.tsx similarity index 100% rename from renderer/components/upscayl-tab/view/ResetButton.tsx rename to renderer/components/main-content/reset-button.tsx diff --git a/renderer/components/main-content/slider-view.tsx b/renderer/components/main-content/slider-view.tsx new file mode 100644 index 0000000..e387f1f --- /dev/null +++ b/renderer/components/main-content/slider-view.tsx @@ -0,0 +1,73 @@ +import React, { useCallback, useState } from "react"; +import { ReactCompareSlider } from "react-compare-slider"; +import useTranslation from "../hooks/use-translation"; + +const SliderView = ({ + sanitizedImagePath, + sanitizedUpscaledImagePath, + zoomAmount, +}: { + sanitizedImagePath: string; + sanitizedUpscaledImagePath: string; + zoomAmount: string; +}) => { + const t = useTranslation(); + + const [backgroundPosition, setBackgroundPosition] = useState("0% 0%"); + + 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}%`); + }, []); + + return ( + +

+ {t("APP.SLIDER.ORIGINAL_TITLE")} +

+ + {t("APP.SLIDER.ORIGINAL_TITLE")} + + } + itemTwo={ + <> +

+ {t("APP.SLIDER.UPSCAYLED_TITLE")} +

+ {t("APP.SLIDER.UPSCAYLED_TITLE")} + + } + className="group h-screen" + /> + ); +}; + +export default SliderView; diff --git a/renderer/components/news-modal.tsx b/renderer/components/news-modal.tsx new file mode 100644 index 0000000..51efc7f --- /dev/null +++ b/renderer/components/news-modal.tsx @@ -0,0 +1,110 @@ +import { newsAtom, showNewsModalAtom } from "@/atoms/news-atom"; +import { translationAtom } from "@/atoms/translations-atom"; +import matter, { GrayMatterFile } from "gray-matter"; +import { useAtom, useAtomValue } from "jotai"; +import React, { useEffect } from "react"; +import Markdown from "react-markdown"; +import remarkGfm from "remark-gfm"; + +export const NewsModal = () => { + const t = useAtomValue(translationAtom); + + const [news, setNews] = useAtom(newsAtom); + const [showNewsModal, setShowNewsModal] = useAtom(showNewsModalAtom); + + 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]); + + return ( + +
+ + +
+ {news && ( + + {news.content} + + )} +
+
+ +
+ +
+
+ ); +}; diff --git a/renderer/components/sidebar/index.tsx b/renderer/components/sidebar/index.tsx new file mode 100644 index 0000000..ecd62a3 --- /dev/null +++ b/renderer/components/sidebar/index.tsx @@ -0,0 +1,241 @@ +"use client"; +import { useState } from "react"; +import { useAtom, useAtomValue, useSetAtom } from "jotai"; +import { + batchModeAtom, + compressionAtom, + dontShowCloudModalAtom, + noImageProcessingAtom, + savedOutputPathAtom, + overwriteAtom, + progressAtom, + scaleAtom, + customWidthAtom, + useCustomWidthAtom, + tileSizeAtom, + showSidebarAtom, +} from "../../atoms/user-settings-atom"; +import useLogger from "../hooks/use-logger"; +import { + BatchUpscaylPayload, + DoubleUpscaylPayload, + ImageUpscaylPayload, +} from "@common/types/types"; +import { useToast } from "@/components/ui/use-toast"; +import UpscaylSteps from "./upscayl-tab/upscayl-steps"; +import SettingsTab from "./settings-tab"; +import Footer from "../footer"; +import { NewsModal } from "../news-modal"; +import Tabs from "../tabs"; +import Header from "../header"; +import { ChevronLeftIcon } from "lucide-react"; +import { logAtom } from "@/atoms/log-atom"; +import { ELECTRON_COMMANDS } from "@common/electron-commands"; +import useUpscaylVersion from "../hooks/use-upscayl-version"; +import useTranslation from "../hooks/use-translation"; +import UpscaylLogo from "./upscayl-logo"; +import SidebarToggleButton from "./sidebar-button"; + +const Sidebar = ({ + setUpscaledImagePath, + batchFolderPath, + setUpscaledBatchFolderPath, + dimensions, + imagePath, + selectImageHandler, + selectFolderHandler, +}: { + setUpscaledImagePath: React.Dispatch>; + batchFolderPath: string; + setUpscaledBatchFolderPath: React.Dispatch>; + dimensions: { + width: number | null; + height: number | null; + }; + imagePath: string; + selectImageHandler: () => Promise; + selectFolderHandler: () => Promise; +}) => { + const t = useTranslation(); + const logit = useLogger(); + const { toast } = useToast(); + const version = useUpscaylVersion(); + + // LOCAL STATES + // TODO: Add electron handler for os + const [model, setModel] = useState("realesrgan-x4plus"); + const [doubleUpscayl, setDoubleUpscayl] = useState(false); + const overwrite = useAtomValue(overwriteAtom); + const [gpuId, setGpuId] = useState(""); + const [saveImageAs, setSaveImageAs] = useState("png"); + + const [selectedTab, setSelectedTab] = useState(0); + const [showCloudModal, setShowCloudModal] = useState(false); + + // ATOMIC STATES + const outputPath = useAtomValue(savedOutputPathAtom); + const [compression, setCompression] = useAtom(compressionAtom); + const setProgress = useSetAtom(progressAtom); + const [batchMode, setBatchMode] = useAtom(batchModeAtom); + const logData = useAtomValue(logAtom); + const [scale] = useAtom(scaleAtom); + const setDontShowCloudModal = useSetAtom(dontShowCloudModalAtom); + const noImageProcessing = useAtomValue(noImageProcessingAtom); + const customWidth = useAtomValue(customWidthAtom); + const useCustomWidth = useAtomValue(useCustomWidthAtom); + const tileSize = useAtomValue(tileSizeAtom); + const [showSidebar, setShowSidebar] = useAtom(showSidebarAtom); + + const handleModelChange = (e: any) => { + setModel(e.value); + logit("🔀 Model changed: ", e.value); + localStorage.setItem( + "model", + JSON.stringify({ label: e.label, value: e.value }), + ); + }; + + const upscaylHandler = async () => { + logit("🔄 Resetting Upscaled Image Path"); + setUpscaledImagePath(""); + setUpscaledBatchFolderPath(""); + if (imagePath !== "" || batchFolderPath !== "") { + setProgress(t("APP.PROGRESS.WAIT_TITLE")); + // Double Upscayl + if (doubleUpscayl) { + window.electron.send( + ELECTRON_COMMANDS.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( + ELECTRON_COMMANDS.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(ELECTRON_COMMANDS.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("ERRORS.NO_IMAGE_ERROR.TITLE"), + description: t("ERRORS.NO_IMAGE_ERROR.DESCRIPTION"), + }); + logit("🚫 No valid image selected"); + } + }; + + return ( + <> + {/* TOP LOGO WHEN SIDEBAR IS HIDDEN */} + {!showSidebar && } + + + +
+ + + {window.electron.platform === "mac" && ( +
+ )} + +
+ + + + + + {selectedTab === 0 && ( + + )} + + {selectedTab === 1 && ( + + )} +
+
+ + ); +}; + +export default Sidebar; diff --git a/renderer/components/settings-tab/DonateButton.tsx b/renderer/components/sidebar/settings-tab/donate-button.tsx similarity index 100% rename from renderer/components/settings-tab/DonateButton.tsx rename to renderer/components/sidebar/settings-tab/donate-button.tsx diff --git a/renderer/components/settings-tab/index.tsx b/renderer/components/sidebar/settings-tab/index.tsx similarity index 78% rename from renderer/components/settings-tab/index.tsx rename to renderer/components/sidebar/settings-tab/index.tsx index ad645b4..e5eca27 100644 --- a/renderer/components/settings-tab/index.tsx +++ b/renderer/components/sidebar/settings-tab/index.tsx @@ -1,26 +1,26 @@ -import { ThemeSelect } from "./ThemeSelect"; -import { SaveOutputFolderToggle } from "./SaveOutputFolderToggle"; -import { GpuIdInput } from "./GpuIdInput"; -import { CustomModelsFolderSelect } from "./CustomModelsFolderSelect"; -import { LogArea } from "./LogArea"; -import { ImageScaleSelect } from "./ImageScaleSelect"; -import { ImageFormatSelect } from "./ImageFormatSelect"; -import { DonateButton } from "./DonateButton"; +import { SelectTheme } from "./select-theme"; +import { SaveOutputFolderToggle } from "./save-output-folder-toggle"; +import { InputGpuId } from "./input-gpu-id"; +import { CustomModelsFolderSelect } from "./select-custom-models-folder"; +import { LogArea } from "./log-area"; +import { SelectImageScale } from "./select-image-scale"; +import { SelectImageFormat } from "./select-image-format"; +import { DonateButton } from "./donate-button"; import React, { useEffect, useState } from "react"; import { themeChange } from "theme-change"; import { useAtom, useAtomValue } from "jotai"; -import { customModelsPathAtom, scaleAtom } from "../../atoms/userSettingsAtom"; -import { modelsListAtom } from "../../atoms/modelsListAtom"; -import useLog from "../hooks/useLog"; -import { CompressionInput } from "./CompressionInput"; -import OverwriteToggle from "./OverwriteToggle"; -import { UpscaylCloudModal } from "../UpscaylCloudModal"; -import { ResetSettings } from "./ResetSettings"; -import { featureFlags } from "@common/feature-flags"; -import TurnOffNotificationsToggle from "./TurnOffNotificationsToggle"; +import { customModelsPathAtom, scaleAtom } from "@/atoms/user-settings-atom"; +import { modelsListAtom } from "@/atoms/models-list-atom"; +import useLogger from "@/components/hooks/use-logger"; +import { InputCompression } from "./input-compression"; +import OverwriteToggle from "./overwrite-toggle"; +import { UpscaylCloudModal } from "@/components/upscayl-cloud-modal"; +import { ResetSettingsButton } from "./reset-settings-button"; +import { FEATURE_FLAGS } from "@common/feature-flags"; +import TurnOffNotificationsToggle from "./turn-off-notifications-toggle"; import { cn } from "@/lib/utils"; -import { CustomResolutionInput } from "./CustomResolutionInput"; -import { TileSizeInput } from "./TileSizeInput"; +import { InputCustomResolution } from "./input-custom-resolution"; +import { InputTileSize } from "./input-tile-size"; import LanguageSwitcher from "./language-switcher"; import { translationAtom } from "@/atoms/translations-atom"; @@ -34,7 +34,6 @@ interface IProps { gpuId: string; setGpuId: React.Dispatch>; logData: string[]; - os: "linux" | "mac" | "win" | undefined; show: boolean; setShow: React.Dispatch>; setDontShowCloudModal: React.Dispatch>; @@ -50,19 +49,11 @@ function SettingsTab({ saveImageAs, setSaveImageAs, logData, - os, + show, setShow, setDontShowCloudModal, }: IProps) { - // STATES - const [currentModel, setCurrentModel] = useState<{ - label: string; - value: string; - }>({ - label: null, - value: null, - }); const [isCopied, setIsCopied] = useState(false); const [customModelsPath, setCustomModelsPath] = useAtom(customModelsPathAtom); @@ -72,7 +63,7 @@ function SettingsTab({ const [timeoutId, setTimeoutId] = useState(null); const t = useAtomValue(translationAtom); - const { logit } = useLog(); + const logit = useLogger(); useEffect(() => { themeChange(false); @@ -90,7 +81,6 @@ function SettingsTab({ } if (!localStorage.getItem("model")) { - setCurrentModel(modelOptions[0]); setModel(modelOptions[0].value); localStorage.setItem("model", JSON.stringify(modelOptions[0])); logit("🔀 Setting model to", modelOptions[0].value); @@ -107,7 +97,6 @@ function SettingsTab({ logit("🔀 Setting model to", modelOptions[0].value); currentlySavedModel = modelOptions[0]; } - setCurrentModel(currentlySavedModel); setModel(currentlySavedModel.value); logit( "⚙️ Getting model from localStorage: ", @@ -194,7 +183,7 @@ function SettingsTab({ > {t("SETTINGS.SUPPORT.DOCS_BUTTON_TITLE")} - {featureFlags.APP_STORE_BUILD && ( + {FEATURE_FLAGS.APP_STORE_BUILD && ( )} - {!featureFlags.APP_STORE_BUILD && } + {!FEATURE_FLAGS.APP_STORE_BUILD && }
{/* THEME SELECTOR */} - + {/* IMAGE FORMAT BUTTONS */} - {/* IMAGE SCALE */} - + - + - @@ -240,9 +229,9 @@ function SettingsTab({ {/* GPU ID INPUT */} - + - + {/* CUSTOM MODEL */} {/* RESET SETTINGS */} - + - {featureFlags.SHOW_UPSCAYL_CLOUD_INFO && ( + {FEATURE_FLAGS.SHOW_UPSCAYL_CLOUD_INFO && ( <> + ); +}; + +export default SidebarToggleButton; diff --git a/renderer/components/sidebar/upscayl-logo.tsx b/renderer/components/sidebar/upscayl-logo.tsx new file mode 100644 index 0000000..35ba71a --- /dev/null +++ b/renderer/components/sidebar/upscayl-logo.tsx @@ -0,0 +1,15 @@ +import UpscaylSVGLogo from "../icons/upscayl-logo-svg"; +import useTranslation from "../hooks/use-translation"; + +const UpscaylLogo = () => { + const t = useTranslation(); + + return ( +
+ + {t("TITLE")} +
+ ); +}; + +export default UpscaylLogo; diff --git a/renderer/components/upscayl-tab/config/LeftPaneImageSteps.tsx b/renderer/components/sidebar/upscayl-tab/upscayl-steps.tsx similarity index 87% rename from renderer/components/upscayl-tab/config/LeftPaneImageSteps.tsx rename to renderer/components/sidebar/upscayl-tab/upscayl-steps.tsx index 3c68a88..016e5c1 100644 --- a/renderer/components/upscayl-tab/config/LeftPaneImageSteps.tsx +++ b/renderer/components/sidebar/upscayl-tab/upscayl-steps.tsx @@ -1,26 +1,24 @@ import { useAtom, useAtomValue } from "jotai"; -import React, { useCallback, useEffect, useState } from "react"; +import React, { useEffect, useMemo, useState } from "react"; import { Tooltip } from "react-tooltip"; import { themeChange } from "theme-change"; -import { TModelsList, modelsListAtom } from "../../../atoms/modelsListAtom"; -import useLog from "../../hooks/useLog"; +import { TModelsList, modelsListAtom } from "../../../atoms/models-list-atom"; +import useLogger from "../../hooks/use-logger"; import { - noImageProcessingAtom, savedOutputPathAtom, progressAtom, rememberOutputFolderAtom, scaleAtom, customWidthAtom, useCustomWidthAtom, -} from "../../../atoms/userSettingsAtom"; -import { featureFlags } from "@common/feature-flags"; -import getModelScale from "@common/check-model-scale"; -import COMMAND from "@common/commands"; +} from "../../../atoms/user-settings-atom"; +import { FEATURE_FLAGS } from "@common/feature-flags"; +import { ELECTRON_COMMANDS } from "@common/electron-commands"; import Select from "react-select"; import { cn } from "@/lib/utils"; import { useToast } from "@/components/ui/use-toast"; -import { ImageScaleSelect } from "@/components/settings-tab/ImageScaleSelect"; import { translationAtom } from "@/atoms/translations-atom"; +import { SelectImageScale } from "../settings-tab/select-image-scale"; interface IProps { selectImageHandler: () => Promise; @@ -42,7 +40,7 @@ interface IProps { setGpuId: React.Dispatch>; } -function LeftPaneImageSteps({ +function UpscaylSteps({ selectImageHandler, selectFolderHandler, handleModelChange, @@ -65,23 +63,19 @@ function LeftPaneImageSteps({ const modelOptions = useAtomValue(modelsListAtom); const [scale, setScale] = useAtom(scaleAtom); - const noImageProcessing = useAtomValue(noImageProcessingAtom); const [outputPath, setOutputPath] = useAtom(savedOutputPathAtom); const [progress, setProgress] = useAtom(progressAtom); const rememberOutputFolder = useAtomValue(rememberOutputFolderAtom); const [open, setOpen] = React.useState(false); - const [customWidth, setCustomWidth] = useAtom(customWidthAtom); - const [useCustomWidth, setUseCustomWidth] = useAtom(useCustomWidthAtom); + const customWidth = useAtomValue(customWidthAtom); + const useCustomWidth = useAtomValue(useCustomWidthAtom); - const [targetWidth, setTargetWidth] = useState(null); - const [targetHeight, setTargetHeight] = useState(null); - - const { logit } = useLog(); + const logit = useLogger(); const { toast } = useToast(); const t = useAtomValue(translationAtom); const outputHandler = async () => { - var path = await window.electron.invoke(COMMAND.SELECT_FOLDER); + const path = await window.electron.invoke(ELECTRON_COMMANDS.SELECT_FOLDER); if (path !== null) { logit("🗂 Setting Output Path: ", path); setOutputPath(path); @@ -136,12 +130,7 @@ function LeftPaneImageSteps({ logit("🔀 Setting model to", currentModel.value); }, [currentModel]); - useEffect(() => { - setTargetWidth(getUpscaleResolution().width); - setTargetHeight(getUpscaleResolution().height); - }, [dimensions.width, dimensions.height, doubleUpscayl, scale]); - - const getUpscaleResolution = useCallback(() => { + const upscaylResolution = useMemo(() => { const newDimensions = { width: dimensions.width, height: dimensions.height, @@ -278,7 +267,7 @@ function LeftPaneImageSteps({
)} - +
{/* STEP 3 */} @@ -288,7 +277,7 @@ function LeftPaneImageSteps({ {t("APP.OUTPUT_PATH_SELECTION.TITLE")} - {featureFlags.APP_STORE_BUILD && ( + {FEATURE_FLAGS.APP_STORE_BUILD && ( )}
- {!outputPath && featureFlags.APP_STORE_BUILD && ( + {!outputPath && FEATURE_FLAGS.APP_STORE_BUILD && (
{t("APP.OUTPUT_PATH_SELECTION.NOT_SELECTED")} @@ -307,7 +296,7 @@ function LeftPaneImageSteps({
)} - {!batchMode && !featureFlags.APP_STORE_BUILD && ( + {!batchMode && !FEATURE_FLAGS.APP_STORE_BUILD && (

{!batchMode ? t("APP.OUTPUT_PATH_SELECTION.DEFAULT_IMG_PATH") @@ -335,7 +324,7 @@ function LeftPaneImageSteps({ {t("APP.SCALE_SELECTION.TO_TITLE")} - {getUpscaleResolution().width}x{getUpscaleResolution().height} + {upscaylResolution.width}x{upscaylResolution.height}

)} @@ -366,4 +355,4 @@ function LeftPaneImageSteps({ ); } -export default LeftPaneImageSteps; +export default UpscaylSteps; diff --git a/renderer/components/UpscaylCloudModal.tsx b/renderer/components/upscayl-cloud-modal.tsx similarity index 100% rename from renderer/components/UpscaylCloudModal.tsx rename to renderer/components/upscayl-cloud-modal.tsx diff --git a/renderer/lib/valid-formats.ts b/renderer/lib/valid-formats.ts new file mode 100644 index 0000000..5457faa --- /dev/null +++ b/renderer/lib/valid-formats.ts @@ -0,0 +1 @@ +export const VALID_IMAGE_FORMATS = ["png", "jpg", "jpeg", "jfif", "webp"]; diff --git a/renderer/locales/en.json b/renderer/locales/en.json index 60bf7a8..5041fa4 100644 --- a/renderer/locales/en.json +++ b/renderer/locales/en.json @@ -12,7 +12,7 @@ "LINK_TITLE": "The Upscayl Team" }, "SETTINGS": { - "TITLE": "SETTINGS", + "TITLE": "Settings", "CHANGE_LANGUAGE": { "TITLE": "Change Language" }, "IMAGE_COMPRESSION": { "TITLE": "Image Compression", @@ -127,8 +127,8 @@ "FROM_TITLE": "Upscayl from ", "TO_TITLE": " to ", "NO_OUTPUT_FOLDER_ALERT": "Please select an output folder first", - "START_BUTTON_TITLE": "Upscayl", - "IN_PROGRESS_BUTTON_TITLE": "Upscayling⏳" + "START_BUTTON_TITLE": "Upscayl 🚀", + "IN_PROGRESS_BUTTON_TITLE": "Upscayling ⏳" }, "IMAGE_OPTIONS": { "RESET_BUTTON_TITLE": "Reset Image", diff --git a/renderer/locales/es.json b/renderer/locales/es.json index bf97879..911d6ba 100644 --- a/renderer/locales/es.json +++ b/renderer/locales/es.json @@ -12,7 +12,7 @@ "LINK_TITLE": "El equipo de Upscayl" }, "SETTINGS": { - "TITLE": "AJUSTES", + "TITLE": "Ajustes", "CHANGE_LANGUAGE": { "TITLE": "Cambiar idioma" }, "IMAGE_COMPRESSION": { "TITLE": "Compresión de imagen", @@ -127,8 +127,8 @@ "FROM_TITLE": "Aumentar desde ", "TO_TITLE": " a ", "NO_OUTPUT_FOLDER_ALERT": "Por favor, selecciona primero una carpeta de salida", - "START_BUTTON_TITLE": "Upscayl", - "IN_PROGRESS_BUTTON_TITLE": "Aumentando⏳" + "START_BUTTON_TITLE": "Upscayl 🚀", + "IN_PROGRESS_BUTTON_TITLE": "Aumentando ⏳" }, "IMAGE_OPTIONS": { "RESET_BUTTON_TITLE": "Restablecer imagen", diff --git a/renderer/locales/fr.json b/renderer/locales/fr.json index abcb8d2..eb50172 100644 --- a/renderer/locales/fr.json +++ b/renderer/locales/fr.json @@ -12,7 +12,7 @@ "LINK_TITLE": "L'équipe Upscayl" }, "SETTINGS": { - "TITLE": "PARAMÈTRES", + "TITLE": "Paramètres", "CHANGE_LANGUAGE": { "TITLE": "Changer de langue" }, "IMAGE_COMPRESSION": { "TITLE": "Compression d'image", @@ -127,8 +127,8 @@ "FROM_TITLE": "Suréchantillonner de ", "TO_TITLE": " à ", "NO_OUTPUT_FOLDER_ALERT": "Veuillez d'abord sélectionner un dossier de sortie", - "START_BUTTON_TITLE": "Suréchantillonner", - "IN_PROGRESS_BUTTON_TITLE": "Suréchantillonnage⏳" + "START_BUTTON_TITLE": "Suréchantillonner 🚀", + "IN_PROGRESS_BUTTON_TITLE": "Suréchantillonnage ⏳" }, "IMAGE_OPTIONS": { "RESET_BUTTON_TITLE": "Réinitialiser l'image", diff --git a/renderer/locales/ja.json b/renderer/locales/ja.json index f30bff0..b05d8fc 100644 --- a/renderer/locales/ja.json +++ b/renderer/locales/ja.json @@ -127,8 +127,8 @@ "FROM_TITLE": "Upscayl元 ", "TO_TITLE": " から ", "NO_OUTPUT_FOLDER_ALERT": "まず出力フォルダを選択してください", - "START_BUTTON_TITLE": "Upscayl", - "IN_PROGRESS_BUTTON_TITLE": "Upscayl中⏳" + "START_BUTTON_TITLE": "Upscayl 🚀", + "IN_PROGRESS_BUTTON_TITLE": "Upscayl中 ⏳" }, "IMAGE_OPTIONS": { "RESET_BUTTON_TITLE": "画像をリセット", diff --git a/renderer/locales/locale_template b/renderer/locales/locale_template deleted file mode 100644 index a1285bb..0000000 --- a/renderer/locales/locale_template +++ /dev/null @@ -1,215 +0,0 @@ -// !!!!!!!!!!!!!!!!!!!!DO NOT DELETE THIS FILE!!!!!!!!!!!!!! -// Copy this to a new file -// Name the file {language}.json like en-US.json, ru-RU.json, etc -// Replace the english strings present below with relevant languages -// !!!!!!!!!!!!!!!!!!!!!KEEP ANYTHING PRESENT WITHIN FLOWER BRACES {variable} - THEY ARE VARIABLES!!!!!!!!!!!!!!!!!!!!!!! -// Delete these comments starting with "//" as json format does not accept comments -{ - "TITLE": "Upscayl", - "INTRO": "Introducing Upscayl Cloud!", - "HEADER": { - "GITHUB_BUTTON_TITLE": "Star us on GitHub 😁", - "DESCRIPTION": "AI Image Upscaler" - }, - "FOOTER": { - "NEWS_TITLE": "UPSCAYL NEWS", - "COPYRIGHT": "Copyright ©", - "TITLE": "By ", - "LINK_TITLE": "The Upscayl Team" - }, - "SETTINGS": { - "TITLE": "SETTINGS", - "CHANGE_LANGUAGE": { "TITLE": "Change Language" }, - "IMAGE_COMPRESSION": { - "TITLE": "Image Compression", - "DESCRIPTION": "PNG compression is lossless, so it might not reduce the file size significantly and higher compression values might affect the performance. JPG and WebP compression is lossy." - }, - "CUSTOM_MODELS": { - "TITLE": "ADD CUSTOM MODELS", - "BUTTON_FOLDER": "Select Folder", - "DESCRIPTION": "You can add your own models easily. For more details:", - "LINK_TITLE": "Custom Models Repository" - }, - "CUSTOM_INPUT_RESOLUTION": { - "TITLE": "CUSTOM OUTPUT WIDTH", - "RESTART": "REQUIRES RESTART", - "DESCRIPTION": "Use a custom width for the output images. The height will be adjusted automatically. Enabling this will override the scale setting." - }, - "DONATE": { - "DESCRIPTION": "If you like what we do :)", - "BUTTON_TITLE": "💎 DONATE" - }, - "GPU_ID_INPUT": { - "TITLE": "GPU ID", - "DESCRIPTION": "Please read the Upscayl Documentation for more information.", - "ADDITIONAL_DESCRIPTION": "Enable performance mode on Windows for better results." - }, - "IMAGE_FORMAT": { - "TITLE": "SAVE IMAGE AS", - "PNG": "PNG", - "JPG": "JPG", - "WEBP": "WEBP" - }, - "IMAGE_SCALE": { - "TITLE": "Image Scale", - "DESCRIPTION": "Anything above 4X (except 16X Double Upscayl) only resizes the image and does not use AI upscaling.", - "WARNING": "Anything above 5X may cause performance issues on some devices!", - "ADDITIONAL_WARNING": "This may cause performance issues on some devices!" - }, - "LOG_AREA": { - "ON_COPY": "COPIED ✅", - "BUTTON_TITLE": "COPY LOGS 📋", - "NO_LOGS": "No logs to show" - }, - "OVERWRITE_TOGGLE": { - "TITLE": "OVERWRITE PREVIOUS UPSCALE", - "DESCRIPTION": "If enabled, Upscayl will process the image again instead of loading it directly." - }, - "RESET_SETTINGS": { - "BUTTON_TITLE": "RESET UPSCAYL", - "ALERT": "Upscayl has been reset. Please restart the app." - }, - "SAVE_OUTPUT_FOLDER": { - "TITLE": "SAVE OUTPUT FOLDER", - "DESCRIPTION": "If enabled, the output folder will be remembered between sessions." - }, - "THEME": { - "TITLE": "UPSCAYL THEME" - }, - "LANGUAGE": { - "TITLE": "UPSCAYL LANGUAGE" - }, - "CUSTOM_TILE_SIZE": { - "TITLE": "CUSTOM TILE SIZE", - "DESCRIPTION": "Use a custom tile size for segmenting the image. This can help process images faster by reducing the number of tiles generated." - }, - "TURN_OFF_NOTIFICATIONS": { - "TITLE": "TURN OFF NOTIFICATIONS", - "DESCRIPTION": "If enabled, Upscayl will not send any system notifications on success or failure." - }, - "SUPPORT": { - "TITLE": "Having issues?", - "DOCS_BUTTON_TITLE": "🙏 GET HELP", - "EMAIL_BUTTON_TITLE": "📧 EMAIL DEVELOPER" - } - }, - "APP": { - "TITLE": "Upscayl", - "BATCH_MODE": { - "TITLE": "Batch Upscayl", - "DESCRIPTION": "This will let you Upscayl all files in a folder at once" - }, - "FILE_SELECTION": { - "TITLE": "Step 1", - "BATCH_MODE_TYPE": "Select Folder", - "SINGLE_MODE_TYPE": "Select Image" - }, - "MODEL_SELECTION": { - "TITLE": "Step 2", - "DESCRIPTION": "Select Model" - }, - "DOUBLE_UPSCAYL": { - "TITLE": "Double Upscayl", - "DESCRIPTION": "Enable this option to run upscayl twice on an image. Note that this may cause a significant increase in processing time and possibly performance issues for scales greater than 4X." - }, - "OUTPUT_PATH_SELECTION": { - "TITLE": "Step 3", - "MAC_APP_STORE_ALERT": "Due to MacOS App Store security restrictions, Upscayl requires you to select an output folder everytime you start it.\n\nTo avoid this, you can permanently save a default output folder in the Upscayl 'Settings' tab.", - "NOT_SELECTED": "Not Selected", - "DEFAULT_IMG_PATH": "Defaults to Image's path", - "DEFAULT_FOLDER_PATH": "Defaults to Folder's path", - "BUTTON_TITLE": "Set Output Folder" - }, - "SCALE_SELECTION": { - "TITLE": "Step 4", - "FROM_TITLE": "Upscayl from ", - "TO_TITLE": " to ", - "NO_OUTPUT_FOLDER_ALERT": "Please select an output folder first", - "START_BUTTON_TITLE": "Upscayl", - "IN_PROGRESS_BUTTON_TITLE": "Upscayling⏳" - }, - "IMAGE_OPTIONS": { - "RESET_BUTTON_TITLE": "Reset Image", - "LENS_VIEW_TITLE": "Lens View", - "SLIDER_VIEW_TITLE": "Slider View", - "ZOOM_AMOUNT_TITLE": "Zoom Amount", - "LENS_SIZE_TITLE": "Lens Size" - }, - "PROGRESS_BAR": { - "BATCH_UPSCAYL_IN_PROGRESS_TITLE": "Batch Upscayl In Progress:", - "IN_PROGRESS_TITLE": "Doing the Upscayl magic...", - "STOP_BUTTON_TITLE": "STOP" - }, - "RESET_BUTTON_TITLE": "Reset", - "RIGHT_PANE_INFO": { - "SELECT_FOLDER": "Select a Folder to Upscayl", - "SELECT_IMAGE": "Select an Image to Upscayl", - "SELECT_FOLDER_DESCRIPTION": "Make sure that the folder doesn't contain anything except PNG, JPG, JPEG & WEBP images.", - "SELECT_IMAGES_DESCRIPTION": "Select or drag and drop a PNG, JPG, JPEG or WEBP image." - }, - "PROGRESS": { - "PROCESSING_TITLE": "Processing the image...", - "SCALING_CONVERTING_TITLE": "Scaling and converting image...", - "WAIT_TITLE": "Hold on...", - "SUCCESS_TITLE": "Upscayl Successful!", - "BATCH": { - "SELECTED_FOLDER_TITLE": "Selected folder:", - "DONE_TITLE": "All done!", - "OPEN_UPSCAYLED_FOLDER_TITLE": "Open Upscayled Folder" - } - }, - "SLIDER": { - "ORIGINAL_TITLE": "Original", - "UPSCAYLED_TITLE": "Upscayled" - }, - "DIALOG_BOX": { - "CLOSE": "Close" - } - }, - "ERRORS": { - "GPU_ERROR": { - "TITLE": "GPU Error", - "DESCRIPTION": "Ran into an issue with the GPU. Please read the docs for troubleshooting! ({data})" - }, - "COPY_ERROR": { - "TITLE": "Copy Error", - "DESCRIPTION": "" - }, - "READ_WRITE_ERROR": { - "TITLE": "Read/Write Error", - "DESCRIPTION": "Make sure that the path is correct and you have proper read/write permissions \n({data})" - }, - "TILE_SIZE_ERROR": { - "TITLE": "Error", - "DESCRIPTION": "The tile size is wrong. Please change the tile size in the settings or set to 0 ({data})" - }, - "EXCEPTION_ERROR": { - "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." - }, - "GENERIC_ERROR": { - "TITLE": "Error" - }, - "INVALID_IMAGE_ERROR": { - "TITLE": "Invalid Image", - "DESCRIPTION": "Please select an image with a valid extension like PNG, JPG, JPEG, JFIF or WEBP.", - "ADDITIONAL_DESCRIPTION": "Please drag and drop an image" - }, - "NO_IMAGE_ERROR": { - "TITLE": "No image selected", - "DESCRIPTION": "Please select an image to upscale" - }, - "OPEN_DOCS_TITLE": "Open Docs", - "OPEN_DOCS_BUTTON_TITLE": "Troubleshoot" - }, - "UPSCAYL_CLOUD": { - "COMING_SOON": "Coming soon!", - "CATCHY_PHRASE_1": "No more errors, hardware issues, quality compromises or long loading times!", - "CATCHY_PHRASE_2": "🌐 Upscayl anywhere, anytime, any device\n☁️ No Graphics Card or hardware required\n👩 Face Enhancement\n🦋 10+ models to choose from\n🏎 5x faster than Upscayl Desktop\n🎞 Video Upscaling\n💰 Commercial Usage\n😴 Upscayl while you sleep", - "ALREADY_REGISTERED_ALERT": "Thank you {name}! It seems that your email has already been registered :D If that's not the case, please try again.", - "ADD_SUCCESS": "Thank you for joining the waitlist! We will notify you when Upscayl Cloud is ready for you.", - "INCORRECT_FIELDS_ALERT": "Please fill in all the fields correctly.", - "JOIN_WAITLIST": "Join the waitlist", - "DONT_SHOW_AGAIN": "DON'T SHOW AGAIN" - } -} diff --git a/renderer/locales/ru.json b/renderer/locales/ru.json index 043b336..00e58ce 100644 --- a/renderer/locales/ru.json +++ b/renderer/locales/ru.json @@ -12,7 +12,7 @@ "LINK_TITLE": "Команда Upscayl" }, "SETTINGS": { - "TITLE": "НАСТРОЙКИ", + "TITLE": "Настройки", "CHANGE_LANGUAGE": { "TITLE": "Сменить язык" }, "IMAGE_COMPRESSION": { "TITLE": "Сжатие изображения", @@ -127,8 +127,8 @@ "FROM_TITLE": "Увеличить с ", "TO_TITLE": " до ", "NO_OUTPUT_FOLDER_ALERT": "Пожалуйста, сначала выберите папку вывода", - "START_BUTTON_TITLE": "Увеличить", - "IN_PROGRESS_BUTTON_TITLE": "Увеличение⏳" + "START_BUTTON_TITLE": "Увеличить 🚀", + "IN_PROGRESS_BUTTON_TITLE": "Увеличение ⏳" }, "IMAGE_OPTIONS": { "RESET_BUTTON_TITLE": "Сбросить изображение", diff --git a/renderer/locales/zh.json b/renderer/locales/zh.json index ac0cffb..4cb4d7d 100644 --- a/renderer/locales/zh.json +++ b/renderer/locales/zh.json @@ -127,8 +127,8 @@ "FROM_TITLE": "从 ", "TO_TITLE": " 升图到 ", "NO_OUTPUT_FOLDER_ALERT": "请先选择一个输出文件夹", - "START_BUTTON_TITLE": "升图!", - "IN_PROGRESS_BUTTON_TITLE": "正在升图⏳" + "START_BUTTON_TITLE": "升图!🚀", + "IN_PROGRESS_BUTTON_TITLE": "正在升图 ⏳" }, "IMAGE_OPTIONS": { "RESET_BUTTON_TITLE": "重置图片", diff --git a/renderer/pages/index.tsx b/renderer/pages/index.tsx index 8394be5..3eb9b06 100644 --- a/renderer/pages/index.tsx +++ b/renderer/pages/index.tsx @@ -1,143 +1,107 @@ "use client"; -import { useState, useEffect, useCallback, useMemo, useRef } 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 { useState, useEffect } from "react"; +import { ELECTRON_COMMANDS } from "@common/electron-commands"; +import { useAtom, useAtomValue, useSetAtom } from "jotai"; +import { modelsListAtom } from "../atoms/models-list-atom"; 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"; +} from "../atoms/user-settings-atom"; +import useLogger from "../components/hooks/use-logger"; +import { newsAtom, showNewsModalAtom } from "@/atoms/news-atom"; 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 UpscaylSVGLogo from "@/components/icons/upscayl-logo-svg"; import { translationAtom } from "@/atoms/translations-atom"; +import Sidebar from "@/components/sidebar"; +import MainContent from "@/components/main-content"; +import getDirectoryFromPath from "@common/get-directory-from-path"; +import { FEATURE_FLAGS } from "@common/feature-flags"; +import { VALID_IMAGE_FORMATS } from "@/lib/valid-formats"; +import { initCustomModels } from "@/components/hooks/use-custom-models"; const Home = () => { - const allowedFileTypes = ["png", "jpg", "jpeg", "jfif", "webp"]; - const t = useAtomValue(translationAtom); + const logit = useLogger(); + const { toast } = useToast(); + + initCustomModels(); + + const [isLoading, setIsLoading] = useState(true); - // 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 }); - const upscaledImageRef = useRef(null); - const [lensPosition, setLensPosition] = useState({ x: 0, y: 0 }); + const setOutputPath = useSetAtom(savedOutputPathAtom); + const rememberOutputFolder = useAtomValue(rememberOutputFolderAtom); + + const batchMode = useAtomValue(batchModeAtom); + const [batchFolderPath, setBatchFolderPath] = useState(""); + const [upscaledBatchFolderPath, setUpscaledBatchFolderPath] = useState(""); + + const setProgress = useSetAtom(progressAtom); + const [doubleUpscaylCounter, setDoubleUpscaylCounter] = useState(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 selectImageHandler = async () => { + resetImagePaths(); + const path = await window.electron.invoke(ELECTRON_COMMANDS.SELECT_FILE); + if (path === null) return; + logit("🖼 Selected Image Path: ", path); + setImagePath(path); + const dirname = getDirectoryFromPath(path); + logit("📁 Selected Image Directory: ", dirname); + if (!FEATURE_FLAGS.APP_STORE_BUILD) { + if (!rememberOutputFolder) { + setOutputPath(dirname); + } + } + validateImagePath(path); + }; - const sanitizedImagePath = useMemo( - () => sanitizePath(imagePath), - [imagePath], - ); - - const sanitizedUpscaledImagePath = useMemo( - () => sanitizePath(upscaledImagePath), - [upscaledImagePath], - ); - - const handleMouseMoveCompare = (e: React.MouseEvent) => { - if (upscaledImageRef.current) { - const { left, top, width, height } = - upscaledImageRef.current.getBoundingClientRect(); - const x = e.clientX - left; - const y = e.clientY - top; - setLensPosition({ - x: Math.max(0, Math.min(x - lensSize, width - lensSize * 2)), - y: Math.max(0, Math.min(y - lensSize / 2, height - lensSize)), - }); + const selectFolderHandler = async () => { + resetImagePaths(); + const path = await window.electron.invoke(ELECTRON_COMMANDS.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(""); + } } }; - // SET CONFIG VARIABLES ON FIRST RUN - useEffect(() => { - // UPSCAYL VERSION - const upscaylVersion = navigator?.userAgent?.match( - /Upscayl\/([\d\.]+\d+)/, - )[1]; - setVersion(upscaylVersion); - }, []); + const validateImagePath = (path: string) => { + if (path.length > 0) { + logit("🖼 imagePath: ", path); + const extension = path.split(".").pop().toLowerCase(); + logit("🔤 Extension: ", extension); + if (!VALID_IMAGE_FORMATS.includes(extension)) { + toast({ + title: t("ERRORS.INVALID_IMAGE_ERROR.TITLE"), + description: t("ERRORS.INVALID_IMAGE_ERROR.DESCRIPTION"), + }); + resetImagePaths(); + } + } else { + resetImagePaths(); + } + }; // ELECTRON EVENT LISTENERS useEffect(() => { @@ -203,25 +167,19 @@ const Home = () => { resetImagePaths(); } }; - // OS - window.electron.on( - COMMAND.OS, - (_, data: "linux" | "mac" | "win" | undefined) => { - if (data) { - setOs(data); - } - }, - ); // LOG - window.electron.on(COMMAND.LOG, (_, data: string) => { + window.electron.on(ELECTRON_COMMANDS.LOG, (_, data: string) => { logit(`🎒 BACKEND REPORTED: `, data); }); // SCALING AND CONVERTING - window.electron.on(COMMAND.SCALING_AND_CONVERTING, (_, data: string) => { - setProgress(t("APP.PROGRESS.PROCESSING_TITLE")); - }); + window.electron.on( + ELECTRON_COMMANDS.SCALING_AND_CONVERTING, + (_, data: string) => { + setProgress(t("APP.PROGRESS.PROCESSING_TITLE")); + }, + ); // UPSCAYL ERROR - window.electron.on(COMMAND.UPSCAYL_ERROR, (_, data: string) => { + window.electron.on(ELECTRON_COMMANDS.UPSCAYL_ERROR, (_, data: string) => { toast({ title: t("ERRORS.GENERIC_ERROR.TITLE"), description: data, @@ -229,130 +187,95 @@ const Home = () => { 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.PROGRESS.SCALING_CONVERTING_TITLE")); - } else if (data.includes("Successful")) { - setProgress(t("APP.PROGRESS.SUCCESS_TITLE")); - } - 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.PROGRESS.SUCCESS_TITLE")); - } - 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); + window.electron.on( + ELECTRON_COMMANDS.UPSCAYL_PROGRESS, + (_, data: string) => { + if (data.length > 0 && data.length < 10) { + setProgress(data); + } else if (data.includes("converting")) { + setProgress(t("APP.PROGRESS.SCALING_CONVERTING_TITLE")); + } else if (data.includes("Successful")) { + setProgress(t("APP.PROGRESS.SUCCESS_TITLE")); } - setProgress(data); - } - handleErrors(data); - logit(`🚧 DOUBLE_UPSCAYL_PROGRESS: `, data); - }); + handleErrors(data); + logit(`🚧 UPSCAYL_PROGRESS: `, data); + }, + ); + // FOLDER UPSCAYL PROGRESS + window.electron.on( + ELECTRON_COMMANDS.FOLDER_UPSCAYL_PROGRESS, + (_, data: string) => { + if (data.includes("Successful")) { + setProgress(t("APP.PROGRESS.SUCCESS_TITLE")); + } + if (data.length > 0 && data.length < 10) { + setProgress(data); + } + handleErrors(data); + logit(`🚧 FOLDER_UPSCAYL_PROGRESS: `, data); + }, + ); + // DOUBLE UPSCAYL PROGRESS + window.electron.on( + ELECTRON_COMMANDS.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) => { + window.electron.on(ELECTRON_COMMANDS.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"), + window.electron.on( + ELECTRON_COMMANDS.FOLDER_UPSCAYL_DONE, + (_, data: string) => { + setProgress(""); + setUpscaledBatchFolderPath(data); + logit(`💯 FOLDER_UPSCAYL_DONE: `, data); + }, ); - 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); - } + // DOUBLE UPSCAYL DONE + window.electron.on( + ELECTRON_COMMANDS.DOUBLE_UPSCAYL_DONE, + (_, data: string) => { + setProgress(""); + setTimeout(() => setUpscaledImagePath(data), 500); + setDoubleUpscaylCounter(0); + logit(`💯 DOUBLE_UPSCAYL_DONE: `, data); + }, + ); + // CUSTOM FOLDER LISTENER + window.electron.on( + ELECTRON_COMMANDS.CUSTOM_MODEL_FILES_LIST, + (_, data: string[]) => { + logit(`📜 CUSTOM_MODEL_FILES_LIST: `, data); + const newModelOptions = data.map((model) => { + return { + value: model, + label: model, + }; }); - } catch (error) { - console.log("Could not fetch Upscayl News"); - } - }, [news]); + // Add newModelsList to modelOptions and remove duplicates + const modelMap = new Map(); + [...modelOptions, ...newModelOptions].forEach((model) => { + modelMap.set(model.value, model); + }); + const uniqueModelOptions = Array.from(modelMap.values()); + setModelOptions(uniqueModelOptions); + }, + ); + }, []); // LOADING STATE useEffect(() => { @@ -373,555 +296,36 @@ const Home = () => { 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("ERRORS.INVALID_IMAGE_ERROR.TITLE"), - description: t("ERRORS.INVALID_IMAGE_ERROR.DESCRIPTION"), - }); - 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("ERRORS.INVALID_IMAGE_ERROR.TITLE"), - description: t("ERRORS.INVALID_IMAGE_ERROR.ADDITIONAL_DESCRIPTION"), - }); - 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("ERRORS.INVALID_IMAGE_ERROR.TITLE"), - description: t("ERRORS.INVALID_IMAGE_ERROR.ADDITIONAL_DESCRIPTION"), - }); - } 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("ERRORS.INVALID_IMAGE_ERROR.TITLE"), - description: t("ERRORS.INVALID_IMAGE_ERROR.ADDITIONAL_DESCRIPTION"), - }); - } 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.PROGRESS.WAIT_TITLE")); - // 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("ERRORS.NO_IMAGE_ERROR.TITLE"), - description: t("ERRORS.NO_IMAGE_ERROR.DESCRIPTION"), - }); - 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("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.PROGRESS.BATCH.SELECTED_FOLDER_TITLE")} - {" "} - {batchFolderPath} -

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

- {t("APP.PROGRESS.BATCH.DONE_TITLE")} -

- -
- )} - - {!batchMode && - viewType === "lens" && - upscaledImagePath && - imagePath && ( -
- {/* UPSCALED IMAGE */} - Upscaled - {/* LENS */} -
-
-
- Original -
-
- Upscaled -
-
-
- Original - Upscayl -
-
-
- )} - {/* COMPARISON SLIDER */} - {!batchMode && - viewType === "slider" && - imagePath.length > 0 && - upscaledImagePath.length > 0 && ( - <> - -

- {t("APP.SLIDER.ORIGINAL_TITLE")} -

- - {t("APP.SLIDER.ORIGINAL_TITLE")} - - } - itemTwo={ - <> -

- {t("APP.SLIDER.UPSCAYLED_TITLE")} -

- {t("APP.SLIDER.UPSCAYLED_TITLE")} - - } - className="group h-screen" - /> - - )} -
+ +
); }; diff --git a/renderer/renderer.d.ts b/renderer/renderer.d.ts index 10f9c44..427de64 100644 --- a/renderer/renderer.d.ts +++ b/renderer/renderer.d.ts @@ -2,6 +2,7 @@ import { IpcRenderer } from "electron"; export interface IElectronAPI { on: (command, func?) => IpcRenderer; + off: (command, func?) => IpcRenderer; send: (command, func?: T) => IpcRenderer; invoke: (command, func?) => any; platform: "mac" | "win" | "linux"; diff --git a/tsconfig.json b/tsconfig.json index 61c23ef..b5e2cd7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,8 @@ "skipLibCheck": true, "paths": { "@electron/*": ["./electron/*"], - "@/*": ["./renderer/*"] + "@/*": ["./renderer/*"], + "@common/*": ["./common/*"] } }, "include": ["./electron/**/*", "./common/**/*", "./renderer/**/*"],