mirror of
https://github.com/upscayl/upscayl.git
synced 2024-11-23 23:21:05 +01:00
Refactor Renderer Code (#987)
* Initial refactor * Remove unused imports * Update code * Refactor and Update Code - Change file names to kebab-caase - Add new useTranslation Hook - Change useLog hook name to useLogger - Update translation hook to provide autocomplete * Update import and component name * Rename files and components * Update locales * Update electron commands * Update var * Change Lowercase * Replace filter with map * Add props * Update flag check * Add validate paths * Update formats * Update import * Update function * Update function and translation * Update handlePaste
This commit is contained in:
parent
bf62c684c2
commit
95843ded88
@ -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 };
|
@ -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,
|
||||
};
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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!");
|
||||
}
|
||||
};
|
||||
|
@ -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]);
|
||||
}
|
||||
|
@ -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]);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -11,11 +11,40 @@ import { atomWithStorage } from "jotai/utils";
|
||||
type Translations = typeof en;
|
||||
type Locales = "en" | "ru" | "ja" | "zh" | "es" | "fr";
|
||||
|
||||
const translations: Record<Locales, Translations> = {
|
||||
en,
|
||||
ru,
|
||||
ja,
|
||||
zh,
|
||||
es,
|
||||
fr,
|
||||
};
|
||||
|
||||
// Create a type for nested key paths
|
||||
type NestedKeyOf<Object> = Object extends object
|
||||
? {
|
||||
[Key in keyof Object]: Key extends string | number
|
||||
? Key | `${Key}.${NestedKeyOf<Object[Key]>}`
|
||||
: 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<Translations>,
|
||||
): 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<Locales>("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<Locales, Translations> = {
|
||||
en,
|
||||
ru,
|
||||
ja,
|
||||
zh,
|
||||
es,
|
||||
fr,
|
||||
};
|
||||
|
||||
return (key: string, params: Record<string, string> = {}): string => {
|
||||
return (
|
||||
key: NestedKeyOf<Translations>,
|
||||
params: Record<string, string> = {},
|
||||
): string => {
|
||||
const template = getNestedTranslation(translations[locale], key);
|
||||
|
||||
// Replace placeholders with parameters, e.g., {name} => John
|
||||
|
@ -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";
|
||||
|
@ -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")}
|
||||
>
|
||||
<div className="flex items-center gap-3 px-5 py-5">
|
||||
<Logo className="inline-block h-14 w-14" />
|
||||
<UpscaylSVGLogo className="inline-block h-14 w-14" />
|
||||
<div className="flex flex-col justify-center">
|
||||
<h1 className="text-3xl font-bold">
|
||||
{t("TITLE")}{" "}
|
||||
<span className="text-xs">
|
||||
{version} {featureFlags.APP_STORE_BUILD && "Mac"}
|
||||
{version} {FEATURE_FLAGS.APP_STORE_BUILD && "Mac"}
|
||||
</span>
|
||||
</h1>
|
||||
<p className="">{t("HEADER.DESCRIPTION")}</p>
|
||||
|
@ -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<React.SetStateAction<boolean>>;
|
||||
news: GrayMatterFile<string>;
|
||||
}) => {
|
||||
const t = useAtomValue(translationAtom);
|
||||
|
||||
return (
|
||||
<dialog className={`modal ${show && "modal-open"}`}>
|
||||
<div className="modal-box flex flex-col items-center gap-4 text-center">
|
||||
<button
|
||||
className="btn btn-circle absolute right-4 top-2"
|
||||
onClick={() => setShow(false)}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
stroke="none"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-width="1.5"
|
||||
d="m8.464 15.535l7.072-7.07m-7.072 0l7.072 7.07"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div>
|
||||
{news && (
|
||||
<Markdown remarkPlugins={[remarkGfm]} className="prose">
|
||||
{news.content}
|
||||
</Markdown>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="dialog" className="modal-backdrop">
|
||||
<button onClick={() => setShow(false)}>
|
||||
{t("APP.DIALOG_BOX.CLOSE")}
|
||||
</button>
|
||||
</form>
|
||||
</dialog>
|
||||
);
|
||||
};
|
17
renderer/components/hooks/use-custom-models.ts
Normal file
17
renderer/components/hooks/use-custom-models.ts
Normal file
@ -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);
|
||||
}
|
||||
}, []);
|
||||
};
|
17
renderer/components/hooks/use-electron.ts
Normal file
17
renderer/components/hooks/use-electron.ts
Normal file
@ -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);
|
||||
};
|
||||
}, []);
|
||||
};
|
@ -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;
|
9
renderer/components/hooks/use-translation.ts
Normal file
9
renderer/components/hooks/use-translation.ts
Normal file
@ -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;
|
16
renderer/components/hooks/use-upscayl-version.ts
Normal file
16
renderer/components/hooks/use-upscayl-version.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
const useUpscaylVersion = () => {
|
||||
const [version, setVersion] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const upscaylVersion = navigator?.userAgent?.match(
|
||||
/Upscayl\/([\d\.]+\d+)/,
|
||||
)?.[1];
|
||||
setVersion(upscaylVersion);
|
||||
}, []);
|
||||
|
||||
return version;
|
||||
};
|
||||
|
||||
export default useUpscaylVersion;
|
@ -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"
|
||||
>
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<g transform="translate(2 1)" stroke="currentColor" stroke-width="1.5">
|
||||
<circle
|
||||
@ -18,7 +15,8 @@ function Spinner() {
|
||||
cy="11.462"
|
||||
r="5"
|
||||
fill-opacity="1"
|
||||
fill="currentColor">
|
||||
fill="currentColor"
|
||||
>
|
||||
<animate
|
||||
attributeName="fill-opacity"
|
||||
begin="0s"
|
||||
@ -33,7 +31,8 @@ function Spinner() {
|
||||
cy="27.063"
|
||||
r="5"
|
||||
fill-opacity="0"
|
||||
fill="currentColor">
|
||||
fill="currentColor"
|
||||
>
|
||||
<animate
|
||||
attributeName="fill-opacity"
|
||||
begin="0s"
|
||||
@ -48,7 +47,8 @@ function Spinner() {
|
||||
cy="42.663"
|
||||
r="5"
|
||||
fill-opacity="0"
|
||||
fill="currentColor">
|
||||
fill="currentColor"
|
||||
>
|
||||
<animate
|
||||
attributeName="fill-opacity"
|
||||
begin="0s"
|
||||
@ -63,7 +63,8 @@ function Spinner() {
|
||||
cy="49.125"
|
||||
r="5"
|
||||
fill-opacity="0"
|
||||
fill="currentColor">
|
||||
fill="currentColor"
|
||||
>
|
||||
<animate
|
||||
attributeName="fill-opacity"
|
||||
begin="0s"
|
||||
@ -78,7 +79,8 @@ function Spinner() {
|
||||
cy="42.663"
|
||||
r="5"
|
||||
fill-opacity="0"
|
||||
fill="currentColor">
|
||||
fill="currentColor"
|
||||
>
|
||||
<animate
|
||||
attributeName="fill-opacity"
|
||||
begin="0s"
|
||||
@ -93,7 +95,8 @@ function Spinner() {
|
||||
cy="27.063"
|
||||
r="5"
|
||||
fill-opacity="0"
|
||||
fill="currentColor">
|
||||
fill="currentColor"
|
||||
>
|
||||
<animate
|
||||
attributeName="fill-opacity"
|
||||
begin="0s"
|
||||
@ -108,7 +111,8 @@ function Spinner() {
|
||||
cy="11.462"
|
||||
r="5"
|
||||
fill-opacity="0"
|
||||
fill="currentColor">
|
||||
fill="currentColor"
|
||||
>
|
||||
<animate
|
||||
attributeName="fill-opacity"
|
||||
begin="0s"
|
@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
|
||||
const Logo = ({ ...rest }) => {
|
||||
const UpscaylSVGLogo = ({ ...rest }) => {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 256 256"
|
||||
@ -166,4 +166,4 @@ const Logo = ({ ...rest }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Logo;
|
||||
export default UpscaylSVGLogo;
|
@ -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;
|
26
renderer/components/main-content/image-viewer.tsx
Normal file
26
renderer/components/main-content/image-viewer.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import { sanitizePath } from "@common/sanitize-path";
|
||||
|
||||
const ImageViewer = ({
|
||||
imagePath,
|
||||
setDimensions,
|
||||
}: {
|
||||
imagePath: string;
|
||||
setDimensions: (dimensions: { width: number; height: number }) => void;
|
||||
}) => {
|
||||
return (
|
||||
<img
|
||||
src={"file:///" + sanitizePath(imagePath)}
|
||||
onLoad={(e: React.SyntheticEvent<HTMLImageElement>) => {
|
||||
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;
|
292
renderer/components/main-content/index.tsx
Normal file
292
renderer/components/main-content/index.tsx
Normal file
@ -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<React.SetStateAction<string>>;
|
||||
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<HTMLDivElement>) => {
|
||||
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 (
|
||||
<div
|
||||
className="relative flex h-screen w-full flex-col items-center justify-center"
|
||||
onDrop={handleDrop}
|
||||
onDragOver={handleDragOver}
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDoubleClick={batchMode ? selectFolderHandler : selectImageHandler}
|
||||
onPaste={handlePaste}
|
||||
>
|
||||
<MacTitlebarDragRegion />
|
||||
|
||||
{progress.length > 0 &&
|
||||
upscaledImagePath.length === 0 &&
|
||||
upscaledBatchFolderPath.length === 0 && (
|
||||
<ProgressBar
|
||||
batchMode={batchMode}
|
||||
progress={progress}
|
||||
doubleUpscaylCounter={doubleUpscaylCounter}
|
||||
resetImagePaths={resetImagePaths}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* DEFAULT PANE INFO */}
|
||||
{showInformationCard && (
|
||||
<InstructionsCard version={version} batchMode={batchMode} />
|
||||
)}
|
||||
|
||||
<ImageViewSettings
|
||||
zoomAmount={zoomAmount}
|
||||
setZoomAmount={setZoomAmount}
|
||||
resetImagePaths={resetImagePaths}
|
||||
/>
|
||||
|
||||
{/* SHOW SELECTED IMAGE */}
|
||||
{!batchMode && upscaledImagePath.length === 0 && imagePath.length > 0 && (
|
||||
<ImageViewer imagePath={imagePath} setDimensions={setDimensions} />
|
||||
)}
|
||||
|
||||
{/* BATCH UPSCALE SHOW SELECTED FOLDER */}
|
||||
{batchMode &&
|
||||
upscaledBatchFolderPath.length === 0 &&
|
||||
batchFolderPath.length > 0 && (
|
||||
<p className="select-none text-base-content">
|
||||
<span className="font-bold">
|
||||
{t("APP.PROGRESS.BATCH.SELECTED_FOLDER_TITLE")}
|
||||
</span>{" "}
|
||||
{batchFolderPath}
|
||||
</p>
|
||||
)}
|
||||
{/* BATCH UPSCALE DONE INFO */}
|
||||
|
||||
{batchMode && upscaledBatchFolderPath.length > 0 && (
|
||||
<div className="z-50 flex flex-col items-center">
|
||||
<p className="select-none py-4 font-bold text-base-content">
|
||||
{t("APP.PROGRESS.BATCH.DONE_TITLE")}
|
||||
</p>
|
||||
<button
|
||||
className="bg-gradient-blue btn btn-primary rounded-btn p-3 font-medium text-white/90 transition-colors"
|
||||
onClick={openFolderHandler}
|
||||
>
|
||||
{t("APP.PROGRESS.BATCH.OPEN_UPSCAYLED_FOLDER_TITLE")}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!batchMode && viewType === "lens" && upscaledImagePath && imagePath && (
|
||||
<LensViewer
|
||||
zoomAmount={zoomAmount}
|
||||
lensSize={lensSize}
|
||||
sanitizedImagePath={sanitizedImagePath}
|
||||
sanitizedUpscaledImagePath={sanitizedUpscaledImagePath}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* COMPARISON SLIDER */}
|
||||
{!batchMode &&
|
||||
viewType === "slider" &&
|
||||
imagePath.length > 0 &&
|
||||
upscaledImagePath.length > 0 && (
|
||||
<SliderView
|
||||
sanitizedImagePath={sanitizedImagePath}
|
||||
sanitizedUpscaledImagePath={sanitizedUpscaledImagePath}
|
||||
zoomAmount={zoomAmount}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MainContent;
|
@ -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;
|
104
renderer/components/main-content/lens-view.tsx
Normal file
104
renderer/components/main-content/lens-view.tsx
Normal file
@ -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;
|
||||
}) => (
|
||||
<div className="h-full w-full overflow-hidden">
|
||||
<img
|
||||
src={src}
|
||||
alt={alt}
|
||||
className="h-full w-full"
|
||||
style={{
|
||||
objectFit: "contain",
|
||||
objectPosition: `${-lensPosition.x}px ${-lensPosition.y}px`,
|
||||
transform: `scale(${zoomAmount / 100})`,
|
||||
transformOrigin: "top left",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
const LensViewer = ({
|
||||
zoomAmount,
|
||||
lensSize,
|
||||
sanitizedImagePath,
|
||||
sanitizedUpscaledImagePath,
|
||||
}: {
|
||||
zoomAmount: string;
|
||||
lensSize: number;
|
||||
sanitizedImagePath: string;
|
||||
sanitizedUpscaledImagePath: string;
|
||||
}) => {
|
||||
const upscaledImageRef = useRef<HTMLImageElement>(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 (
|
||||
<div
|
||||
className="group relative h-full w-full overflow-hidden"
|
||||
onMouseMove={handleMouseMoveCompare}
|
||||
>
|
||||
{/* UPSCALED IMAGE */}
|
||||
<img
|
||||
className="h-full w-full object-contain"
|
||||
src={"file:///" + sanitizedUpscaledImagePath}
|
||||
alt="Upscaled"
|
||||
ref={upscaledImageRef}
|
||||
/>
|
||||
{/* LENS */}
|
||||
<div
|
||||
className="pointer-events-none absolute opacity-0 transition-opacity before:absolute before:left-1/2 before:h-full before:w-[2px] before:bg-white group-hover:opacity-100"
|
||||
style={{
|
||||
left: `${lensPosition.x}px`,
|
||||
top: `${lensPosition.y}px`,
|
||||
width: lensSize * 2,
|
||||
height: lensSize,
|
||||
border: "2px solid white",
|
||||
boxShadow: "0 0 0 9999px rgba(0, 0, 0, 0.5)",
|
||||
}}
|
||||
>
|
||||
<div className="flex h-full w-full">
|
||||
<LensImage
|
||||
src={"file:///" + sanitizedImagePath}
|
||||
alt="Original"
|
||||
lensPosition={lensPosition}
|
||||
zoomAmount={parseInt(zoomAmount)}
|
||||
/>
|
||||
<LensImage
|
||||
src={"file:///" + sanitizedUpscaledImagePath}
|
||||
alt="Upscaled"
|
||||
lensPosition={lensPosition}
|
||||
zoomAmount={parseInt(zoomAmount)}
|
||||
/>
|
||||
</div>
|
||||
<div className="absolute bottom-0 left-0 flex w-full items-center justify-around bg-black bg-opacity-50 p-1 px-2 text-center text-xs text-white backdrop-blur-sm">
|
||||
<span>Original</span>
|
||||
<span>Upscayl</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LensViewer;
|
@ -0,0 +1,7 @@
|
||||
const MacTitlebarDragRegion = () => {
|
||||
return window.electron.platform === "mac" ? (
|
||||
<div className="mac-titlebar absolute top-0 h-8 w-full"></div>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export default MacTitlebarDragRegion;
|
@ -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 (
|
||||
<div className="absolute z-50 flex h-full w-full flex-col items-center justify-center bg-base-300/50 backdrop-blur-lg">
|
||||
<div className="flex flex-col items-center gap-2 rounded-btn bg-base-100/50 p-4 backdrop-blur-lg">
|
||||
<Logo className="spinner h-12 w-12" />
|
||||
<UpscaylSVGLogo className="spinner h-12 w-12" />
|
||||
|
||||
<p className="rounded-full px-2 pb-2 font-bold">
|
||||
{batchMode &&
|
||||
`${t("APP.PROGRESS_BAR.BATCH_UPSCAYL_IN_PROGRESS_TITLE")} ${batchProgress}`}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
{progress !== "Hold on..." ? (
|
||||
<p className="text-sm font-bold">
|
||||
@ -60,10 +70,12 @@ function ProgressBar({
|
||||
) : (
|
||||
<p className="text-sm font-bold">{progress}</p>
|
||||
)}
|
||||
|
||||
<p className="animate-pulse rounded-full px-2 pb-3 text-xs font-medium text-neutral-content/50">
|
||||
{t("APP.PROGRESS_BAR.IN_PROGRESS_TITLE")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button onClick={stopHandler} className="btn btn-outline">
|
||||
{t("APP.PROGRESS_BAR.STOP_BUTTON_TITLE")}
|
||||
</button>
|
73
renderer/components/main-content/slider-view.tsx
Normal file
73
renderer/components/main-content/slider-view.tsx
Normal file
@ -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 (
|
||||
<ReactCompareSlider
|
||||
itemOne={
|
||||
<>
|
||||
<p className="absolute bottom-1 left-1 rounded-md bg-black p-1 text-sm font-medium text-white opacity-30">
|
||||
{t("APP.SLIDER.ORIGINAL_TITLE")}
|
||||
</p>
|
||||
|
||||
<img
|
||||
/* USE REGEX TO GET THE FILENAME AND ENCODE IT INTO PROPER FORM IN ORDER TO AVOID ERRORS DUE TO SPECIAL CHARACTERS */
|
||||
src={"file:///" + sanitizedImagePath}
|
||||
alt={t("APP.SLIDER.ORIGINAL_TITLE")}
|
||||
onMouseMove={handleMouseMove}
|
||||
style={{
|
||||
objectFit: "contain",
|
||||
backgroundPosition: "0% 0%",
|
||||
transformOrigin: backgroundPosition,
|
||||
}}
|
||||
className={`h-full w-full bg-gradient-to-br from-base-300 to-base-100 transition-transform group-hover:scale-[${zoomAmount}%]`}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
itemTwo={
|
||||
<>
|
||||
<p className="absolute bottom-1 right-1 rounded-md bg-black p-1 text-sm font-medium text-white opacity-30">
|
||||
{t("APP.SLIDER.UPSCAYLED_TITLE")}
|
||||
</p>
|
||||
<img
|
||||
/* USE REGEX TO GET THE FILENAME AND ENCODE IT INTO PROPER FORM IN ORDER TO AVOID ERRORS DUE TO SPECIAL CHARACTERS */
|
||||
src={"file:///" + sanitizedUpscaledImagePath}
|
||||
alt={t("APP.SLIDER.UPSCAYLED_TITLE")}
|
||||
style={{
|
||||
objectFit: "contain",
|
||||
backgroundPosition: "0% 0%",
|
||||
transformOrigin: backgroundPosition,
|
||||
}}
|
||||
onMouseMove={handleMouseMove}
|
||||
className={`h-full w-full bg-gradient-to-br from-base-300 to-base-100 transition-transform group-hover:scale-[${
|
||||
zoomAmount || "100%"
|
||||
}%]`}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
className="group h-screen"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default SliderView;
|
110
renderer/components/news-modal.tsx
Normal file
110
renderer/components/news-modal.tsx
Normal file
@ -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 (
|
||||
<dialog className={`modal ${showNewsModal && "modal-open"}`}>
|
||||
<div className="modal-box flex flex-col items-center gap-4 text-center">
|
||||
<button
|
||||
className="btn btn-circle absolute right-4 top-2"
|
||||
onClick={() => {
|
||||
setShowNewsModal(false);
|
||||
setNews((prev) => ({ ...prev, seen: true }));
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
stroke="none"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-width="1.5"
|
||||
d="m8.464 15.535l7.072-7.07m-7.072 0l7.072 7.07"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div>
|
||||
{news && (
|
||||
<Markdown remarkPlugins={[remarkGfm]} className="prose">
|
||||
{news.content}
|
||||
</Markdown>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="dialog" className="modal-backdrop">
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowNewsModal(false);
|
||||
setNews((prev) => ({ ...prev, seen: true }));
|
||||
}}
|
||||
>
|
||||
{t("APP.DIALOG_BOX.CLOSE")}
|
||||
</button>
|
||||
</form>
|
||||
</dialog>
|
||||
);
|
||||
};
|
241
renderer/components/sidebar/index.tsx
Normal file
241
renderer/components/sidebar/index.tsx
Normal file
@ -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<React.SetStateAction<string>>;
|
||||
batchFolderPath: string;
|
||||
setUpscaledBatchFolderPath: React.Dispatch<React.SetStateAction<string>>;
|
||||
dimensions: {
|
||||
width: number | null;
|
||||
height: number | null;
|
||||
};
|
||||
imagePath: string;
|
||||
selectImageHandler: () => Promise<void>;
|
||||
selectFolderHandler: () => Promise<void>;
|
||||
}) => {
|
||||
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<DoubleUpscaylPayload>(
|
||||
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<BatchUpscaylPayload>(
|
||||
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<ImageUpscaylPayload>(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 && <UpscaylLogo />}
|
||||
|
||||
<SidebarToggleButton
|
||||
showSidebar={showSidebar}
|
||||
setShowSidebar={setShowSidebar}
|
||||
/>
|
||||
|
||||
<div
|
||||
className={`relative flex h-screen min-w-[350px] max-w-[350px] flex-col bg-base-100 ${showSidebar ? "" : "hidden"}`}
|
||||
>
|
||||
<button
|
||||
className="absolute -right-0 top-1/2 z-[999] -translate-y-1/2 translate-x-1/2 rounded-full bg-base-100 p-4"
|
||||
onClick={() => setShowSidebar((prev) => !prev)}
|
||||
>
|
||||
<ChevronLeftIcon />
|
||||
</button>
|
||||
|
||||
{window.electron.platform === "mac" && (
|
||||
<div className="mac-titlebar pt-8"></div>
|
||||
)}
|
||||
|
||||
<Header version={version} />
|
||||
|
||||
<NewsModal />
|
||||
|
||||
<Tabs selectedTab={selectedTab} setSelectedTab={setSelectedTab} />
|
||||
|
||||
{selectedTab === 0 && (
|
||||
<UpscaylSteps
|
||||
selectImageHandler={selectImageHandler}
|
||||
selectFolderHandler={selectFolderHandler}
|
||||
handleModelChange={handleModelChange}
|
||||
upscaylHandler={upscaylHandler}
|
||||
batchMode={batchMode}
|
||||
setBatchMode={setBatchMode}
|
||||
imagePath={imagePath}
|
||||
doubleUpscayl={doubleUpscayl}
|
||||
setDoubleUpscayl={setDoubleUpscayl}
|
||||
dimensions={dimensions}
|
||||
setGpuId={setGpuId}
|
||||
model={model}
|
||||
setModel={setModel}
|
||||
setSaveImageAs={setSaveImageAs}
|
||||
/>
|
||||
)}
|
||||
|
||||
{selectedTab === 1 && (
|
||||
<SettingsTab
|
||||
batchMode={batchMode}
|
||||
setModel={setModel}
|
||||
compression={compression}
|
||||
setCompression={setCompression}
|
||||
gpuId={gpuId}
|
||||
setGpuId={setGpuId}
|
||||
saveImageAs={saveImageAs}
|
||||
setSaveImageAs={setSaveImageAs}
|
||||
logData={logData}
|
||||
show={showCloudModal}
|
||||
setShow={setShowCloudModal}
|
||||
setDontShowCloudModal={setDontShowCloudModal}
|
||||
/>
|
||||
)}
|
||||
<Footer />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Sidebar;
|
@ -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<React.SetStateAction<string>>;
|
||||
logData: string[];
|
||||
os: "linux" | "mac" | "win" | undefined;
|
||||
show: boolean;
|
||||
setShow: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
setDontShowCloudModal: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
@ -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")}
|
||||
</a>
|
||||
{featureFlags.APP_STORE_BUILD && (
|
||||
{FEATURE_FLAGS.APP_STORE_BUILD && (
|
||||
<a
|
||||
className="btn btn-primary"
|
||||
href={`mailto:upscayl@gmail.com?subject=Upscayl%20Issue%3A%20%3CIssue%20name%20here%3E&body=Device%20Name%3A%20%3CYOUR%20DEVICE%20MODEL%3E%0AOperating%20System%3A%20%3CYOUR%20OPERATING%20SYSTEM%20VERSION%3E%0AUpscayl%20Version%3A%20${upscaylVersion}%0A%0AHi%2C%20I'm%20having%20an%20issue%20with%20Upscayl.%20%3CDESCRIBE%20ISSUE%20HERE%3E`}
|
||||
@ -203,7 +192,7 @@ function SettingsTab({
|
||||
{t("SETTINGS.SUPPORT.EMAIL_BUTTON_TITLE")}
|
||||
</a>
|
||||
)}
|
||||
{!featureFlags.APP_STORE_BUILD && <DonateButton />}
|
||||
{!FEATURE_FLAGS.APP_STORE_BUILD && <DonateButton />}
|
||||
</div>
|
||||
|
||||
<LogArea
|
||||
@ -213,23 +202,23 @@ function SettingsTab({
|
||||
/>
|
||||
|
||||
{/* THEME SELECTOR */}
|
||||
<ThemeSelect />
|
||||
<SelectTheme />
|
||||
|
||||
<LanguageSwitcher />
|
||||
|
||||
{/* IMAGE FORMAT BUTTONS */}
|
||||
<ImageFormatSelect
|
||||
<SelectImageFormat
|
||||
batchMode={batchMode}
|
||||
saveImageAs={saveImageAs}
|
||||
setExportType={setExportType}
|
||||
/>
|
||||
|
||||
{/* IMAGE SCALE */}
|
||||
<ImageScaleSelect scale={scale} setScale={setScale} />
|
||||
<SelectImageScale scale={scale} setScale={setScale} />
|
||||
|
||||
<CustomResolutionInput />
|
||||
<InputCustomResolution />
|
||||
|
||||
<CompressionInput
|
||||
<InputCompression
|
||||
compression={compression}
|
||||
handleCompressionChange={handleCompressionChange}
|
||||
/>
|
||||
@ -240,9 +229,9 @@ function SettingsTab({
|
||||
<TurnOffNotificationsToggle />
|
||||
|
||||
{/* GPU ID INPUT */}
|
||||
<GpuIdInput gpuId={gpuId} handleGpuIdChange={handleGpuIdChange} />
|
||||
<InputGpuId gpuId={gpuId} handleGpuIdChange={handleGpuIdChange} />
|
||||
|
||||
<TileSizeInput />
|
||||
<InputTileSize />
|
||||
|
||||
{/* CUSTOM MODEL */}
|
||||
<CustomModelsFolderSelect
|
||||
@ -251,9 +240,9 @@ function SettingsTab({
|
||||
/>
|
||||
|
||||
{/* RESET SETTINGS */}
|
||||
<ResetSettings />
|
||||
<ResetSettingsButton />
|
||||
|
||||
{featureFlags.SHOW_UPSCAYL_CLOUD_INFO && (
|
||||
{FEATURE_FLAGS.SHOW_UPSCAYL_CLOUD_INFO && (
|
||||
<>
|
||||
<button
|
||||
className="mx-5 mb-5 animate-pulse rounded-btn bg-success p-1 text-sm text-slate-50 shadow-lg shadow-success/40"
|
@ -6,7 +6,7 @@ type CompressionInputProps = {
|
||||
handleCompressionChange: (arg: any) => void;
|
||||
};
|
||||
|
||||
export function CompressionInput({
|
||||
export function InputCompression({
|
||||
compression,
|
||||
handleCompressionChange,
|
||||
}: CompressionInputProps) {
|
@ -1,9 +1,12 @@
|
||||
import { customWidthAtom, useCustomWidthAtom } from "@/atoms/userSettingsAtom";
|
||||
import {
|
||||
customWidthAtom,
|
||||
useCustomWidthAtom,
|
||||
} from "@/atoms/user-settings-atom";
|
||||
import { useAtom, useAtomValue } from "jotai";
|
||||
import React from "react";
|
||||
import { translationAtom } from "@/atoms/translations-atom";
|
||||
|
||||
export function CustomResolutionInput() {
|
||||
export function InputCustomResolution() {
|
||||
const [useCustomWidth, setUseCustomWidth] = useAtom(useCustomWidthAtom);
|
||||
const [customWidth, setCustomWidth] = useAtom(customWidthAtom);
|
||||
const t = useAtomValue(translationAtom);
|
@ -7,7 +7,7 @@ type GpuIdInputProps = {
|
||||
handleGpuIdChange: (arg: string) => void;
|
||||
};
|
||||
|
||||
export function GpuIdInput({ gpuId, handleGpuIdChange }) {
|
||||
export function InputGpuId({ gpuId, handleGpuIdChange }) {
|
||||
const t = useAtomValue(translationAtom);
|
||||
|
||||
return (
|
@ -1,9 +1,9 @@
|
||||
import { translationAtom } from "@/atoms/translations-atom";
|
||||
import { tileSizeAtom } from "@/atoms/userSettingsAtom";
|
||||
import { tileSizeAtom } from "@/atoms/user-settings-atom";
|
||||
import { useAtom, useAtomValue } from "jotai";
|
||||
import React from "react";
|
||||
|
||||
export function TileSizeInput() {
|
||||
export function InputTileSize() {
|
||||
const [tileSize, setTileSize] = useAtom(tileSizeAtom);
|
||||
const t = useAtomValue(translationAtom);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { translationAtom } from "@/atoms/translations-atom";
|
||||
import { overwriteAtom } from "@/atoms/userSettingsAtom";
|
||||
import { overwriteAtom } from "@/atoms/user-settings-atom";
|
||||
import { useAtom, useAtomValue } from "jotai";
|
||||
import React, { useEffect } from "react";
|
||||
|
@ -2,7 +2,7 @@ import { translationAtom } from "@/atoms/translations-atom";
|
||||
import { useAtomValue } from "jotai";
|
||||
import React from "react";
|
||||
|
||||
export function ResetSettings() {
|
||||
export function ResetSettingsButton() {
|
||||
const t = useAtomValue(translationAtom);
|
||||
return (
|
||||
<div className="flex flex-col items-start gap-2">
|
@ -2,7 +2,7 @@ import { translationAtom } from "@/atoms/translations-atom";
|
||||
import {
|
||||
savedOutputPathAtom,
|
||||
rememberOutputFolderAtom,
|
||||
} from "@/atoms/userSettingsAtom";
|
||||
} from "@/atoms/user-settings-atom";
|
||||
import { useAtom, useAtomValue } from "jotai";
|
||||
|
||||
export function SaveOutputFolderToggle() {
|
@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import commands from "../../../common/commands";
|
||||
import { ELECTRON_COMMANDS } from "@common/electron-commands";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { translationAtom } from "@/atoms/translations-atom";
|
||||
|
||||
@ -32,12 +32,15 @@ export function CustomModelsFolderSelect({
|
||||
className="btn btn-primary"
|
||||
onClick={async () => {
|
||||
const customModelPath = await window.electron.invoke(
|
||||
commands.SELECT_CUSTOM_MODEL_FOLDER,
|
||||
ELECTRON_COMMANDS.SELECT_CUSTOM_MODEL_FOLDER,
|
||||
);
|
||||
|
||||
if (customModelPath !== null) {
|
||||
setCustomModelsPath(customModelPath);
|
||||
window.electron.send(commands.GET_MODELS_LIST, customModelPath);
|
||||
window.electron.send(
|
||||
ELECTRON_COMMANDS.GET_MODELS_LIST,
|
||||
customModelPath,
|
||||
);
|
||||
} else {
|
||||
setCustomModelsPath("");
|
||||
}
|
@ -7,7 +7,7 @@ type ImageFormatSelectProps = {
|
||||
setExportType: (arg: string) => void;
|
||||
};
|
||||
|
||||
export function ImageFormatSelect({
|
||||
export function SelectImageFormat({
|
||||
batchMode,
|
||||
saveImageAs,
|
||||
setExportType,
|
@ -1,5 +1,5 @@
|
||||
import { translationAtom } from "@/atoms/translations-atom";
|
||||
import { useCustomWidthAtom } from "@/atoms/userSettingsAtom";
|
||||
import { useCustomWidthAtom } from "@/atoms/user-settings-atom";
|
||||
import { useAtomValue } from "jotai";
|
||||
|
||||
type ImageScaleSelectProps = {
|
||||
@ -8,7 +8,7 @@ type ImageScaleSelectProps = {
|
||||
hideInfo?: boolean;
|
||||
};
|
||||
|
||||
export function ImageScaleSelect({
|
||||
export function SelectImageScale({
|
||||
scale,
|
||||
setScale,
|
||||
hideInfo,
|
@ -1,7 +1,8 @@
|
||||
import { translationAtom } from "@/atoms/translations-atom";
|
||||
import { useAtomValue } from "jotai";
|
||||
import React from "react";
|
||||
export function ThemeSelect() {
|
||||
|
||||
export function SelectTheme() {
|
||||
const availableThemes = [
|
||||
{ label: "upscayl", value: "upscayl" },
|
||||
{ label: "light", value: "light" },
|
@ -1,5 +1,5 @@
|
||||
import { translationAtom } from "@/atoms/translations-atom";
|
||||
import { turnOffNotificationsAtom } from "@/atoms/userSettingsAtom";
|
||||
import { turnOffNotificationsAtom } from "@/atoms/user-settings-atom";
|
||||
import { useAtom, useAtomValue } from "jotai";
|
||||
|
||||
const TurnOffNotificationsToggle = () => {
|
25
renderer/components/sidebar/sidebar-button.tsx
Normal file
25
renderer/components/sidebar/sidebar-button.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ChevronRightIcon } from "lucide-react";
|
||||
import React from "react";
|
||||
|
||||
const SidebarToggleButton = ({
|
||||
showSidebar,
|
||||
setShowSidebar,
|
||||
}: {
|
||||
showSidebar: boolean;
|
||||
setShowSidebar: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}) => {
|
||||
return (
|
||||
<button
|
||||
className={cn(
|
||||
"fixed left-0 top-1/2 z-[999] -translate-y-1/2 rounded-r-full bg-base-100 p-4 ",
|
||||
showSidebar ? "hidden" : "",
|
||||
)}
|
||||
onClick={() => setShowSidebar((prev) => !prev)}
|
||||
>
|
||||
<ChevronRightIcon />
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default SidebarToggleButton;
|
15
renderer/components/sidebar/upscayl-logo.tsx
Normal file
15
renderer/components/sidebar/upscayl-logo.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import UpscaylSVGLogo from "../icons/upscayl-logo-svg";
|
||||
import useTranslation from "../hooks/use-translation";
|
||||
|
||||
const UpscaylLogo = () => {
|
||||
const t = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="fixed right-2 top-2 z-50 flex items-center justify-center gap-2 rounded-[7px] bg-base-300 px-2 py-1 font-medium text-base-content ">
|
||||
<UpscaylSVGLogo className="w-5" />
|
||||
{t("TITLE")}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UpscaylLogo;
|
@ -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<void>;
|
||||
@ -42,7 +40,7 @@ interface IProps {
|
||||
setGpuId: React.Dispatch<React.SetStateAction<string>>;
|
||||
}
|
||||
|
||||
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<number>(null);
|
||||
const [targetHeight, setTargetHeight] = useState<number>(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({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ImageScaleSelect scale={scale} setScale={setScale} hideInfo />
|
||||
<SelectImageScale scale={scale} setScale={setScale} hideInfo />
|
||||
</div>
|
||||
|
||||
{/* STEP 3 */}
|
||||
@ -288,7 +277,7 @@ function LeftPaneImageSteps({
|
||||
<span className="leading-none">
|
||||
{t("APP.OUTPUT_PATH_SELECTION.TITLE")}
|
||||
</span>
|
||||
{featureFlags.APP_STORE_BUILD && (
|
||||
{FEATURE_FLAGS.APP_STORE_BUILD && (
|
||||
<button
|
||||
className="badge badge-outline badge-sm cursor-pointer"
|
||||
onClick={() =>
|
||||
@ -299,7 +288,7 @@ function LeftPaneImageSteps({
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{!outputPath && featureFlags.APP_STORE_BUILD && (
|
||||
{!outputPath && FEATURE_FLAGS.APP_STORE_BUILD && (
|
||||
<div className="text-xs">
|
||||
<span className="rounded-btn bg-base-200 px-2 font-medium uppercase text-base-content/50">
|
||||
{t("APP.OUTPUT_PATH_SELECTION.NOT_SELECTED")}
|
||||
@ -307,7 +296,7 @@ function LeftPaneImageSteps({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{!batchMode && !featureFlags.APP_STORE_BUILD && (
|
||||
{!batchMode && !FEATURE_FLAGS.APP_STORE_BUILD && (
|
||||
<p className="mb-2 text-sm">
|
||||
{!batchMode
|
||||
? t("APP.OUTPUT_PATH_SELECTION.DEFAULT_IMG_PATH")
|
||||
@ -335,7 +324,7 @@ function LeftPaneImageSteps({
|
||||
</span>
|
||||
{t("APP.SCALE_SELECTION.TO_TITLE")}
|
||||
<span className="font-bold">
|
||||
{getUpscaleResolution().width}x{getUpscaleResolution().height}
|
||||
{upscaylResolution.width}x{upscaylResolution.height}
|
||||
</span>
|
||||
</p>
|
||||
)}
|
||||
@ -366,4 +355,4 @@ function LeftPaneImageSteps({
|
||||
);
|
||||
}
|
||||
|
||||
export default LeftPaneImageSteps;
|
||||
export default UpscaylSteps;
|
1
renderer/lib/valid-formats.ts
Normal file
1
renderer/lib/valid-formats.ts
Normal file
@ -0,0 +1 @@
|
||||
export const VALID_IMAGE_FORMATS = ["png", "jpg", "jpeg", "jfif", "webp"];
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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": "画像をリセット",
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
@ -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": "Сбросить изображение",
|
||||
|
@ -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": "重置图片",
|
||||
|
File diff suppressed because it is too large
Load Diff
1
renderer/renderer.d.ts
vendored
1
renderer/renderer.d.ts
vendored
@ -2,6 +2,7 @@ import { IpcRenderer } from "electron";
|
||||
|
||||
export interface IElectronAPI {
|
||||
on: (command, func?) => IpcRenderer;
|
||||
off: (command, func?) => IpcRenderer;
|
||||
send: <T>(command, func?: T) => IpcRenderer;
|
||||
invoke: (command, func?) => any;
|
||||
platform: "mac" | "win" | "linux";
|
||||
|
@ -11,7 +11,8 @@
|
||||
"skipLibCheck": true,
|
||||
"paths": {
|
||||
"@electron/*": ["./electron/*"],
|
||||
"@/*": ["./renderer/*"]
|
||||
"@/*": ["./renderer/*"],
|
||||
"@common/*": ["./common/*"]
|
||||
}
|
||||
},
|
||||
"include": ["./electron/**/*", "./common/**/*", "./renderer/**/*"],
|
||||
|
Loading…
Reference in New Issue
Block a user