mirror of
https://github.com/upscayl/upscayl.git
synced 2024-11-27 17:00:52 +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_FILE: "Select a File",
|
||||||
SELECT_FOLDER: "Select a Folder",
|
SELECT_FOLDER: "Select a Folder",
|
||||||
UPSCAYL: "Upscale the Image",
|
UPSCAYL: "Upscale the Image",
|
||||||
@ -25,6 +25,6 @@ const COMMAND = {
|
|||||||
OS: "Get OS",
|
OS: "Get OS",
|
||||||
SCALING_AND_CONVERTING: "Adding some finishing touches",
|
SCALING_AND_CONVERTING: "Adding some finishing touches",
|
||||||
UPSCAYL_ERROR: "Upscaling Error",
|
UPSCAYL_ERROR: "Upscaling Error",
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
export default COMMAND;
|
export { ELECTRON_COMMANDS };
|
@ -3,7 +3,7 @@ type FeatureFlags = {
|
|||||||
SHOW_UPSCAYL_CLOUD_INFO: boolean;
|
SHOW_UPSCAYL_CLOUD_INFO: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const featureFlags: FeatureFlags = {
|
export const FEATURE_FLAGS: FeatureFlags = {
|
||||||
APP_STORE_BUILD: false,
|
APP_STORE_BUILD: false,
|
||||||
SHOW_UPSCAYL_CLOUD_INFO: false,
|
SHOW_UPSCAYL_CLOUD_INFO: false,
|
||||||
};
|
};
|
||||||
|
@ -11,7 +11,7 @@ import { spawnUpscayl } from "../utils/spawn-upscayl";
|
|||||||
import { getBatchArguments } from "../utils/get-arguments";
|
import { getBatchArguments } from "../utils/get-arguments";
|
||||||
import slash from "../utils/slash";
|
import slash from "../utils/slash";
|
||||||
import { modelsPath } from "../utils/get-resource-paths";
|
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 { BatchUpscaylPayload } from "../../common/types/types";
|
||||||
import showNotification from "../utils/show-notification";
|
import showNotification from "../utils/show-notification";
|
||||||
import { DEFAULT_MODELS } from "../../common/models-list";
|
import { DEFAULT_MODELS } from "../../common/models-list";
|
||||||
@ -72,28 +72,28 @@ const batchUpscayl = async (event, payload: BatchUpscaylPayload) => {
|
|||||||
if (!mainWindow) return;
|
if (!mainWindow) return;
|
||||||
data = data.toString();
|
data = data.toString();
|
||||||
mainWindow.webContents.send(
|
mainWindow.webContents.send(
|
||||||
COMMAND.FOLDER_UPSCAYL_PROGRESS,
|
ELECTRON_COMMANDS.FOLDER_UPSCAYL_PROGRESS,
|
||||||
data.toString(),
|
data.toString(),
|
||||||
);
|
);
|
||||||
if ((data as string).includes("Error")) {
|
if ((data as string).includes("Error")) {
|
||||||
logit("❌ ", data);
|
logit("❌ ", data);
|
||||||
encounteredError = true;
|
encounteredError = true;
|
||||||
} else if (data.includes("Resizing")) {
|
} else if (data.includes("Resizing")) {
|
||||||
mainWindow.webContents.send(COMMAND.SCALING_AND_CONVERTING);
|
mainWindow.webContents.send(ELECTRON_COMMANDS.SCALING_AND_CONVERTING);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const onError = (data: any) => {
|
const onError = (data: any) => {
|
||||||
if (!mainWindow) return;
|
if (!mainWindow) return;
|
||||||
mainWindow.setProgressBar(-1);
|
mainWindow.setProgressBar(-1);
|
||||||
mainWindow.webContents.send(
|
mainWindow.webContents.send(
|
||||||
COMMAND.FOLDER_UPSCAYL_PROGRESS,
|
ELECTRON_COMMANDS.FOLDER_UPSCAYL_PROGRESS,
|
||||||
data.toString(),
|
data.toString(),
|
||||||
);
|
);
|
||||||
failed = true;
|
failed = true;
|
||||||
upscayl.kill();
|
upscayl.kill();
|
||||||
mainWindow &&
|
mainWindow &&
|
||||||
mainWindow.webContents.send(
|
mainWindow.webContents.send(
|
||||||
COMMAND.UPSCAYL_ERROR,
|
ELECTRON_COMMANDS.UPSCAYL_ERROR,
|
||||||
`Error upscaling images! ${data}`,
|
`Error upscaling images! ${data}`,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@ -104,7 +104,7 @@ const batchUpscayl = async (event, payload: BatchUpscaylPayload) => {
|
|||||||
logit("💯 Done upscaling");
|
logit("💯 Done upscaling");
|
||||||
upscayl.kill();
|
upscayl.kill();
|
||||||
mainWindow.webContents.send(
|
mainWindow.webContents.send(
|
||||||
COMMAND.FOLDER_UPSCAYL_DONE,
|
ELECTRON_COMMANDS.FOLDER_UPSCAYL_DONE,
|
||||||
outputFolderPath,
|
outputFolderPath,
|
||||||
);
|
);
|
||||||
if (!encounteredError) {
|
if (!encounteredError) {
|
||||||
|
@ -5,11 +5,11 @@ import {
|
|||||||
} from "../utils/config-variables";
|
} from "../utils/config-variables";
|
||||||
import logit from "../utils/logit";
|
import logit from "../utils/logit";
|
||||||
import slash from "../utils/slash";
|
import slash from "../utils/slash";
|
||||||
import COMMAND from "../../common/commands";
|
import { ELECTRON_COMMANDS } from "@common/electron-commands";
|
||||||
import getModels from "../utils/get-models";
|
import getModels from "../utils/get-models";
|
||||||
import { getMainWindow } from "../main-window";
|
import { getMainWindow } from "../main-window";
|
||||||
import settings from "electron-settings";
|
import settings from "electron-settings";
|
||||||
import { featureFlags } from "../../common/feature-flags";
|
import { FEATURE_FLAGS } from "../../common/feature-flags";
|
||||||
|
|
||||||
const customModelsSelect = async (event, message) => {
|
const customModelsSelect = async (event, message) => {
|
||||||
const mainWindow = getMainWindow();
|
const mainWindow = getMainWindow();
|
||||||
@ -27,7 +27,7 @@ const customModelsSelect = async (event, message) => {
|
|||||||
message: "Select Custom Models Folder that is named 'models'",
|
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);
|
console.log("🚨 Setting Bookmark: ", bookmarks);
|
||||||
await settings.set("custom-models-bookmarks", bookmarks[0]);
|
await settings.set("custom-models-bookmarks", bookmarks[0]);
|
||||||
}
|
}
|
||||||
@ -55,7 +55,10 @@ const customModelsSelect = async (event, message) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const models = await getModels(savedCustomModelsPath);
|
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);
|
logit("📁 Custom Folder Path: ", savedCustomModelsPath);
|
||||||
return savedCustomModelsPath;
|
return savedCustomModelsPath;
|
||||||
|
@ -14,7 +14,7 @@ import {
|
|||||||
} from "../utils/get-arguments";
|
} from "../utils/get-arguments";
|
||||||
import { modelsPath } from "../utils/get-resource-paths";
|
import { modelsPath } from "../utils/get-resource-paths";
|
||||||
import logit from "../utils/logit";
|
import logit from "../utils/logit";
|
||||||
import COMMAND from "../../common/commands";
|
import { ELECTRON_COMMANDS } from "@common/electron-commands";
|
||||||
import { DoubleUpscaylPayload } from "../../common/types/types";
|
import { DoubleUpscaylPayload } from "../../common/types/types";
|
||||||
import { ImageFormat } from "../types/types";
|
import { ImageFormat } from "../types/types";
|
||||||
import showNotification from "../utils/show-notification";
|
import showNotification from "../utils/show-notification";
|
||||||
@ -87,12 +87,15 @@ const doubleUpscayl = async (event, payload: DoubleUpscaylPayload) => {
|
|||||||
if (!mainWindow) return;
|
if (!mainWindow) return;
|
||||||
data.toString();
|
data.toString();
|
||||||
// SEND UPSCAYL PROGRESS TO RENDERER
|
// 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
|
// SET FAILED TO TRUE
|
||||||
failed2 = true;
|
failed2 = true;
|
||||||
mainWindow &&
|
mainWindow &&
|
||||||
mainWindow.webContents.send(
|
mainWindow.webContents.send(
|
||||||
COMMAND.UPSCAYL_ERROR,
|
ELECTRON_COMMANDS.UPSCAYL_ERROR,
|
||||||
"Error upscaling image. Error: " + data,
|
"Error upscaling image. Error: " + data,
|
||||||
);
|
);
|
||||||
showNotification("Upscayl Failure", "Failed to upscale image!");
|
showNotification("Upscayl Failure", "Failed to upscale image!");
|
||||||
@ -105,13 +108,16 @@ const doubleUpscayl = async (event, payload: DoubleUpscaylPayload) => {
|
|||||||
// CONVERT DATA TO STRING
|
// CONVERT DATA TO STRING
|
||||||
data = data.toString();
|
data = data.toString();
|
||||||
// SEND UPSCAYL PROGRESS TO RENDERER
|
// 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 PROGRESS HAS ERROR, UPSCAYL FAILED
|
||||||
if (data.includes("Error")) {
|
if (data.includes("Error")) {
|
||||||
upscayl2.kill();
|
upscayl2.kill();
|
||||||
failed2 = true;
|
failed2 = true;
|
||||||
} else if (data.includes("Resizing")) {
|
} 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");
|
logit("💯 Done upscaling");
|
||||||
|
|
||||||
mainWindow.setProgressBar(-1);
|
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!");
|
showNotification("Upscayled", "Image upscayled successfully!");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -132,12 +141,15 @@ const doubleUpscayl = async (event, payload: DoubleUpscaylPayload) => {
|
|||||||
mainWindow.setProgressBar(-1);
|
mainWindow.setProgressBar(-1);
|
||||||
data.toString();
|
data.toString();
|
||||||
// SEND UPSCAYL PROGRESS TO RENDERER
|
// 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
|
// SET FAILED TO TRUE
|
||||||
failed = true;
|
failed = true;
|
||||||
mainWindow &&
|
mainWindow &&
|
||||||
mainWindow.webContents.send(
|
mainWindow.webContents.send(
|
||||||
COMMAND.UPSCAYL_ERROR,
|
ELECTRON_COMMANDS.UPSCAYL_ERROR,
|
||||||
"Error upscaling image. Error: " + data,
|
"Error upscaling image. Error: " + data,
|
||||||
);
|
);
|
||||||
showNotification("Upscayl Failure", "Failed to upscale image!");
|
showNotification("Upscayl Failure", "Failed to upscale image!");
|
||||||
@ -150,13 +162,16 @@ const doubleUpscayl = async (event, payload: DoubleUpscaylPayload) => {
|
|||||||
// CONVERT DATA TO STRING
|
// CONVERT DATA TO STRING
|
||||||
data = data.toString();
|
data = data.toString();
|
||||||
// SEND UPSCAYL PROGRESS TO RENDERER
|
// 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 PROGRESS HAS ERROR, UPSCAYL FAILED
|
||||||
if (data.includes("Error") || data.includes("failed")) {
|
if (data.includes("Error") || data.includes("failed")) {
|
||||||
upscayl.kill();
|
upscayl.kill();
|
||||||
failed = true;
|
failed = true;
|
||||||
} else if (data.includes("Resizing")) {
|
} 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 { getMainWindow } from "../main-window";
|
||||||
import {
|
import {
|
||||||
savedCustomModelsPath,
|
savedCustomModelsPath,
|
||||||
@ -17,7 +17,10 @@ const getModelsList = async (event, payload) => {
|
|||||||
logit("📁 Custom Models Folder Path: ", savedCustomModelsPath);
|
logit("📁 Custom Models Folder Path: ", savedCustomModelsPath);
|
||||||
const models = await getModels(payload);
|
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 fs from "fs";
|
||||||
import { modelsPath } from "../utils/get-resource-paths";
|
import { modelsPath } from "../utils/get-resource-paths";
|
||||||
import COMMAND from "../../common/commands";
|
import { ELECTRON_COMMANDS } from "@common/electron-commands";
|
||||||
import {
|
import {
|
||||||
savedCustomModelsPath,
|
savedCustomModelsPath,
|
||||||
setChildProcesses,
|
setChildProcesses,
|
||||||
@ -60,14 +60,17 @@ const imageUpscayl = async (event, payload: ImageUpscaylPayload) => {
|
|||||||
// Check if windows can write the new filename to the file system
|
// Check if windows can write the new filename to the file system
|
||||||
if (outFile.length >= 255) {
|
if (outFile.length >= 255) {
|
||||||
logit("Filename too long for Windows.");
|
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
|
// UPSCALE
|
||||||
if (fs.existsSync(outFile) && !overwrite) {
|
if (fs.existsSync(outFile) && !overwrite) {
|
||||||
// If already upscayled, just output that file
|
// If already upscayled, just output that file
|
||||||
logit("✅ Already upscayled at: ", outFile);
|
logit("✅ Already upscayled at: ", outFile);
|
||||||
mainWindow.webContents.send(COMMAND.UPSCAYL_DONE, outFile);
|
mainWindow.webContents.send(ELECTRON_COMMANDS.UPSCAYL_DONE, outFile);
|
||||||
} else {
|
} else {
|
||||||
logit(
|
logit(
|
||||||
"✅ Upscayl Variables: ",
|
"✅ Upscayl Variables: ",
|
||||||
@ -115,18 +118,24 @@ const imageUpscayl = async (event, payload: ImageUpscaylPayload) => {
|
|||||||
logit(data.toString());
|
logit(data.toString());
|
||||||
mainWindow.setProgressBar(parseFloat(data.slice(0, data.length)) / 100);
|
mainWindow.setProgressBar(parseFloat(data.slice(0, data.length)) / 100);
|
||||||
data = data.toString();
|
data = data.toString();
|
||||||
mainWindow.webContents.send(COMMAND.UPSCAYL_PROGRESS, data.toString());
|
mainWindow.webContents.send(
|
||||||
|
ELECTRON_COMMANDS.UPSCAYL_PROGRESS,
|
||||||
|
data.toString(),
|
||||||
|
);
|
||||||
if (data.includes("Error")) {
|
if (data.includes("Error")) {
|
||||||
upscayl.kill();
|
upscayl.kill();
|
||||||
failed = true;
|
failed = true;
|
||||||
} else if (data.includes("Resizing")) {
|
} else if (data.includes("Resizing")) {
|
||||||
mainWindow.webContents.send(COMMAND.SCALING_AND_CONVERTING);
|
mainWindow.webContents.send(ELECTRON_COMMANDS.SCALING_AND_CONVERTING);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const onError = (data) => {
|
const onError = (data) => {
|
||||||
if (!mainWindow) return;
|
if (!mainWindow) return;
|
||||||
mainWindow.setProgressBar(-1);
|
mainWindow.setProgressBar(-1);
|
||||||
mainWindow.webContents.send(COMMAND.UPSCAYL_ERROR, data.toString());
|
mainWindow.webContents.send(
|
||||||
|
ELECTRON_COMMANDS.UPSCAYL_ERROR,
|
||||||
|
data.toString(),
|
||||||
|
);
|
||||||
failed = true;
|
failed = true;
|
||||||
upscayl.kill();
|
upscayl.kill();
|
||||||
return;
|
return;
|
||||||
@ -137,7 +146,7 @@ const imageUpscayl = async (event, payload: ImageUpscaylPayload) => {
|
|||||||
// Free up memory
|
// Free up memory
|
||||||
upscayl.kill();
|
upscayl.kill();
|
||||||
mainWindow.setProgressBar(-1);
|
mainWindow.setProgressBar(-1);
|
||||||
mainWindow.webContents.send(COMMAND.UPSCAYL_DONE, outFile);
|
mainWindow.webContents.send(ELECTRON_COMMANDS.UPSCAYL_DONE, outFile);
|
||||||
showNotification("Upscayl", "Image upscayled successfully!");
|
showNotification("Upscayl", "Image upscayled successfully!");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -3,7 +3,7 @@ import { getMainWindow } from "../main-window";
|
|||||||
import { savedImagePath, setSavedImagePath } from "../utils/config-variables";
|
import { savedImagePath, setSavedImagePath } from "../utils/config-variables";
|
||||||
import logit from "../utils/logit";
|
import logit from "../utils/logit";
|
||||||
import settings from "electron-settings";
|
import settings from "electron-settings";
|
||||||
import { featureFlags } from "../../common/feature-flags";
|
import { FEATURE_FLAGS } from "../../common/feature-flags";
|
||||||
|
|
||||||
const selectFile = async () => {
|
const selectFile = async () => {
|
||||||
const mainWindow = getMainWindow();
|
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);
|
console.log("🚨 Setting Bookmark: ", bookmarks);
|
||||||
settings.set("file-bookmarks", bookmarks[0]);
|
settings.set("file-bookmarks", bookmarks[0]);
|
||||||
}
|
}
|
||||||
|
@ -5,12 +5,12 @@ import {
|
|||||||
} from "../utils/config-variables";
|
} from "../utils/config-variables";
|
||||||
import logit from "../utils/logit";
|
import logit from "../utils/logit";
|
||||||
import settings from "electron-settings";
|
import settings from "electron-settings";
|
||||||
import { featureFlags } from "../../common/feature-flags";
|
import { FEATURE_FLAGS } from "../../common/feature-flags";
|
||||||
|
|
||||||
const selectFolder = async (event, message) => {
|
const selectFolder = async (event, message) => {
|
||||||
let closeAccess;
|
let closeAccess;
|
||||||
const folderBookmarks = await settings.get("folder-bookmarks");
|
const folderBookmarks = await settings.get("folder-bookmarks");
|
||||||
if (featureFlags.APP_STORE_BUILD && folderBookmarks) {
|
if (FEATURE_FLAGS.APP_STORE_BUILD && folderBookmarks) {
|
||||||
logit("🚨 Folder Bookmarks: ", folderBookmarks);
|
logit("🚨 Folder Bookmarks: ", folderBookmarks);
|
||||||
try {
|
try {
|
||||||
closeAccess = app.startAccessingSecurityScopedResource(
|
closeAccess = app.startAccessingSecurityScopedResource(
|
||||||
@ -31,7 +31,7 @@ const selectFolder = async (event, message) => {
|
|||||||
securityScopedBookmarks: true,
|
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);
|
console.log("🚨 Setting folder Bookmark: ", bookmarks);
|
||||||
await settings.set("folder-bookmarks", bookmarks[0]);
|
await settings.set("folder-bookmarks", bookmarks[0]);
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import prepareNext from "electron-next";
|
|||||||
import { autoUpdater } from "electron-updater";
|
import { autoUpdater } from "electron-updater";
|
||||||
import log from "electron-log";
|
import log from "electron-log";
|
||||||
import { app, ipcMain, protocol } from "electron";
|
import { app, ipcMain, protocol } from "electron";
|
||||||
import COMMAND from "../common/commands";
|
import { ELECTRON_COMMANDS } from "../common/electron-commands";
|
||||||
import logit from "./utils/logit";
|
import logit from "./utils/logit";
|
||||||
import openFolder from "./commands/open-folder";
|
import openFolder from "./commands/open-folder";
|
||||||
import stop from "./commands/stop";
|
import stop from "./commands/stop";
|
||||||
@ -17,7 +17,7 @@ import { execPath, modelsPath } from "./utils/get-resource-paths";
|
|||||||
import batchUpscayl from "./commands/batch-upscayl";
|
import batchUpscayl from "./commands/batch-upscayl";
|
||||||
import doubleUpscayl from "./commands/double-upscayl";
|
import doubleUpscayl from "./commands/double-upscayl";
|
||||||
import autoUpdate from "./commands/auto-update";
|
import autoUpdate from "./commands/auto-update";
|
||||||
import { featureFlags } from "../common/feature-flags";
|
import { FEATURE_FLAGS } from "../common/feature-flags";
|
||||||
import settings from "electron-settings";
|
import settings from "electron-settings";
|
||||||
|
|
||||||
// INITIALIZATION
|
// INITIALIZATION
|
||||||
@ -43,14 +43,14 @@ app.on("ready", async () => {
|
|||||||
log.info(
|
log.info(
|
||||||
"🆙 Upscayl version:",
|
"🆙 Upscayl version:",
|
||||||
app.getVersion(),
|
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("🚀 UPSCAYL EXEC PATH: ", execPath);
|
||||||
log.info("🚀 MODELS PATH: ", modelsPath);
|
log.info("🚀 MODELS PATH: ", modelsPath);
|
||||||
|
|
||||||
let closeAccess;
|
let closeAccess;
|
||||||
const folderBookmarks = await settings.get("folder-bookmarks");
|
const folderBookmarks = await settings.get("folder-bookmarks");
|
||||||
if (featureFlags.APP_STORE_BUILD && folderBookmarks) {
|
if (FEATURE_FLAGS.APP_STORE_BUILD && folderBookmarks) {
|
||||||
logit("🚨 Folder Bookmarks: ", folderBookmarks);
|
logit("🚨 Folder Bookmarks: ", folderBookmarks);
|
||||||
try {
|
try {
|
||||||
closeAccess = app.startAccessingSecurityScopedResource(
|
closeAccess = app.startAccessingSecurityScopedResource(
|
||||||
@ -68,29 +68,32 @@ app.on("window-all-closed", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// ! ENABLE THIS FOR MACOS APP STORE BUILD
|
// ! ENABLE THIS FOR MACOS APP STORE BUILD
|
||||||
if (featureFlags.APP_STORE_BUILD) {
|
if (FEATURE_FLAGS.APP_STORE_BUILD) {
|
||||||
logit("🚀 APP STORE BUILD ENABLED");
|
logit("🚀 APP STORE BUILD ENABLED");
|
||||||
app.commandLine.appendSwitch("in-process-gpu");
|
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);
|
autoUpdater.on("update-downloaded", autoUpdate);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { BrowserWindow, shell } from "electron";
|
import { BrowserWindow, shell } from "electron";
|
||||||
import { getPlatform } from "./utils/get-device-specs";
|
import { getPlatform } from "./utils/get-device-specs";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import COMMAND from "../common/commands";
|
import { ELECTRON_COMMANDS } from "../common/electron-commands";
|
||||||
import { fetchLocalStorage } from "./utils/config-variables";
|
import { fetchLocalStorage } from "./utils/config-variables";
|
||||||
import electronIsDev from "electron-is-dev";
|
import electronIsDev from "electron-is-dev";
|
||||||
import { format } from "url";
|
import { format } from "url";
|
||||||
@ -49,7 +49,7 @@ const createMainWindow = () => {
|
|||||||
|
|
||||||
fetchLocalStorage();
|
fetchLocalStorage();
|
||||||
|
|
||||||
mainWindow.webContents.send(COMMAND.OS, getPlatform());
|
mainWindow.webContents.send(ELECTRON_COMMANDS.OS, getPlatform());
|
||||||
|
|
||||||
mainWindow.setMenuBarVisibility(false);
|
mainWindow.setMenuBarVisibility(false);
|
||||||
};
|
};
|
||||||
|
@ -2,7 +2,7 @@ import fs from "fs";
|
|||||||
import logit from "./logit";
|
import logit from "./logit";
|
||||||
import { MessageBoxOptions, app, dialog } from "electron";
|
import { MessageBoxOptions, app, dialog } from "electron";
|
||||||
import settings from "electron-settings";
|
import settings from "electron-settings";
|
||||||
import { featureFlags } from "../../common/feature-flags";
|
import { FEATURE_FLAGS } from "../../common/feature-flags";
|
||||||
|
|
||||||
const getModels = async (folderPath: string | undefined) => {
|
const getModels = async (folderPath: string | undefined) => {
|
||||||
let models: string[] = [];
|
let models: string[] = [];
|
||||||
@ -11,14 +11,14 @@ const getModels = async (folderPath: string | undefined) => {
|
|||||||
// SECURITY SCOPED BOOKMARKS
|
// SECURITY SCOPED BOOKMARKS
|
||||||
let closeAccess;
|
let closeAccess;
|
||||||
const customModelsBookmarks = await settings.get("custom-models-bookmarks");
|
const customModelsBookmarks = await settings.get("custom-models-bookmarks");
|
||||||
if (featureFlags.APP_STORE_BUILD && customModelsBookmarks) {
|
if (FEATURE_FLAGS.APP_STORE_BUILD && customModelsBookmarks) {
|
||||||
console.log(
|
console.log(
|
||||||
"🚀 => file: get-models.ts:18 => customModelsBookmarks:",
|
"🚀 => file: get-models.ts:18 => customModelsBookmarks:",
|
||||||
customModelsBookmarks
|
customModelsBookmarks,
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
closeAccess = app.startAccessingSecurityScopedResource(
|
closeAccess = app.startAccessingSecurityScopedResource(
|
||||||
customModelsBookmarks as string
|
customModelsBookmarks as string,
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logit("📁 Custom Models Bookmarks Error: ", error);
|
logit("📁 Custom Models Bookmarks Error: ", error);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import log from "electron-log";
|
import log from "electron-log";
|
||||||
import COMMAND from "../../common/commands";
|
import { ELECTRON_COMMANDS } from "@common/electron-commands";
|
||||||
import { getMainWindow } from "../main-window";
|
import { getMainWindow } from "../main-window";
|
||||||
|
|
||||||
const logit = (...args: any) => {
|
const logit = (...args: any) => {
|
||||||
@ -9,7 +9,7 @@ const logit = (...args: any) => {
|
|||||||
if (process.env.NODE_ENV === "development") {
|
if (process.env.NODE_ENV === "development") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mainWindow.webContents.send(COMMAND.LOG, args.join(" "));
|
mainWindow.webContents.send(ELECTRON_COMMANDS.LOG, args.join(" "));
|
||||||
};
|
};
|
||||||
|
|
||||||
export default logit;
|
export default logit;
|
||||||
|
@ -11,19 +11,6 @@ import { atomWithStorage } from "jotai/utils";
|
|||||||
type Translations = typeof en;
|
type Translations = typeof en;
|
||||||
type Locales = "en" | "ru" | "ja" | "zh" | "es" | "fr";
|
type Locales = "en" | "ru" | "ja" | "zh" | "es" | "fr";
|
||||||
|
|
||||||
// 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
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Atom to store the current locale
|
|
||||||
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> = {
|
const translations: Record<Locales, Translations> = {
|
||||||
en,
|
en,
|
||||||
ru,
|
ru,
|
||||||
@ -33,7 +20,44 @@ export const translationAtom = atom((get) => {
|
|||||||
fr,
|
fr,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (key: string, params: Record<string, string> = {}): string => {
|
// 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: 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
|
||||||
|
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);
|
||||||
|
|
||||||
|
return (
|
||||||
|
key: NestedKeyOf<Translations>,
|
||||||
|
params: Record<string, string> = {},
|
||||||
|
): string => {
|
||||||
const template = getNestedTranslation(translations[locale], key);
|
const template = getNestedTranslation(translations[locale], key);
|
||||||
|
|
||||||
// Replace placeholders with parameters, e.g., {name} => John
|
// 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 { translationAtom } from "@/atoms/translations-atom";
|
||||||
import { useAtomValue, useSetAtom } from "jotai";
|
import { useAtomValue, useSetAtom } from "jotai";
|
||||||
import React from "react";
|
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 React from "react";
|
||||||
import Logo from "./icons/Logo";
|
import UpscaylSVGLogo from "@/components/icons/upscayl-logo-svg";
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import { translationAtom } from "@/atoms/translations-atom";
|
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")}
|
data-tooltip-content={t("HEADER.GITHUB_BUTTON_TITLE")}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3 px-5 py-5">
|
<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">
|
<div className="flex flex-col justify-center">
|
||||||
<h1 className="text-3xl font-bold">
|
<h1 className="text-3xl font-bold">
|
||||||
{t("TITLE")}{" "}
|
{t("TITLE")}{" "}
|
||||||
<span className="text-xs">
|
<span className="text-xs">
|
||||||
{version} {featureFlags.APP_STORE_BUILD && "Mac"}
|
{version} {FEATURE_FLAGS.APP_STORE_BUILD && "Mac"}
|
||||||
</span>
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
<p className="">{t("HEADER.DESCRIPTION")}</p>
|
<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 log from "electron-log/renderer";
|
||||||
import { useSetAtom } from "jotai";
|
import { useSetAtom } from "jotai";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
const useLog = () => {
|
const useLogger = () => {
|
||||||
const setLogData = useSetAtom(logAtom);
|
const setLogData = useSetAtom(logAtom);
|
||||||
|
|
||||||
const logit = (...args: any) => {
|
const logit = (...args: any) => {
|
||||||
@ -13,9 +13,7 @@ const useLog = () => {
|
|||||||
setLogData((prevLogData) => [...prevLogData, data]);
|
setLogData((prevLogData) => [...prevLogData, data]);
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return logit;
|
||||||
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() {
|
function Spinner() {
|
||||||
return (
|
return (
|
||||||
// By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL
|
// By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL
|
||||||
@ -10,7 +6,8 @@ function Spinner() {
|
|||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
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 fill="none" fill-rule="evenodd">
|
||||||
<g transform="translate(2 1)" stroke="currentColor" stroke-width="1.5">
|
<g transform="translate(2 1)" stroke="currentColor" stroke-width="1.5">
|
||||||
<circle
|
<circle
|
||||||
@ -18,7 +15,8 @@ function Spinner() {
|
|||||||
cy="11.462"
|
cy="11.462"
|
||||||
r="5"
|
r="5"
|
||||||
fill-opacity="1"
|
fill-opacity="1"
|
||||||
fill="currentColor">
|
fill="currentColor"
|
||||||
|
>
|
||||||
<animate
|
<animate
|
||||||
attributeName="fill-opacity"
|
attributeName="fill-opacity"
|
||||||
begin="0s"
|
begin="0s"
|
||||||
@ -33,7 +31,8 @@ function Spinner() {
|
|||||||
cy="27.063"
|
cy="27.063"
|
||||||
r="5"
|
r="5"
|
||||||
fill-opacity="0"
|
fill-opacity="0"
|
||||||
fill="currentColor">
|
fill="currentColor"
|
||||||
|
>
|
||||||
<animate
|
<animate
|
||||||
attributeName="fill-opacity"
|
attributeName="fill-opacity"
|
||||||
begin="0s"
|
begin="0s"
|
||||||
@ -48,7 +47,8 @@ function Spinner() {
|
|||||||
cy="42.663"
|
cy="42.663"
|
||||||
r="5"
|
r="5"
|
||||||
fill-opacity="0"
|
fill-opacity="0"
|
||||||
fill="currentColor">
|
fill="currentColor"
|
||||||
|
>
|
||||||
<animate
|
<animate
|
||||||
attributeName="fill-opacity"
|
attributeName="fill-opacity"
|
||||||
begin="0s"
|
begin="0s"
|
||||||
@ -63,7 +63,8 @@ function Spinner() {
|
|||||||
cy="49.125"
|
cy="49.125"
|
||||||
r="5"
|
r="5"
|
||||||
fill-opacity="0"
|
fill-opacity="0"
|
||||||
fill="currentColor">
|
fill="currentColor"
|
||||||
|
>
|
||||||
<animate
|
<animate
|
||||||
attributeName="fill-opacity"
|
attributeName="fill-opacity"
|
||||||
begin="0s"
|
begin="0s"
|
||||||
@ -78,7 +79,8 @@ function Spinner() {
|
|||||||
cy="42.663"
|
cy="42.663"
|
||||||
r="5"
|
r="5"
|
||||||
fill-opacity="0"
|
fill-opacity="0"
|
||||||
fill="currentColor">
|
fill="currentColor"
|
||||||
|
>
|
||||||
<animate
|
<animate
|
||||||
attributeName="fill-opacity"
|
attributeName="fill-opacity"
|
||||||
begin="0s"
|
begin="0s"
|
||||||
@ -93,7 +95,8 @@ function Spinner() {
|
|||||||
cy="27.063"
|
cy="27.063"
|
||||||
r="5"
|
r="5"
|
||||||
fill-opacity="0"
|
fill-opacity="0"
|
||||||
fill="currentColor">
|
fill="currentColor"
|
||||||
|
>
|
||||||
<animate
|
<animate
|
||||||
attributeName="fill-opacity"
|
attributeName="fill-opacity"
|
||||||
begin="0s"
|
begin="0s"
|
||||||
@ -108,7 +111,8 @@ function Spinner() {
|
|||||||
cy="11.462"
|
cy="11.462"
|
||||||
r="5"
|
r="5"
|
||||||
fill-opacity="0"
|
fill-opacity="0"
|
||||||
fill="currentColor">
|
fill="currentColor"
|
||||||
|
>
|
||||||
<animate
|
<animate
|
||||||
attributeName="fill-opacity"
|
attributeName="fill-opacity"
|
||||||
begin="0s"
|
begin="0s"
|
@ -1,6 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
const Logo = ({ ...rest }) => {
|
const UpscaylSVGLogo = ({ ...rest }) => {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
viewBox="0 0 256 256"
|
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 { 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 { cn } from "@/lib/utils";
|
||||||
import { useAtom, useAtomValue } from "jotai";
|
import { useAtom, useAtomValue } from "jotai";
|
||||||
import { WrenchIcon } from "lucide-react";
|
import { WrenchIcon } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
const ImageOptions = ({
|
const ImageViewSettings = ({
|
||||||
zoomAmount,
|
zoomAmount,
|
||||||
setZoomAmount,
|
setZoomAmount,
|
||||||
resetImagePaths,
|
resetImagePaths,
|
||||||
hideZoomOptions,
|
|
||||||
}: {
|
}: {
|
||||||
zoomAmount: string;
|
zoomAmount: string;
|
||||||
setZoomAmount: (arg: any) => void;
|
setZoomAmount: (arg: any) => void;
|
||||||
resetImagePaths: () => void;
|
resetImagePaths: () => void;
|
||||||
hideZoomOptions?: boolean;
|
|
||||||
}) => {
|
}) => {
|
||||||
const [openSidebar, setOpenSidebar] = useState(false);
|
const [openSidebar, setOpenSidebar] = useState(false);
|
||||||
const [viewType, setViewType] = useAtom(viewTypeAtom);
|
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 { useAtomValue } from "jotai";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
function RightPaneInfo({ version, batchMode }) {
|
function InstructionsCard({ version, batchMode }) {
|
||||||
const t = useAtomValue(translationAtom);
|
const t = useAtomValue(translationAtom);
|
||||||
|
|
||||||
return (
|
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 React, { useEffect } from "react";
|
||||||
import Spinner from "../../icons/Spinner";
|
import UpscaylSVGLogo from "@/components/icons/upscayl-logo-svg";
|
||||||
import Logo from "@/components/icons/Logo";
|
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import { translationAtom } from "@/atoms/translations-atom";
|
import { translationAtom } from "@/atoms/translations-atom";
|
||||||
|
import { ELECTRON_COMMANDS } from "@common/electron-commands";
|
||||||
|
import useLogger from "../hooks/use-logger";
|
||||||
|
|
||||||
function ProgressBar({
|
function ProgressBar({
|
||||||
progress,
|
progress,
|
||||||
doubleUpscaylCounter,
|
doubleUpscaylCounter,
|
||||||
stopHandler,
|
|
||||||
batchMode,
|
batchMode,
|
||||||
|
resetImagePaths,
|
||||||
}: {
|
}: {
|
||||||
progress: string;
|
progress: string;
|
||||||
doubleUpscaylCounter: number;
|
doubleUpscaylCounter: number;
|
||||||
stopHandler: () => void;
|
|
||||||
batchMode: boolean;
|
batchMode: boolean;
|
||||||
|
resetImagePaths: () => void;
|
||||||
}) {
|
}) {
|
||||||
const [batchProgress, setBatchProgress] = React.useState(0);
|
const [batchProgress, setBatchProgress] = React.useState(0);
|
||||||
const t = useAtomValue(translationAtom);
|
const t = useAtomValue(translationAtom);
|
||||||
|
const logit = useLogger();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const progressString = progress.trim().replace(/\n/g, "");
|
const progressString = progress.trim().replace(/\n/g, "");
|
||||||
@ -26,6 +28,12 @@ function ProgressBar({
|
|||||||
}
|
}
|
||||||
}, [progress]);
|
}, [progress]);
|
||||||
|
|
||||||
|
const stopHandler = () => {
|
||||||
|
window.electron.send(ELECTRON_COMMANDS.STOP);
|
||||||
|
logit("🛑 Stopping Upscayl");
|
||||||
|
resetImagePaths();
|
||||||
|
};
|
||||||
|
|
||||||
// const progressStyle = useMemo(() => {
|
// const progressStyle = useMemo(() => {
|
||||||
// if (progress.includes("%")) {
|
// if (progress.includes("%")) {
|
||||||
// return {
|
// return {
|
||||||
@ -44,11 +52,13 @@ function ProgressBar({
|
|||||||
return (
|
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="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">
|
<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">
|
<p className="rounded-full px-2 pb-2 font-bold">
|
||||||
{batchMode &&
|
{batchMode &&
|
||||||
`${t("APP.PROGRESS_BAR.BATCH_UPSCAYL_IN_PROGRESS_TITLE")} ${batchProgress}`}
|
`${t("APP.PROGRESS_BAR.BATCH_UPSCAYL_IN_PROGRESS_TITLE")} ${batchProgress}`}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-col items-center gap-1">
|
<div className="flex flex-col items-center gap-1">
|
||||||
{progress !== "Hold on..." ? (
|
{progress !== "Hold on..." ? (
|
||||||
<p className="text-sm font-bold">
|
<p className="text-sm font-bold">
|
||||||
@ -60,10 +70,12 @@ function ProgressBar({
|
|||||||
) : (
|
) : (
|
||||||
<p className="text-sm font-bold">{progress}</p>
|
<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">
|
<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")}
|
{t("APP.PROGRESS_BAR.IN_PROGRESS_TITLE")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button onClick={stopHandler} className="btn btn-outline">
|
<button onClick={stopHandler} className="btn btn-outline">
|
||||||
{t("APP.PROGRESS_BAR.STOP_BUTTON_TITLE")}
|
{t("APP.PROGRESS_BAR.STOP_BUTTON_TITLE")}
|
||||||
</button>
|
</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 { SelectTheme } from "./select-theme";
|
||||||
import { SaveOutputFolderToggle } from "./SaveOutputFolderToggle";
|
import { SaveOutputFolderToggle } from "./save-output-folder-toggle";
|
||||||
import { GpuIdInput } from "./GpuIdInput";
|
import { InputGpuId } from "./input-gpu-id";
|
||||||
import { CustomModelsFolderSelect } from "./CustomModelsFolderSelect";
|
import { CustomModelsFolderSelect } from "./select-custom-models-folder";
|
||||||
import { LogArea } from "./LogArea";
|
import { LogArea } from "./log-area";
|
||||||
import { ImageScaleSelect } from "./ImageScaleSelect";
|
import { SelectImageScale } from "./select-image-scale";
|
||||||
import { ImageFormatSelect } from "./ImageFormatSelect";
|
import { SelectImageFormat } from "./select-image-format";
|
||||||
import { DonateButton } from "./DonateButton";
|
import { DonateButton } from "./donate-button";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { themeChange } from "theme-change";
|
import { themeChange } from "theme-change";
|
||||||
import { useAtom, useAtomValue } from "jotai";
|
import { useAtom, useAtomValue } from "jotai";
|
||||||
import { customModelsPathAtom, scaleAtom } from "../../atoms/userSettingsAtom";
|
import { customModelsPathAtom, scaleAtom } from "@/atoms/user-settings-atom";
|
||||||
import { modelsListAtom } from "../../atoms/modelsListAtom";
|
import { modelsListAtom } from "@/atoms/models-list-atom";
|
||||||
import useLog from "../hooks/useLog";
|
import useLogger from "@/components/hooks/use-logger";
|
||||||
import { CompressionInput } from "./CompressionInput";
|
import { InputCompression } from "./input-compression";
|
||||||
import OverwriteToggle from "./OverwriteToggle";
|
import OverwriteToggle from "./overwrite-toggle";
|
||||||
import { UpscaylCloudModal } from "../UpscaylCloudModal";
|
import { UpscaylCloudModal } from "@/components/upscayl-cloud-modal";
|
||||||
import { ResetSettings } from "./ResetSettings";
|
import { ResetSettingsButton } from "./reset-settings-button";
|
||||||
import { featureFlags } from "@common/feature-flags";
|
import { FEATURE_FLAGS } from "@common/feature-flags";
|
||||||
import TurnOffNotificationsToggle from "./TurnOffNotificationsToggle";
|
import TurnOffNotificationsToggle from "./turn-off-notifications-toggle";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { CustomResolutionInput } from "./CustomResolutionInput";
|
import { InputCustomResolution } from "./input-custom-resolution";
|
||||||
import { TileSizeInput } from "./TileSizeInput";
|
import { InputTileSize } from "./input-tile-size";
|
||||||
import LanguageSwitcher from "./language-switcher";
|
import LanguageSwitcher from "./language-switcher";
|
||||||
import { translationAtom } from "@/atoms/translations-atom";
|
import { translationAtom } from "@/atoms/translations-atom";
|
||||||
|
|
||||||
@ -34,7 +34,6 @@ interface IProps {
|
|||||||
gpuId: string;
|
gpuId: string;
|
||||||
setGpuId: React.Dispatch<React.SetStateAction<string>>;
|
setGpuId: React.Dispatch<React.SetStateAction<string>>;
|
||||||
logData: string[];
|
logData: string[];
|
||||||
os: "linux" | "mac" | "win" | undefined;
|
|
||||||
show: boolean;
|
show: boolean;
|
||||||
setShow: React.Dispatch<React.SetStateAction<boolean>>;
|
setShow: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
setDontShowCloudModal: React.Dispatch<React.SetStateAction<boolean>>;
|
setDontShowCloudModal: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
@ -50,19 +49,11 @@ function SettingsTab({
|
|||||||
saveImageAs,
|
saveImageAs,
|
||||||
setSaveImageAs,
|
setSaveImageAs,
|
||||||
logData,
|
logData,
|
||||||
os,
|
|
||||||
show,
|
show,
|
||||||
setShow,
|
setShow,
|
||||||
setDontShowCloudModal,
|
setDontShowCloudModal,
|
||||||
}: IProps) {
|
}: IProps) {
|
||||||
// STATES
|
|
||||||
const [currentModel, setCurrentModel] = useState<{
|
|
||||||
label: string;
|
|
||||||
value: string;
|
|
||||||
}>({
|
|
||||||
label: null,
|
|
||||||
value: null,
|
|
||||||
});
|
|
||||||
const [isCopied, setIsCopied] = useState(false);
|
const [isCopied, setIsCopied] = useState(false);
|
||||||
|
|
||||||
const [customModelsPath, setCustomModelsPath] = useAtom(customModelsPathAtom);
|
const [customModelsPath, setCustomModelsPath] = useAtom(customModelsPathAtom);
|
||||||
@ -72,7 +63,7 @@ function SettingsTab({
|
|||||||
const [timeoutId, setTimeoutId] = useState(null);
|
const [timeoutId, setTimeoutId] = useState(null);
|
||||||
const t = useAtomValue(translationAtom);
|
const t = useAtomValue(translationAtom);
|
||||||
|
|
||||||
const { logit } = useLog();
|
const logit = useLogger();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
themeChange(false);
|
themeChange(false);
|
||||||
@ -90,7 +81,6 @@ function SettingsTab({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!localStorage.getItem("model")) {
|
if (!localStorage.getItem("model")) {
|
||||||
setCurrentModel(modelOptions[0]);
|
|
||||||
setModel(modelOptions[0].value);
|
setModel(modelOptions[0].value);
|
||||||
localStorage.setItem("model", JSON.stringify(modelOptions[0]));
|
localStorage.setItem("model", JSON.stringify(modelOptions[0]));
|
||||||
logit("🔀 Setting model to", modelOptions[0].value);
|
logit("🔀 Setting model to", modelOptions[0].value);
|
||||||
@ -107,7 +97,6 @@ function SettingsTab({
|
|||||||
logit("🔀 Setting model to", modelOptions[0].value);
|
logit("🔀 Setting model to", modelOptions[0].value);
|
||||||
currentlySavedModel = modelOptions[0];
|
currentlySavedModel = modelOptions[0];
|
||||||
}
|
}
|
||||||
setCurrentModel(currentlySavedModel);
|
|
||||||
setModel(currentlySavedModel.value);
|
setModel(currentlySavedModel.value);
|
||||||
logit(
|
logit(
|
||||||
"⚙️ Getting model from localStorage: ",
|
"⚙️ Getting model from localStorage: ",
|
||||||
@ -194,7 +183,7 @@ function SettingsTab({
|
|||||||
>
|
>
|
||||||
{t("SETTINGS.SUPPORT.DOCS_BUTTON_TITLE")}
|
{t("SETTINGS.SUPPORT.DOCS_BUTTON_TITLE")}
|
||||||
</a>
|
</a>
|
||||||
{featureFlags.APP_STORE_BUILD && (
|
{FEATURE_FLAGS.APP_STORE_BUILD && (
|
||||||
<a
|
<a
|
||||||
className="btn btn-primary"
|
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`}
|
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")}
|
{t("SETTINGS.SUPPORT.EMAIL_BUTTON_TITLE")}
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
{!featureFlags.APP_STORE_BUILD && <DonateButton />}
|
{!FEATURE_FLAGS.APP_STORE_BUILD && <DonateButton />}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<LogArea
|
<LogArea
|
||||||
@ -213,23 +202,23 @@ function SettingsTab({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* THEME SELECTOR */}
|
{/* THEME SELECTOR */}
|
||||||
<ThemeSelect />
|
<SelectTheme />
|
||||||
|
|
||||||
<LanguageSwitcher />
|
<LanguageSwitcher />
|
||||||
|
|
||||||
{/* IMAGE FORMAT BUTTONS */}
|
{/* IMAGE FORMAT BUTTONS */}
|
||||||
<ImageFormatSelect
|
<SelectImageFormat
|
||||||
batchMode={batchMode}
|
batchMode={batchMode}
|
||||||
saveImageAs={saveImageAs}
|
saveImageAs={saveImageAs}
|
||||||
setExportType={setExportType}
|
setExportType={setExportType}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* IMAGE SCALE */}
|
{/* IMAGE SCALE */}
|
||||||
<ImageScaleSelect scale={scale} setScale={setScale} />
|
<SelectImageScale scale={scale} setScale={setScale} />
|
||||||
|
|
||||||
<CustomResolutionInput />
|
<InputCustomResolution />
|
||||||
|
|
||||||
<CompressionInput
|
<InputCompression
|
||||||
compression={compression}
|
compression={compression}
|
||||||
handleCompressionChange={handleCompressionChange}
|
handleCompressionChange={handleCompressionChange}
|
||||||
/>
|
/>
|
||||||
@ -240,9 +229,9 @@ function SettingsTab({
|
|||||||
<TurnOffNotificationsToggle />
|
<TurnOffNotificationsToggle />
|
||||||
|
|
||||||
{/* GPU ID INPUT */}
|
{/* GPU ID INPUT */}
|
||||||
<GpuIdInput gpuId={gpuId} handleGpuIdChange={handleGpuIdChange} />
|
<InputGpuId gpuId={gpuId} handleGpuIdChange={handleGpuIdChange} />
|
||||||
|
|
||||||
<TileSizeInput />
|
<InputTileSize />
|
||||||
|
|
||||||
{/* CUSTOM MODEL */}
|
{/* CUSTOM MODEL */}
|
||||||
<CustomModelsFolderSelect
|
<CustomModelsFolderSelect
|
||||||
@ -251,9 +240,9 @@ function SettingsTab({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* RESET SETTINGS */}
|
{/* RESET SETTINGS */}
|
||||||
<ResetSettings />
|
<ResetSettingsButton />
|
||||||
|
|
||||||
{featureFlags.SHOW_UPSCAYL_CLOUD_INFO && (
|
{FEATURE_FLAGS.SHOW_UPSCAYL_CLOUD_INFO && (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
className="mx-5 mb-5 animate-pulse rounded-btn bg-success p-1 text-sm text-slate-50 shadow-lg shadow-success/40"
|
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;
|
handleCompressionChange: (arg: any) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function CompressionInput({
|
export function InputCompression({
|
||||||
compression,
|
compression,
|
||||||
handleCompressionChange,
|
handleCompressionChange,
|
||||||
}: CompressionInputProps) {
|
}: 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 { useAtom, useAtomValue } from "jotai";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { translationAtom } from "@/atoms/translations-atom";
|
import { translationAtom } from "@/atoms/translations-atom";
|
||||||
|
|
||||||
export function CustomResolutionInput() {
|
export function InputCustomResolution() {
|
||||||
const [useCustomWidth, setUseCustomWidth] = useAtom(useCustomWidthAtom);
|
const [useCustomWidth, setUseCustomWidth] = useAtom(useCustomWidthAtom);
|
||||||
const [customWidth, setCustomWidth] = useAtom(customWidthAtom);
|
const [customWidth, setCustomWidth] = useAtom(customWidthAtom);
|
||||||
const t = useAtomValue(translationAtom);
|
const t = useAtomValue(translationAtom);
|
@ -7,7 +7,7 @@ type GpuIdInputProps = {
|
|||||||
handleGpuIdChange: (arg: string) => void;
|
handleGpuIdChange: (arg: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function GpuIdInput({ gpuId, handleGpuIdChange }) {
|
export function InputGpuId({ gpuId, handleGpuIdChange }) {
|
||||||
const t = useAtomValue(translationAtom);
|
const t = useAtomValue(translationAtom);
|
||||||
|
|
||||||
return (
|
return (
|
@ -1,9 +1,9 @@
|
|||||||
import { translationAtom } from "@/atoms/translations-atom";
|
import { translationAtom } from "@/atoms/translations-atom";
|
||||||
import { tileSizeAtom } from "@/atoms/userSettingsAtom";
|
import { tileSizeAtom } from "@/atoms/user-settings-atom";
|
||||||
import { useAtom, useAtomValue } from "jotai";
|
import { useAtom, useAtomValue } from "jotai";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
export function TileSizeInput() {
|
export function InputTileSize() {
|
||||||
const [tileSize, setTileSize] = useAtom(tileSizeAtom);
|
const [tileSize, setTileSize] = useAtom(tileSizeAtom);
|
||||||
const t = useAtomValue(translationAtom);
|
const t = useAtomValue(translationAtom);
|
||||||
|
|
@ -1,5 +1,5 @@
|
|||||||
import { translationAtom } from "@/atoms/translations-atom";
|
import { translationAtom } from "@/atoms/translations-atom";
|
||||||
import { overwriteAtom } from "@/atoms/userSettingsAtom";
|
import { overwriteAtom } from "@/atoms/user-settings-atom";
|
||||||
import { useAtom, useAtomValue } from "jotai";
|
import { useAtom, useAtomValue } from "jotai";
|
||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
|
|
@ -2,7 +2,7 @@ import { translationAtom } from "@/atoms/translations-atom";
|
|||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
export function ResetSettings() {
|
export function ResetSettingsButton() {
|
||||||
const t = useAtomValue(translationAtom);
|
const t = useAtomValue(translationAtom);
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-start gap-2">
|
<div className="flex flex-col items-start gap-2">
|
@ -2,7 +2,7 @@ import { translationAtom } from "@/atoms/translations-atom";
|
|||||||
import {
|
import {
|
||||||
savedOutputPathAtom,
|
savedOutputPathAtom,
|
||||||
rememberOutputFolderAtom,
|
rememberOutputFolderAtom,
|
||||||
} from "@/atoms/userSettingsAtom";
|
} from "@/atoms/user-settings-atom";
|
||||||
import { useAtom, useAtomValue } from "jotai";
|
import { useAtom, useAtomValue } from "jotai";
|
||||||
|
|
||||||
export function SaveOutputFolderToggle() {
|
export function SaveOutputFolderToggle() {
|
@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import commands from "../../../common/commands";
|
import { ELECTRON_COMMANDS } from "@common/electron-commands";
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import { translationAtom } from "@/atoms/translations-atom";
|
import { translationAtom } from "@/atoms/translations-atom";
|
||||||
|
|
||||||
@ -32,12 +32,15 @@ export function CustomModelsFolderSelect({
|
|||||||
className="btn btn-primary"
|
className="btn btn-primary"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const customModelPath = await window.electron.invoke(
|
const customModelPath = await window.electron.invoke(
|
||||||
commands.SELECT_CUSTOM_MODEL_FOLDER,
|
ELECTRON_COMMANDS.SELECT_CUSTOM_MODEL_FOLDER,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (customModelPath !== null) {
|
if (customModelPath !== null) {
|
||||||
setCustomModelsPath(customModelPath);
|
setCustomModelsPath(customModelPath);
|
||||||
window.electron.send(commands.GET_MODELS_LIST, customModelPath);
|
window.electron.send(
|
||||||
|
ELECTRON_COMMANDS.GET_MODELS_LIST,
|
||||||
|
customModelPath,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
setCustomModelsPath("");
|
setCustomModelsPath("");
|
||||||
}
|
}
|
@ -7,7 +7,7 @@ type ImageFormatSelectProps = {
|
|||||||
setExportType: (arg: string) => void;
|
setExportType: (arg: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ImageFormatSelect({
|
export function SelectImageFormat({
|
||||||
batchMode,
|
batchMode,
|
||||||
saveImageAs,
|
saveImageAs,
|
||||||
setExportType,
|
setExportType,
|
@ -1,5 +1,5 @@
|
|||||||
import { translationAtom } from "@/atoms/translations-atom";
|
import { translationAtom } from "@/atoms/translations-atom";
|
||||||
import { useCustomWidthAtom } from "@/atoms/userSettingsAtom";
|
import { useCustomWidthAtom } from "@/atoms/user-settings-atom";
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
|
|
||||||
type ImageScaleSelectProps = {
|
type ImageScaleSelectProps = {
|
||||||
@ -8,7 +8,7 @@ type ImageScaleSelectProps = {
|
|||||||
hideInfo?: boolean;
|
hideInfo?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ImageScaleSelect({
|
export function SelectImageScale({
|
||||||
scale,
|
scale,
|
||||||
setScale,
|
setScale,
|
||||||
hideInfo,
|
hideInfo,
|
@ -1,7 +1,8 @@
|
|||||||
import { translationAtom } from "@/atoms/translations-atom";
|
import { translationAtom } from "@/atoms/translations-atom";
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
export function ThemeSelect() {
|
|
||||||
|
export function SelectTheme() {
|
||||||
const availableThemes = [
|
const availableThemes = [
|
||||||
{ label: "upscayl", value: "upscayl" },
|
{ label: "upscayl", value: "upscayl" },
|
||||||
{ label: "light", value: "light" },
|
{ label: "light", value: "light" },
|
@ -1,5 +1,5 @@
|
|||||||
import { translationAtom } from "@/atoms/translations-atom";
|
import { translationAtom } from "@/atoms/translations-atom";
|
||||||
import { turnOffNotificationsAtom } from "@/atoms/userSettingsAtom";
|
import { turnOffNotificationsAtom } from "@/atoms/user-settings-atom";
|
||||||
import { useAtom, useAtomValue } from "jotai";
|
import { useAtom, useAtomValue } from "jotai";
|
||||||
|
|
||||||
const TurnOffNotificationsToggle = () => {
|
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 { useAtom, useAtomValue } from "jotai";
|
||||||
import React, { useCallback, useEffect, useState } from "react";
|
import React, { useEffect, useMemo, useState } from "react";
|
||||||
import { Tooltip } from "react-tooltip";
|
import { Tooltip } from "react-tooltip";
|
||||||
import { themeChange } from "theme-change";
|
import { themeChange } from "theme-change";
|
||||||
import { TModelsList, modelsListAtom } from "../../../atoms/modelsListAtom";
|
import { TModelsList, modelsListAtom } from "../../../atoms/models-list-atom";
|
||||||
import useLog from "../../hooks/useLog";
|
import useLogger from "../../hooks/use-logger";
|
||||||
import {
|
import {
|
||||||
noImageProcessingAtom,
|
|
||||||
savedOutputPathAtom,
|
savedOutputPathAtom,
|
||||||
progressAtom,
|
progressAtom,
|
||||||
rememberOutputFolderAtom,
|
rememberOutputFolderAtom,
|
||||||
scaleAtom,
|
scaleAtom,
|
||||||
customWidthAtom,
|
customWidthAtom,
|
||||||
useCustomWidthAtom,
|
useCustomWidthAtom,
|
||||||
} from "../../../atoms/userSettingsAtom";
|
} from "../../../atoms/user-settings-atom";
|
||||||
import { featureFlags } from "@common/feature-flags";
|
import { FEATURE_FLAGS } from "@common/feature-flags";
|
||||||
import getModelScale from "@common/check-model-scale";
|
import { ELECTRON_COMMANDS } from "@common/electron-commands";
|
||||||
import COMMAND from "@common/commands";
|
|
||||||
import Select from "react-select";
|
import Select from "react-select";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
import { ImageScaleSelect } from "@/components/settings-tab/ImageScaleSelect";
|
|
||||||
import { translationAtom } from "@/atoms/translations-atom";
|
import { translationAtom } from "@/atoms/translations-atom";
|
||||||
|
import { SelectImageScale } from "../settings-tab/select-image-scale";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
selectImageHandler: () => Promise<void>;
|
selectImageHandler: () => Promise<void>;
|
||||||
@ -42,7 +40,7 @@ interface IProps {
|
|||||||
setGpuId: React.Dispatch<React.SetStateAction<string>>;
|
setGpuId: React.Dispatch<React.SetStateAction<string>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function LeftPaneImageSteps({
|
function UpscaylSteps({
|
||||||
selectImageHandler,
|
selectImageHandler,
|
||||||
selectFolderHandler,
|
selectFolderHandler,
|
||||||
handleModelChange,
|
handleModelChange,
|
||||||
@ -65,23 +63,19 @@ function LeftPaneImageSteps({
|
|||||||
|
|
||||||
const modelOptions = useAtomValue(modelsListAtom);
|
const modelOptions = useAtomValue(modelsListAtom);
|
||||||
const [scale, setScale] = useAtom(scaleAtom);
|
const [scale, setScale] = useAtom(scaleAtom);
|
||||||
const noImageProcessing = useAtomValue(noImageProcessingAtom);
|
|
||||||
const [outputPath, setOutputPath] = useAtom(savedOutputPathAtom);
|
const [outputPath, setOutputPath] = useAtom(savedOutputPathAtom);
|
||||||
const [progress, setProgress] = useAtom(progressAtom);
|
const [progress, setProgress] = useAtom(progressAtom);
|
||||||
const rememberOutputFolder = useAtomValue(rememberOutputFolderAtom);
|
const rememberOutputFolder = useAtomValue(rememberOutputFolderAtom);
|
||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = React.useState(false);
|
||||||
const [customWidth, setCustomWidth] = useAtom(customWidthAtom);
|
const customWidth = useAtomValue(customWidthAtom);
|
||||||
const [useCustomWidth, setUseCustomWidth] = useAtom(useCustomWidthAtom);
|
const useCustomWidth = useAtomValue(useCustomWidthAtom);
|
||||||
|
|
||||||
const [targetWidth, setTargetWidth] = useState<number>(null);
|
const logit = useLogger();
|
||||||
const [targetHeight, setTargetHeight] = useState<number>(null);
|
|
||||||
|
|
||||||
const { logit } = useLog();
|
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const t = useAtomValue(translationAtom);
|
const t = useAtomValue(translationAtom);
|
||||||
|
|
||||||
const outputHandler = async () => {
|
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) {
|
if (path !== null) {
|
||||||
logit("🗂 Setting Output Path: ", path);
|
logit("🗂 Setting Output Path: ", path);
|
||||||
setOutputPath(path);
|
setOutputPath(path);
|
||||||
@ -136,12 +130,7 @@ function LeftPaneImageSteps({
|
|||||||
logit("🔀 Setting model to", currentModel.value);
|
logit("🔀 Setting model to", currentModel.value);
|
||||||
}, [currentModel]);
|
}, [currentModel]);
|
||||||
|
|
||||||
useEffect(() => {
|
const upscaylResolution = useMemo(() => {
|
||||||
setTargetWidth(getUpscaleResolution().width);
|
|
||||||
setTargetHeight(getUpscaleResolution().height);
|
|
||||||
}, [dimensions.width, dimensions.height, doubleUpscayl, scale]);
|
|
||||||
|
|
||||||
const getUpscaleResolution = useCallback(() => {
|
|
||||||
const newDimensions = {
|
const newDimensions = {
|
||||||
width: dimensions.width,
|
width: dimensions.width,
|
||||||
height: dimensions.height,
|
height: dimensions.height,
|
||||||
@ -278,7 +267,7 @@ function LeftPaneImageSteps({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ImageScaleSelect scale={scale} setScale={setScale} hideInfo />
|
<SelectImageScale scale={scale} setScale={setScale} hideInfo />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* STEP 3 */}
|
{/* STEP 3 */}
|
||||||
@ -288,7 +277,7 @@ function LeftPaneImageSteps({
|
|||||||
<span className="leading-none">
|
<span className="leading-none">
|
||||||
{t("APP.OUTPUT_PATH_SELECTION.TITLE")}
|
{t("APP.OUTPUT_PATH_SELECTION.TITLE")}
|
||||||
</span>
|
</span>
|
||||||
{featureFlags.APP_STORE_BUILD && (
|
{FEATURE_FLAGS.APP_STORE_BUILD && (
|
||||||
<button
|
<button
|
||||||
className="badge badge-outline badge-sm cursor-pointer"
|
className="badge badge-outline badge-sm cursor-pointer"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
@ -299,7 +288,7 @@ function LeftPaneImageSteps({
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{!outputPath && featureFlags.APP_STORE_BUILD && (
|
{!outputPath && FEATURE_FLAGS.APP_STORE_BUILD && (
|
||||||
<div className="text-xs">
|
<div className="text-xs">
|
||||||
<span className="rounded-btn bg-base-200 px-2 font-medium uppercase text-base-content/50">
|
<span className="rounded-btn bg-base-200 px-2 font-medium uppercase text-base-content/50">
|
||||||
{t("APP.OUTPUT_PATH_SELECTION.NOT_SELECTED")}
|
{t("APP.OUTPUT_PATH_SELECTION.NOT_SELECTED")}
|
||||||
@ -307,7 +296,7 @@ function LeftPaneImageSteps({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{!batchMode && !featureFlags.APP_STORE_BUILD && (
|
{!batchMode && !FEATURE_FLAGS.APP_STORE_BUILD && (
|
||||||
<p className="mb-2 text-sm">
|
<p className="mb-2 text-sm">
|
||||||
{!batchMode
|
{!batchMode
|
||||||
? t("APP.OUTPUT_PATH_SELECTION.DEFAULT_IMG_PATH")
|
? t("APP.OUTPUT_PATH_SELECTION.DEFAULT_IMG_PATH")
|
||||||
@ -335,7 +324,7 @@ function LeftPaneImageSteps({
|
|||||||
</span>
|
</span>
|
||||||
{t("APP.SCALE_SELECTION.TO_TITLE")}
|
{t("APP.SCALE_SELECTION.TO_TITLE")}
|
||||||
<span className="font-bold">
|
<span className="font-bold">
|
||||||
{getUpscaleResolution().width}x{getUpscaleResolution().height}
|
{upscaylResolution.width}x{upscaylResolution.height}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</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"
|
"LINK_TITLE": "The Upscayl Team"
|
||||||
},
|
},
|
||||||
"SETTINGS": {
|
"SETTINGS": {
|
||||||
"TITLE": "SETTINGS",
|
"TITLE": "Settings",
|
||||||
"CHANGE_LANGUAGE": { "TITLE": "Change Language" },
|
"CHANGE_LANGUAGE": { "TITLE": "Change Language" },
|
||||||
"IMAGE_COMPRESSION": {
|
"IMAGE_COMPRESSION": {
|
||||||
"TITLE": "Image Compression",
|
"TITLE": "Image Compression",
|
||||||
@ -127,7 +127,7 @@
|
|||||||
"FROM_TITLE": "Upscayl from ",
|
"FROM_TITLE": "Upscayl from ",
|
||||||
"TO_TITLE": " to ",
|
"TO_TITLE": " to ",
|
||||||
"NO_OUTPUT_FOLDER_ALERT": "Please select an output folder first",
|
"NO_OUTPUT_FOLDER_ALERT": "Please select an output folder first",
|
||||||
"START_BUTTON_TITLE": "Upscayl",
|
"START_BUTTON_TITLE": "Upscayl 🚀",
|
||||||
"IN_PROGRESS_BUTTON_TITLE": "Upscayling ⏳"
|
"IN_PROGRESS_BUTTON_TITLE": "Upscayling ⏳"
|
||||||
},
|
},
|
||||||
"IMAGE_OPTIONS": {
|
"IMAGE_OPTIONS": {
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
"LINK_TITLE": "El equipo de Upscayl"
|
"LINK_TITLE": "El equipo de Upscayl"
|
||||||
},
|
},
|
||||||
"SETTINGS": {
|
"SETTINGS": {
|
||||||
"TITLE": "AJUSTES",
|
"TITLE": "Ajustes",
|
||||||
"CHANGE_LANGUAGE": { "TITLE": "Cambiar idioma" },
|
"CHANGE_LANGUAGE": { "TITLE": "Cambiar idioma" },
|
||||||
"IMAGE_COMPRESSION": {
|
"IMAGE_COMPRESSION": {
|
||||||
"TITLE": "Compresión de imagen",
|
"TITLE": "Compresión de imagen",
|
||||||
@ -127,7 +127,7 @@
|
|||||||
"FROM_TITLE": "Aumentar desde ",
|
"FROM_TITLE": "Aumentar desde ",
|
||||||
"TO_TITLE": " a ",
|
"TO_TITLE": " a ",
|
||||||
"NO_OUTPUT_FOLDER_ALERT": "Por favor, selecciona primero una carpeta de salida",
|
"NO_OUTPUT_FOLDER_ALERT": "Por favor, selecciona primero una carpeta de salida",
|
||||||
"START_BUTTON_TITLE": "Upscayl",
|
"START_BUTTON_TITLE": "Upscayl 🚀",
|
||||||
"IN_PROGRESS_BUTTON_TITLE": "Aumentando ⏳"
|
"IN_PROGRESS_BUTTON_TITLE": "Aumentando ⏳"
|
||||||
},
|
},
|
||||||
"IMAGE_OPTIONS": {
|
"IMAGE_OPTIONS": {
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
"LINK_TITLE": "L'équipe Upscayl"
|
"LINK_TITLE": "L'équipe Upscayl"
|
||||||
},
|
},
|
||||||
"SETTINGS": {
|
"SETTINGS": {
|
||||||
"TITLE": "PARAMÈTRES",
|
"TITLE": "Paramètres",
|
||||||
"CHANGE_LANGUAGE": { "TITLE": "Changer de langue" },
|
"CHANGE_LANGUAGE": { "TITLE": "Changer de langue" },
|
||||||
"IMAGE_COMPRESSION": {
|
"IMAGE_COMPRESSION": {
|
||||||
"TITLE": "Compression d'image",
|
"TITLE": "Compression d'image",
|
||||||
@ -127,7 +127,7 @@
|
|||||||
"FROM_TITLE": "Suréchantillonner de ",
|
"FROM_TITLE": "Suréchantillonner de ",
|
||||||
"TO_TITLE": " à ",
|
"TO_TITLE": " à ",
|
||||||
"NO_OUTPUT_FOLDER_ALERT": "Veuillez d'abord sélectionner un dossier de sortie",
|
"NO_OUTPUT_FOLDER_ALERT": "Veuillez d'abord sélectionner un dossier de sortie",
|
||||||
"START_BUTTON_TITLE": "Suréchantillonner",
|
"START_BUTTON_TITLE": "Suréchantillonner 🚀",
|
||||||
"IN_PROGRESS_BUTTON_TITLE": "Suréchantillonnage ⏳"
|
"IN_PROGRESS_BUTTON_TITLE": "Suréchantillonnage ⏳"
|
||||||
},
|
},
|
||||||
"IMAGE_OPTIONS": {
|
"IMAGE_OPTIONS": {
|
||||||
|
@ -127,7 +127,7 @@
|
|||||||
"FROM_TITLE": "Upscayl元 ",
|
"FROM_TITLE": "Upscayl元 ",
|
||||||
"TO_TITLE": " から ",
|
"TO_TITLE": " から ",
|
||||||
"NO_OUTPUT_FOLDER_ALERT": "まず出力フォルダを選択してください",
|
"NO_OUTPUT_FOLDER_ALERT": "まず出力フォルダを選択してください",
|
||||||
"START_BUTTON_TITLE": "Upscayl",
|
"START_BUTTON_TITLE": "Upscayl 🚀",
|
||||||
"IN_PROGRESS_BUTTON_TITLE": "Upscayl中 ⏳"
|
"IN_PROGRESS_BUTTON_TITLE": "Upscayl中 ⏳"
|
||||||
},
|
},
|
||||||
"IMAGE_OPTIONS": {
|
"IMAGE_OPTIONS": {
|
||||||
|
@ -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"
|
"LINK_TITLE": "Команда Upscayl"
|
||||||
},
|
},
|
||||||
"SETTINGS": {
|
"SETTINGS": {
|
||||||
"TITLE": "НАСТРОЙКИ",
|
"TITLE": "Настройки",
|
||||||
"CHANGE_LANGUAGE": { "TITLE": "Сменить язык" },
|
"CHANGE_LANGUAGE": { "TITLE": "Сменить язык" },
|
||||||
"IMAGE_COMPRESSION": {
|
"IMAGE_COMPRESSION": {
|
||||||
"TITLE": "Сжатие изображения",
|
"TITLE": "Сжатие изображения",
|
||||||
@ -127,7 +127,7 @@
|
|||||||
"FROM_TITLE": "Увеличить с ",
|
"FROM_TITLE": "Увеличить с ",
|
||||||
"TO_TITLE": " до ",
|
"TO_TITLE": " до ",
|
||||||
"NO_OUTPUT_FOLDER_ALERT": "Пожалуйста, сначала выберите папку вывода",
|
"NO_OUTPUT_FOLDER_ALERT": "Пожалуйста, сначала выберите папку вывода",
|
||||||
"START_BUTTON_TITLE": "Увеличить",
|
"START_BUTTON_TITLE": "Увеличить 🚀",
|
||||||
"IN_PROGRESS_BUTTON_TITLE": "Увеличение ⏳"
|
"IN_PROGRESS_BUTTON_TITLE": "Увеличение ⏳"
|
||||||
},
|
},
|
||||||
"IMAGE_OPTIONS": {
|
"IMAGE_OPTIONS": {
|
||||||
|
@ -127,7 +127,7 @@
|
|||||||
"FROM_TITLE": "从 ",
|
"FROM_TITLE": "从 ",
|
||||||
"TO_TITLE": " 升图到 ",
|
"TO_TITLE": " 升图到 ",
|
||||||
"NO_OUTPUT_FOLDER_ALERT": "请先选择一个输出文件夹",
|
"NO_OUTPUT_FOLDER_ALERT": "请先选择一个输出文件夹",
|
||||||
"START_BUTTON_TITLE": "升图!",
|
"START_BUTTON_TITLE": "升图!🚀",
|
||||||
"IN_PROGRESS_BUTTON_TITLE": "正在升图 ⏳"
|
"IN_PROGRESS_BUTTON_TITLE": "正在升图 ⏳"
|
||||||
},
|
},
|
||||||
"IMAGE_OPTIONS": {
|
"IMAGE_OPTIONS": {
|
||||||
|
@ -1,143 +1,107 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { useState, useEffect, useCallback, useMemo, useRef } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import COMMAND from "../../common/commands";
|
import { ELECTRON_COMMANDS } from "@common/electron-commands";
|
||||||
import { ReactCompareSlider } from "react-compare-slider";
|
import { useAtom, useAtomValue, useSetAtom } from "jotai";
|
||||||
import Header from "../components/Header";
|
import { modelsListAtom } from "../atoms/models-list-atom";
|
||||||
import Footer from "../components/Footer";
|
|
||||||
import ProgressBar from "../components/upscayl-tab/view/ProgressBar";
|
|
||||||
import RightPaneInfo from "../components/upscayl-tab/view/RightPaneInfo";
|
|
||||||
import ImageOptions from "../components/upscayl-tab/view/ImageOptions";
|
|
||||||
import LeftPaneImageSteps from "../components/upscayl-tab/config/LeftPaneImageSteps";
|
|
||||||
import Tabs from "../components/Tabs";
|
|
||||||
import SettingsTab from "../components/settings-tab";
|
|
||||||
import { useAtom, useAtomValue } from "jotai";
|
|
||||||
import { logAtom } from "../atoms/logAtom";
|
|
||||||
import { modelsListAtom } from "../atoms/modelsListAtom";
|
|
||||||
import {
|
import {
|
||||||
batchModeAtom,
|
batchModeAtom,
|
||||||
lensSizeAtom,
|
|
||||||
compressionAtom,
|
|
||||||
dontShowCloudModalAtom,
|
|
||||||
noImageProcessingAtom,
|
|
||||||
savedOutputPathAtom,
|
savedOutputPathAtom,
|
||||||
overwriteAtom,
|
|
||||||
progressAtom,
|
progressAtom,
|
||||||
scaleAtom,
|
|
||||||
viewTypeAtom,
|
|
||||||
rememberOutputFolderAtom,
|
rememberOutputFolderAtom,
|
||||||
showSidebarAtom,
|
} from "../atoms/user-settings-atom";
|
||||||
customWidthAtom,
|
import useLogger from "../components/hooks/use-logger";
|
||||||
useCustomWidthAtom,
|
import { newsAtom, showNewsModalAtom } from "@/atoms/news-atom";
|
||||||
tileSizeAtom,
|
|
||||||
} from "../atoms/userSettingsAtom";
|
|
||||||
import useLog from "../components/hooks/useLog";
|
|
||||||
import { UpscaylCloudModal } from "../components/UpscaylCloudModal";
|
|
||||||
import { featureFlags } from "@common/feature-flags";
|
|
||||||
import {
|
|
||||||
BatchUpscaylPayload,
|
|
||||||
DoubleUpscaylPayload,
|
|
||||||
ImageUpscaylPayload,
|
|
||||||
} from "@common/types/types";
|
|
||||||
import { NewsModal } from "@/components/NewsModal";
|
|
||||||
import { newsAtom, showNewsModalAtom } from "@/atoms/newsAtom";
|
|
||||||
import matter from "gray-matter";
|
|
||||||
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
import { ToastAction } from "@/components/ui/toast";
|
import { ToastAction } from "@/components/ui/toast";
|
||||||
import Logo from "@/components/icons/Logo";
|
import UpscaylSVGLogo from "@/components/icons/upscayl-logo-svg";
|
||||||
import { sanitizePath } from "@common/sanitize-path";
|
|
||||||
import getDirectoryFromPath from "@common/get-directory-from-path";
|
|
||||||
import { translationAtom } from "@/atoms/translations-atom";
|
import { translationAtom } from "@/atoms/translations-atom";
|
||||||
|
import Sidebar from "@/components/sidebar";
|
||||||
|
import MainContent from "@/components/main-content";
|
||||||
|
import getDirectoryFromPath from "@common/get-directory-from-path";
|
||||||
|
import { FEATURE_FLAGS } from "@common/feature-flags";
|
||||||
|
import { VALID_IMAGE_FORMATS } from "@/lib/valid-formats";
|
||||||
|
import { initCustomModels } from "@/components/hooks/use-custom-models";
|
||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
const allowedFileTypes = ["png", "jpg", "jpeg", "jfif", "webp"];
|
|
||||||
|
|
||||||
const t = useAtomValue(translationAtom);
|
const t = useAtomValue(translationAtom);
|
||||||
|
const logit = useLogger();
|
||||||
|
const { toast } = useToast();
|
||||||
|
|
||||||
|
initCustomModels();
|
||||||
|
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
// LOCAL STATES
|
|
||||||
const [os, setOs] = useState<"linux" | "mac" | "win" | undefined>(undefined);
|
|
||||||
const [imagePath, setImagePath] = useState("");
|
const [imagePath, setImagePath] = useState("");
|
||||||
const [upscaledImagePath, setUpscaledImagePath] = useState("");
|
const [upscaledImagePath, setUpscaledImagePath] = useState("");
|
||||||
const [model, setModel] = useState("realesrgan-x4plus");
|
|
||||||
const [version, setVersion] = useState("");
|
|
||||||
const [batchFolderPath, setBatchFolderPath] = useState("");
|
|
||||||
const [doubleUpscayl, setDoubleUpscayl] = useState(false);
|
|
||||||
const overwrite = useAtomValue(overwriteAtom);
|
|
||||||
const [upscaledBatchFolderPath, setUpscaledBatchFolderPath] = useState("");
|
|
||||||
const [doubleUpscaylCounter, setDoubleUpscaylCounter] = useState(0);
|
|
||||||
const [gpuId, setGpuId] = useState("");
|
|
||||||
const [saveImageAs, setSaveImageAs] = useState("png");
|
|
||||||
const [zoomAmount, setZoomAmount] = useState("100");
|
|
||||||
const [backgroundPosition, setBackgroundPosition] = useState("0% 0%");
|
|
||||||
const [dimensions, setDimensions] = useState({
|
const [dimensions, setDimensions] = useState({
|
||||||
width: null,
|
width: null,
|
||||||
height: null,
|
height: null,
|
||||||
});
|
});
|
||||||
const [selectedTab, setSelectedTab] = useState(0);
|
const setOutputPath = useSetAtom(savedOutputPathAtom);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const rememberOutputFolder = useAtomValue(rememberOutputFolderAtom);
|
||||||
const [showCloudModal, setShowCloudModal] = useState(false);
|
|
||||||
const [minSize, setMinSize] = useState(22);
|
const batchMode = useAtomValue(batchModeAtom);
|
||||||
const [cursorPosition, setCursorPosition] = useState({ x: 0, y: 0 });
|
const [batchFolderPath, setBatchFolderPath] = useState("");
|
||||||
const upscaledImageRef = useRef<HTMLImageElement>(null);
|
const [upscaledBatchFolderPath, setUpscaledBatchFolderPath] = useState("");
|
||||||
const [lensPosition, setLensPosition] = useState({ x: 0, y: 0 });
|
|
||||||
|
const setProgress = useSetAtom(progressAtom);
|
||||||
|
const [doubleUpscaylCounter, setDoubleUpscaylCounter] = useState(0);
|
||||||
|
|
||||||
// ATOMIC STATES
|
|
||||||
const [outputPath, setOutputPath] = useAtom(savedOutputPathAtom);
|
|
||||||
const [compression, setCompression] = useAtom(compressionAtom);
|
|
||||||
const [progress, setProgress] = useAtom(progressAtom);
|
|
||||||
const [batchMode, setBatchMode] = useAtom(batchModeAtom);
|
|
||||||
const [logData, setLogData] = useAtom(logAtom);
|
|
||||||
const [modelOptions, setModelOptions] = useAtom(modelsListAtom);
|
const [modelOptions, setModelOptions] = useAtom(modelsListAtom);
|
||||||
const [scale] = useAtom(scaleAtom);
|
|
||||||
const [dontShowCloudModal, setDontShowCloudModal] = useAtom(
|
|
||||||
dontShowCloudModalAtom,
|
|
||||||
);
|
|
||||||
const noImageProcessing = useAtomValue(noImageProcessingAtom);
|
|
||||||
const [news, setNews] = useAtom(newsAtom);
|
const [news, setNews] = useAtom(newsAtom);
|
||||||
const [showNewsModal, setShowNewsModal] = useAtom(showNewsModalAtom);
|
const [showNewsModal, setShowNewsModal] = useAtom(showNewsModalAtom);
|
||||||
const viewType = useAtomValue(viewTypeAtom);
|
|
||||||
const lensSize = useAtomValue(lensSizeAtom);
|
|
||||||
const rememberOutputFolder = useAtomValue(rememberOutputFolderAtom);
|
|
||||||
const [showSidebar, setShowSidebar] = useAtom(showSidebarAtom);
|
|
||||||
const customWidth = useAtomValue(customWidthAtom);
|
|
||||||
const useCustomWidth = useAtomValue(useCustomWidthAtom);
|
|
||||||
const tileSize = useAtomValue(tileSizeAtom);
|
|
||||||
|
|
||||||
const { logit } = useLog();
|
const selectImageHandler = async () => {
|
||||||
const { toast } = useToast();
|
resetImagePaths();
|
||||||
|
const path = await window.electron.invoke(ELECTRON_COMMANDS.SELECT_FILE);
|
||||||
|
if (path === null) return;
|
||||||
|
logit("🖼 Selected Image Path: ", path);
|
||||||
|
setImagePath(path);
|
||||||
|
const dirname = getDirectoryFromPath(path);
|
||||||
|
logit("📁 Selected Image Directory: ", dirname);
|
||||||
|
if (!FEATURE_FLAGS.APP_STORE_BUILD) {
|
||||||
|
if (!rememberOutputFolder) {
|
||||||
|
setOutputPath(dirname);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
validateImagePath(path);
|
||||||
|
};
|
||||||
|
|
||||||
const sanitizedImagePath = useMemo(
|
const selectFolderHandler = async () => {
|
||||||
() => sanitizePath(imagePath),
|
resetImagePaths();
|
||||||
[imagePath],
|
const path = await window.electron.invoke(ELECTRON_COMMANDS.SELECT_FOLDER);
|
||||||
);
|
if (path !== null) {
|
||||||
|
logit("🖼 Selected Folder Path: ", path);
|
||||||
const sanitizedUpscaledImagePath = useMemo(
|
setBatchFolderPath(path);
|
||||||
() => sanitizePath(upscaledImagePath),
|
if (!rememberOutputFolder) {
|
||||||
[upscaledImagePath],
|
setOutputPath(path);
|
||||||
);
|
}
|
||||||
|
} else {
|
||||||
const handleMouseMoveCompare = (e: React.MouseEvent) => {
|
logit("🚫 Folder selection cancelled");
|
||||||
if (upscaledImageRef.current) {
|
setBatchFolderPath("");
|
||||||
const { left, top, width, height } =
|
if (!rememberOutputFolder) {
|
||||||
upscaledImageRef.current.getBoundingClientRect();
|
setOutputPath("");
|
||||||
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)),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// SET CONFIG VARIABLES ON FIRST RUN
|
const validateImagePath = (path: string) => {
|
||||||
useEffect(() => {
|
if (path.length > 0) {
|
||||||
// UPSCAYL VERSION
|
logit("🖼 imagePath: ", path);
|
||||||
const upscaylVersion = navigator?.userAgent?.match(
|
const extension = path.split(".").pop().toLowerCase();
|
||||||
/Upscayl\/([\d\.]+\d+)/,
|
logit("🔤 Extension: ", extension);
|
||||||
)[1];
|
if (!VALID_IMAGE_FORMATS.includes(extension)) {
|
||||||
setVersion(upscaylVersion);
|
toast({
|
||||||
}, []);
|
title: t("ERRORS.INVALID_IMAGE_ERROR.TITLE"),
|
||||||
|
description: t("ERRORS.INVALID_IMAGE_ERROR.DESCRIPTION"),
|
||||||
|
});
|
||||||
|
resetImagePaths();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resetImagePaths();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// ELECTRON EVENT LISTENERS
|
// ELECTRON EVENT LISTENERS
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -203,25 +167,19 @@ const Home = () => {
|
|||||||
resetImagePaths();
|
resetImagePaths();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// OS
|
|
||||||
window.electron.on(
|
|
||||||
COMMAND.OS,
|
|
||||||
(_, data: "linux" | "mac" | "win" | undefined) => {
|
|
||||||
if (data) {
|
|
||||||
setOs(data);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
// LOG
|
// LOG
|
||||||
window.electron.on(COMMAND.LOG, (_, data: string) => {
|
window.electron.on(ELECTRON_COMMANDS.LOG, (_, data: string) => {
|
||||||
logit(`🎒 BACKEND REPORTED: `, data);
|
logit(`🎒 BACKEND REPORTED: `, data);
|
||||||
});
|
});
|
||||||
// SCALING AND CONVERTING
|
// SCALING AND CONVERTING
|
||||||
window.electron.on(COMMAND.SCALING_AND_CONVERTING, (_, data: string) => {
|
window.electron.on(
|
||||||
|
ELECTRON_COMMANDS.SCALING_AND_CONVERTING,
|
||||||
|
(_, data: string) => {
|
||||||
setProgress(t("APP.PROGRESS.PROCESSING_TITLE"));
|
setProgress(t("APP.PROGRESS.PROCESSING_TITLE"));
|
||||||
});
|
},
|
||||||
|
);
|
||||||
// UPSCAYL ERROR
|
// UPSCAYL ERROR
|
||||||
window.electron.on(COMMAND.UPSCAYL_ERROR, (_, data: string) => {
|
window.electron.on(ELECTRON_COMMANDS.UPSCAYL_ERROR, (_, data: string) => {
|
||||||
toast({
|
toast({
|
||||||
title: t("ERRORS.GENERIC_ERROR.TITLE"),
|
title: t("ERRORS.GENERIC_ERROR.TITLE"),
|
||||||
description: data,
|
description: data,
|
||||||
@ -229,7 +187,9 @@ const Home = () => {
|
|||||||
resetImagePaths();
|
resetImagePaths();
|
||||||
});
|
});
|
||||||
// UPSCAYL PROGRESS
|
// UPSCAYL PROGRESS
|
||||||
window.electron.on(COMMAND.UPSCAYL_PROGRESS, (_, data: string) => {
|
window.electron.on(
|
||||||
|
ELECTRON_COMMANDS.UPSCAYL_PROGRESS,
|
||||||
|
(_, data: string) => {
|
||||||
if (data.length > 0 && data.length < 10) {
|
if (data.length > 0 && data.length < 10) {
|
||||||
setProgress(data);
|
setProgress(data);
|
||||||
} else if (data.includes("converting")) {
|
} else if (data.includes("converting")) {
|
||||||
@ -239,9 +199,12 @@ const Home = () => {
|
|||||||
}
|
}
|
||||||
handleErrors(data);
|
handleErrors(data);
|
||||||
logit(`🚧 UPSCAYL_PROGRESS: `, data);
|
logit(`🚧 UPSCAYL_PROGRESS: `, data);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
// FOLDER UPSCAYL PROGRESS
|
// FOLDER UPSCAYL PROGRESS
|
||||||
window.electron.on(COMMAND.FOLDER_UPSCAYL_PROGRESS, (_, data: string) => {
|
window.electron.on(
|
||||||
|
ELECTRON_COMMANDS.FOLDER_UPSCAYL_PROGRESS,
|
||||||
|
(_, data: string) => {
|
||||||
if (data.includes("Successful")) {
|
if (data.includes("Successful")) {
|
||||||
setProgress(t("APP.PROGRESS.SUCCESS_TITLE"));
|
setProgress(t("APP.PROGRESS.SUCCESS_TITLE"));
|
||||||
}
|
}
|
||||||
@ -250,9 +213,12 @@ const Home = () => {
|
|||||||
}
|
}
|
||||||
handleErrors(data);
|
handleErrors(data);
|
||||||
logit(`🚧 FOLDER_UPSCAYL_PROGRESS: `, data);
|
logit(`🚧 FOLDER_UPSCAYL_PROGRESS: `, data);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
// DOUBLE UPSCAYL PROGRESS
|
// DOUBLE UPSCAYL PROGRESS
|
||||||
window.electron.on(COMMAND.DOUBLE_UPSCAYL_PROGRESS, (_, data: string) => {
|
window.electron.on(
|
||||||
|
ELECTRON_COMMANDS.DOUBLE_UPSCAYL_PROGRESS,
|
||||||
|
(_, data: string) => {
|
||||||
if (data.length > 0 && data.length < 10) {
|
if (data.length > 0 && data.length < 10) {
|
||||||
if (data === "0.00%") {
|
if (data === "0.00%") {
|
||||||
setDoubleUpscaylCounter(doubleUpscaylCounter + 1);
|
setDoubleUpscaylCounter(doubleUpscaylCounter + 1);
|
||||||
@ -261,29 +227,38 @@ const Home = () => {
|
|||||||
}
|
}
|
||||||
handleErrors(data);
|
handleErrors(data);
|
||||||
logit(`🚧 DOUBLE_UPSCAYL_PROGRESS: `, data);
|
logit(`🚧 DOUBLE_UPSCAYL_PROGRESS: `, data);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
// UPSCAYL DONE
|
// UPSCAYL DONE
|
||||||
window.electron.on(COMMAND.UPSCAYL_DONE, (_, data: string) => {
|
window.electron.on(ELECTRON_COMMANDS.UPSCAYL_DONE, (_, data: string) => {
|
||||||
setProgress("");
|
setProgress("");
|
||||||
setUpscaledImagePath(data);
|
setUpscaledImagePath(data);
|
||||||
logit("upscaledImagePath: ", data);
|
logit("upscaledImagePath: ", data);
|
||||||
logit(`💯 UPSCAYL_DONE: `, data);
|
logit(`💯 UPSCAYL_DONE: `, data);
|
||||||
});
|
});
|
||||||
// FOLDER UPSCAYL DONE
|
// FOLDER UPSCAYL DONE
|
||||||
window.electron.on(COMMAND.FOLDER_UPSCAYL_DONE, (_, data: string) => {
|
window.electron.on(
|
||||||
|
ELECTRON_COMMANDS.FOLDER_UPSCAYL_DONE,
|
||||||
|
(_, data: string) => {
|
||||||
setProgress("");
|
setProgress("");
|
||||||
setUpscaledBatchFolderPath(data);
|
setUpscaledBatchFolderPath(data);
|
||||||
logit(`💯 FOLDER_UPSCAYL_DONE: `, data);
|
logit(`💯 FOLDER_UPSCAYL_DONE: `, data);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
// DOUBLE UPSCAYL DONE
|
// DOUBLE UPSCAYL DONE
|
||||||
window.electron.on(COMMAND.DOUBLE_UPSCAYL_DONE, (_, data: string) => {
|
window.electron.on(
|
||||||
|
ELECTRON_COMMANDS.DOUBLE_UPSCAYL_DONE,
|
||||||
|
(_, data: string) => {
|
||||||
setProgress("");
|
setProgress("");
|
||||||
setTimeout(() => setUpscaledImagePath(data), 500);
|
setTimeout(() => setUpscaledImagePath(data), 500);
|
||||||
setDoubleUpscaylCounter(0);
|
setDoubleUpscaylCounter(0);
|
||||||
logit(`💯 DOUBLE_UPSCAYL_DONE: `, data);
|
logit(`💯 DOUBLE_UPSCAYL_DONE: `, data);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
// CUSTOM FOLDER LISTENER
|
// CUSTOM FOLDER LISTENER
|
||||||
window.electron.on(COMMAND.CUSTOM_MODEL_FILES_LIST, (_, data: string[]) => {
|
window.electron.on(
|
||||||
|
ELECTRON_COMMANDS.CUSTOM_MODEL_FILES_LIST,
|
||||||
|
(_, data: string[]) => {
|
||||||
logit(`📜 CUSTOM_MODEL_FILES_LIST: `, data);
|
logit(`📜 CUSTOM_MODEL_FILES_LIST: `, data);
|
||||||
const newModelOptions = data.map((model) => {
|
const newModelOptions = data.map((model) => {
|
||||||
return {
|
return {
|
||||||
@ -292,68 +267,16 @@ const Home = () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
// Add newModelsList to modelOptions and remove duplicates
|
// Add newModelsList to modelOptions and remove duplicates
|
||||||
const combinedModelOptions = [...modelOptions, ...newModelOptions];
|
const modelMap = new Map();
|
||||||
const uniqueModelOptions = combinedModelOptions.filter(
|
[...modelOptions, ...newModelOptions].forEach((model) => {
|
||||||
// Check if any model in the array appears more than once
|
modelMap.set(model.value, model);
|
||||||
(model, index, array) =>
|
});
|
||||||
array.findIndex((t) => t.value === model.value) === index,
|
const uniqueModelOptions = Array.from(modelMap.values());
|
||||||
);
|
|
||||||
setModelOptions(uniqueModelOptions);
|
setModelOptions(uniqueModelOptions);
|
||||||
});
|
},
|
||||||
}, []);
|
|
||||||
|
|
||||||
// FETCH CUSTOM MODELS FROM CUSTOM MODELS PATH
|
|
||||||
useEffect(() => {
|
|
||||||
const customModelsPath = JSON.parse(
|
|
||||||
localStorage.getItem("customModelsPath"),
|
|
||||||
);
|
);
|
||||||
if (customModelsPath !== null) {
|
|
||||||
window.electron.send(COMMAND.GET_MODELS_LIST, customModelsPath);
|
|
||||||
logit("🎯 GET_MODELS_LIST: ", customModelsPath);
|
|
||||||
}
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// FETCH NEWS
|
|
||||||
useEffect(() => {
|
|
||||||
// TODO: ADD AN ABOUT TAB
|
|
||||||
if (window && window.navigator.onLine === false) return;
|
|
||||||
try {
|
|
||||||
fetch("https://raw.githubusercontent.com/upscayl/upscayl/main/news.md", {
|
|
||||||
cache: "no-cache",
|
|
||||||
})
|
|
||||||
.then((res) => {
|
|
||||||
return res.text();
|
|
||||||
})
|
|
||||||
.then((result) => {
|
|
||||||
const newsData = result;
|
|
||||||
if (!newsData) {
|
|
||||||
console.log("📰 Could not fetch news data");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const markdownData = matter(newsData);
|
|
||||||
if (!markdownData) return;
|
|
||||||
if (markdownData && markdownData.data.dontShow) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
markdownData &&
|
|
||||||
news &&
|
|
||||||
markdownData?.data?.version === news?.data?.version
|
|
||||||
) {
|
|
||||||
console.log("📰 News is up to date");
|
|
||||||
if (showNewsModal === false) {
|
|
||||||
setShowNewsModal(false);
|
|
||||||
}
|
|
||||||
} else if (markdownData) {
|
|
||||||
setNews(matter(newsData));
|
|
||||||
setShowNewsModal(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.log("Could not fetch Upscayl News");
|
|
||||||
}
|
|
||||||
}, [news]);
|
|
||||||
|
|
||||||
// LOADING STATE
|
// LOADING STATE
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
@ -373,555 +296,36 @@ const Home = () => {
|
|||||||
setUpscaledBatchFolderPath("");
|
setUpscaledBatchFolderPath("");
|
||||||
};
|
};
|
||||||
|
|
||||||
// UTILS
|
|
||||||
// CHECK IF IMAGE IS VALID
|
|
||||||
const validateImagePath = (path: string) => {
|
|
||||||
if (path.length > 0) {
|
|
||||||
logit("🖼 imagePath: ", path);
|
|
||||||
const extension = path.toLocaleLowerCase().split(".").pop();
|
|
||||||
logit("🔤 Extension: ", extension);
|
|
||||||
if (!allowedFileTypes.includes(extension.toLowerCase())) {
|
|
||||||
toast({
|
|
||||||
title: t("ERRORS.INVALID_IMAGE_ERROR.TITLE"),
|
|
||||||
description: t("ERRORS.INVALID_IMAGE_ERROR.DESCRIPTION"),
|
|
||||||
});
|
|
||||||
resetImagePaths();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
resetImagePaths();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// HANDLERS
|
|
||||||
const handleMouseMove = useCallback((e: any) => {
|
|
||||||
const { left, top, width, height } = e.target.getBoundingClientRect();
|
|
||||||
const x = ((e.pageX - left) / width) * 100;
|
|
||||||
const y = ((e.pageY - top) / height) * 100;
|
|
||||||
setBackgroundPosition(`${x}% ${y}%`);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const selectImageHandler = async () => {
|
|
||||||
resetImagePaths();
|
|
||||||
var path = await window.electron.invoke(COMMAND.SELECT_FILE);
|
|
||||||
if (path === null) return;
|
|
||||||
logit("🖼 Selected Image Path: ", path);
|
|
||||||
setImagePath(path);
|
|
||||||
var dirname = getDirectoryFromPath(path);
|
|
||||||
logit("📁 Selected Image Directory: ", dirname);
|
|
||||||
if (!featureFlags.APP_STORE_BUILD) {
|
|
||||||
if (!rememberOutputFolder) {
|
|
||||||
setOutputPath(dirname);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
validateImagePath(path);
|
|
||||||
};
|
|
||||||
|
|
||||||
const selectFolderHandler = async () => {
|
|
||||||
resetImagePaths();
|
|
||||||
var path = await window.electron.invoke(COMMAND.SELECT_FOLDER);
|
|
||||||
if (path !== null) {
|
|
||||||
logit("🖼 Selected Folder Path: ", path);
|
|
||||||
setBatchFolderPath(path);
|
|
||||||
if (!rememberOutputFolder) {
|
|
||||||
setOutputPath(path);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logit("🚫 Folder selection cancelled");
|
|
||||||
setBatchFolderPath("");
|
|
||||||
if (!rememberOutputFolder) {
|
|
||||||
setOutputPath("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleModelChange = (e: any) => {
|
|
||||||
setModel(e.value);
|
|
||||||
logit("🔀 Model changed: ", e.value);
|
|
||||||
localStorage.setItem(
|
|
||||||
"model",
|
|
||||||
JSON.stringify({ label: e.label, value: e.value }),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// DRAG AND DROP HANDLERS
|
|
||||||
const handleDragEnter = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
console.log("drag enter");
|
|
||||||
};
|
|
||||||
const handleDragLeave = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
console.log("drag leave");
|
|
||||||
};
|
|
||||||
const handleDragOver = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
console.log("drag over");
|
|
||||||
};
|
|
||||||
const openFolderHandler = (e) => {
|
|
||||||
logit("📂 OPEN_FOLDER: ", upscaledBatchFolderPath);
|
|
||||||
window.electron.send(COMMAND.OPEN_FOLDER, upscaledBatchFolderPath);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDrop = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
resetImagePaths();
|
|
||||||
if (
|
|
||||||
e.dataTransfer.items.length === 0 ||
|
|
||||||
e.dataTransfer.files.length === 0
|
|
||||||
) {
|
|
||||||
logit("👎 No valid files dropped");
|
|
||||||
toast({
|
|
||||||
title: t("ERRORS.INVALID_IMAGE_ERROR.TITLE"),
|
|
||||||
description: t("ERRORS.INVALID_IMAGE_ERROR.ADDITIONAL_DESCRIPTION"),
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const type = e.dataTransfer.items[0].type;
|
|
||||||
const filePath = e.dataTransfer.files[0].path;
|
|
||||||
const extension = e.dataTransfer.files[0].name.split(".").at(-1);
|
|
||||||
logit("⤵️ Dropped file: ", JSON.stringify({ type, filePath, extension }));
|
|
||||||
if (
|
|
||||||
!type.includes("image") ||
|
|
||||||
!allowedFileTypes.includes(extension.toLowerCase())
|
|
||||||
) {
|
|
||||||
logit("🚫 Invalid file dropped");
|
|
||||||
toast({
|
|
||||||
title: t("ERRORS.INVALID_IMAGE_ERROR.TITLE"),
|
|
||||||
description: t("ERRORS.INVALID_IMAGE_ERROR.ADDITIONAL_DESCRIPTION"),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
logit("🖼 Setting image path: ", filePath);
|
|
||||||
setImagePath(filePath);
|
|
||||||
var dirname = getDirectoryFromPath(filePath);
|
|
||||||
logit("🗂 Setting output path: ", dirname);
|
|
||||||
if (!featureFlags.APP_STORE_BUILD) {
|
|
||||||
if (!rememberOutputFolder) {
|
|
||||||
setOutputPath(dirname);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
validateImagePath(filePath);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlePaste = (e) => {
|
|
||||||
resetImagePaths();
|
|
||||||
e.preventDefault();
|
|
||||||
const type = e.clipboardData.items[0].type;
|
|
||||||
const filePath = e.clipboardData.files[0].path;
|
|
||||||
const extension = e.clipboardData.files[0].name.split(".").at(-1);
|
|
||||||
logit("📋 Pasted file: ", JSON.stringify({ type, filePath, extension }));
|
|
||||||
if (
|
|
||||||
!type.includes("image") &&
|
|
||||||
!allowedFileTypes.includes(extension.toLowerCase())
|
|
||||||
) {
|
|
||||||
toast({
|
|
||||||
title: t("ERRORS.INVALID_IMAGE_ERROR.TITLE"),
|
|
||||||
description: t("ERRORS.INVALID_IMAGE_ERROR.ADDITIONAL_DESCRIPTION"),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setImagePath(filePath);
|
|
||||||
var dirname = getDirectoryFromPath(filePath);
|
|
||||||
logit("🗂 Setting output path: ", dirname);
|
|
||||||
if (!rememberOutputFolder) {
|
|
||||||
setOutputPath(dirname);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const upscaylHandler = async () => {
|
|
||||||
logit("🔄 Resetting Upscaled Image Path");
|
|
||||||
setUpscaledImagePath("");
|
|
||||||
setUpscaledBatchFolderPath("");
|
|
||||||
if (imagePath !== "" || batchFolderPath !== "") {
|
|
||||||
setProgress(t("APP.PROGRESS.WAIT_TITLE"));
|
|
||||||
// Double Upscayl
|
|
||||||
if (doubleUpscayl) {
|
|
||||||
window.electron.send<DoubleUpscaylPayload>(COMMAND.DOUBLE_UPSCAYL, {
|
|
||||||
imagePath,
|
|
||||||
outputPath,
|
|
||||||
model,
|
|
||||||
gpuId: gpuId.length === 0 ? null : gpuId,
|
|
||||||
saveImageAs,
|
|
||||||
scale,
|
|
||||||
noImageProcessing,
|
|
||||||
compression: compression.toString(),
|
|
||||||
customWidth: customWidth > 0 ? customWidth.toString() : null,
|
|
||||||
useCustomWidth,
|
|
||||||
tileSize,
|
|
||||||
});
|
|
||||||
logit("🏁 DOUBLE_UPSCAYL");
|
|
||||||
} else if (batchMode) {
|
|
||||||
// Batch Upscayl
|
|
||||||
setDoubleUpscayl(false);
|
|
||||||
window.electron.send<BatchUpscaylPayload>(COMMAND.FOLDER_UPSCAYL, {
|
|
||||||
batchFolderPath,
|
|
||||||
outputPath,
|
|
||||||
model,
|
|
||||||
gpuId: gpuId.length === 0 ? null : gpuId,
|
|
||||||
saveImageAs,
|
|
||||||
scale,
|
|
||||||
noImageProcessing,
|
|
||||||
compression: compression.toString(),
|
|
||||||
customWidth: customWidth > 0 ? customWidth.toString() : null,
|
|
||||||
useCustomWidth,
|
|
||||||
tileSize,
|
|
||||||
});
|
|
||||||
logit("🏁 FOLDER_UPSCAYL");
|
|
||||||
} else {
|
|
||||||
// Single Image Upscayl
|
|
||||||
window.electron.send<ImageUpscaylPayload>(COMMAND.UPSCAYL, {
|
|
||||||
imagePath,
|
|
||||||
outputPath,
|
|
||||||
model,
|
|
||||||
gpuId: gpuId.length === 0 ? null : gpuId,
|
|
||||||
saveImageAs,
|
|
||||||
scale,
|
|
||||||
overwrite,
|
|
||||||
noImageProcessing,
|
|
||||||
compression: compression.toString(),
|
|
||||||
customWidth: customWidth > 0 ? customWidth.toString() : null,
|
|
||||||
useCustomWidth,
|
|
||||||
tileSize,
|
|
||||||
});
|
|
||||||
logit("🏁 UPSCAYL");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
toast({
|
|
||||||
title: t("ERRORS.NO_IMAGE_ERROR.TITLE"),
|
|
||||||
description: t("ERRORS.NO_IMAGE_ERROR.DESCRIPTION"),
|
|
||||||
});
|
|
||||||
logit("🚫 No valid image selected");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const stopHandler = () => {
|
|
||||||
window.electron.send(COMMAND.STOP);
|
|
||||||
logit("🛑 Stopping Upscayl");
|
|
||||||
resetImagePaths();
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<Logo className="absolute left-1/2 top-1/2 w-36 -translate-x-1/2 -translate-y-1/2 animate-pulse" />
|
<UpscaylSVGLogo className="absolute left-1/2 top-1/2 w-36 -translate-x-1/2 -translate-y-1/2 animate-pulse" />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen w-screen flex-row overflow-hidden bg-base-300">
|
<div className="flex h-screen w-screen flex-row overflow-hidden bg-base-300">
|
||||||
{/* TOP LOGO WHEN SIDEBAR IS HIDDEN */}
|
<Sidebar
|
||||||
{!showSidebar && (
|
imagePath={imagePath}
|
||||||
<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 ">
|
dimensions={dimensions}
|
||||||
<Logo className="w-5" />
|
setUpscaledImagePath={setUpscaledImagePath}
|
||||||
{t("TITLE")}
|
batchFolderPath={batchFolderPath}
|
||||||
</div>
|
setUpscaledBatchFolderPath={setUpscaledBatchFolderPath}
|
||||||
)}
|
|
||||||
|
|
||||||
{/* SIDEBAR BUTTON */}
|
|
||||||
<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>
|
|
||||||
|
|
||||||
{/* LEFT PANE */}
|
|
||||||
<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>
|
|
||||||
|
|
||||||
{/* UPSCAYL CLOUD MODAL */}
|
|
||||||
{featureFlags.SHOW_UPSCAYL_CLOUD_INFO && (
|
|
||||||
<UpscaylCloudModal
|
|
||||||
show={showCloudModal}
|
|
||||||
setShow={setShowCloudModal}
|
|
||||||
setDontShowCloudModal={setDontShowCloudModal}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{/* MACOS TITLEBAR */}
|
|
||||||
{window.electron.platform === "mac" && (
|
|
||||||
<div className="mac-titlebar pt-8"></div>
|
|
||||||
)}
|
|
||||||
{/* HEADER */}
|
|
||||||
<Header version={version} />
|
|
||||||
{!dontShowCloudModal && featureFlags.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"
|
|
||||||
onClick={() => {
|
|
||||||
setShowCloudModal(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("INTRO")}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* NEWS DIALOG */}
|
|
||||||
<NewsModal
|
|
||||||
show={showNewsModal}
|
|
||||||
setShow={(val: boolean) => {
|
|
||||||
setShowNewsModal(val);
|
|
||||||
setNews((prev) => ({ ...prev, seen: true }));
|
|
||||||
}}
|
|
||||||
news={news}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Tabs selectedTab={selectedTab} setSelectedTab={setSelectedTab} />
|
|
||||||
|
|
||||||
{selectedTab === 0 && (
|
|
||||||
<LeftPaneImageSteps
|
|
||||||
selectImageHandler={selectImageHandler}
|
selectImageHandler={selectImageHandler}
|
||||||
selectFolderHandler={selectFolderHandler}
|
selectFolderHandler={selectFolderHandler}
|
||||||
handleModelChange={handleModelChange}
|
/>
|
||||||
upscaylHandler={upscaylHandler}
|
<MainContent
|
||||||
batchMode={batchMode}
|
|
||||||
setBatchMode={setBatchMode}
|
|
||||||
imagePath={imagePath}
|
imagePath={imagePath}
|
||||||
doubleUpscayl={doubleUpscayl}
|
resetImagePaths={resetImagePaths}
|
||||||
setDoubleUpscayl={setDoubleUpscayl}
|
upscaledBatchFolderPath={upscaledBatchFolderPath}
|
||||||
dimensions={dimensions}
|
setImagePath={setImagePath}
|
||||||
setGpuId={setGpuId}
|
validateImagePath={validateImagePath}
|
||||||
model={model}
|
selectFolderHandler={selectFolderHandler}
|
||||||
setModel={setModel}
|
selectImageHandler={selectImageHandler}
|
||||||
setSaveImageAs={setSaveImageAs}
|
batchFolderPath={batchFolderPath}
|
||||||
/>
|
upscaledImagePath={upscaledImagePath}
|
||||||
)}
|
|
||||||
|
|
||||||
{selectedTab === 1 && (
|
|
||||||
<SettingsTab
|
|
||||||
batchMode={batchMode}
|
|
||||||
setModel={setModel}
|
|
||||||
compression={compression}
|
|
||||||
setCompression={setCompression}
|
|
||||||
gpuId={gpuId}
|
|
||||||
setGpuId={setGpuId}
|
|
||||||
saveImageAs={saveImageAs}
|
|
||||||
setSaveImageAs={setSaveImageAs}
|
|
||||||
logData={logData}
|
|
||||||
os={os}
|
|
||||||
show={showCloudModal}
|
|
||||||
setShow={setShowCloudModal}
|
|
||||||
setDontShowCloudModal={setDontShowCloudModal}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{/* )} */}
|
|
||||||
<Footer />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* RIGHT PANE */}
|
|
||||||
<div
|
|
||||||
className="relative flex h-screen w-full flex-col items-center justify-center"
|
|
||||||
onDrop={(e) => handleDrop(e)}
|
|
||||||
onDragOver={(e) => handleDragOver(e)}
|
|
||||||
onDragEnter={(e) => handleDragEnter(e)}
|
|
||||||
onDragLeave={(e) => handleDragLeave(e)}
|
|
||||||
onDoubleClick={() => {
|
|
||||||
if (batchMode) {
|
|
||||||
selectFolderHandler();
|
|
||||||
} else {
|
|
||||||
selectImageHandler();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onPaste={(e) => handlePaste(e)}
|
|
||||||
>
|
|
||||||
{window.electron.platform === "mac" && (
|
|
||||||
<div className="mac-titlebar absolute top-0 h-8 w-full"></div>
|
|
||||||
)}
|
|
||||||
{progress.length > 0 &&
|
|
||||||
upscaledImagePath.length === 0 &&
|
|
||||||
upscaledBatchFolderPath.length === 0 ? (
|
|
||||||
<ProgressBar
|
|
||||||
batchMode={batchMode}
|
|
||||||
progress={progress}
|
|
||||||
doubleUpscaylCounter={doubleUpscaylCounter}
|
doubleUpscaylCounter={doubleUpscaylCounter}
|
||||||
stopHandler={stopHandler}
|
setDimensions={setDimensions}
|
||||||
/>
|
/>
|
||||||
) : null}
|
|
||||||
{/* DEFAULT PANE INFO */}
|
|
||||||
{((!batchMode &&
|
|
||||||
imagePath.length === 0 &&
|
|
||||||
upscaledImagePath.length === 0) ||
|
|
||||||
(batchMode &&
|
|
||||||
batchFolderPath.length === 0 &&
|
|
||||||
upscaledBatchFolderPath.length === 0)) && (
|
|
||||||
<RightPaneInfo version={version} batchMode={batchMode} />
|
|
||||||
)}
|
|
||||||
{/* SHOW SELECTED IMAGE */}
|
|
||||||
{!batchMode &&
|
|
||||||
upscaledImagePath.length === 0 &&
|
|
||||||
imagePath.length > 0 && (
|
|
||||||
<>
|
|
||||||
<ImageOptions
|
|
||||||
zoomAmount={zoomAmount}
|
|
||||||
setZoomAmount={setZoomAmount}
|
|
||||||
resetImagePaths={resetImagePaths}
|
|
||||||
hideZoomOptions={true}
|
|
||||||
/>
|
|
||||||
<img
|
|
||||||
src={"file:///" + sanitizePath(imagePath)}
|
|
||||||
onLoad={(e: any) => {
|
|
||||||
setDimensions({
|
|
||||||
width: e.target.naturalWidth,
|
|
||||||
height: e.target.naturalHeight,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
draggable="false"
|
|
||||||
alt=""
|
|
||||||
className="h-full w-full bg-gradient-to-br from-base-300 to-base-100 object-contain"
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{/* BATCH UPSCALE SHOW SELECTED FOLDER */}
|
|
||||||
{batchMode &&
|
|
||||||
upscaledBatchFolderPath.length === 0 &&
|
|
||||||
batchFolderPath.length > 0 && (
|
|
||||||
<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>
|
|
||||||
)}
|
|
||||||
<ImageOptions
|
|
||||||
zoomAmount={zoomAmount}
|
|
||||||
setZoomAmount={setZoomAmount}
|
|
||||||
resetImagePaths={resetImagePaths}
|
|
||||||
/>
|
|
||||||
{!batchMode &&
|
|
||||||
viewType === "lens" &&
|
|
||||||
upscaledImagePath &&
|
|
||||||
imagePath && (
|
|
||||||
<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">
|
|
||||||
<div className="h-full w-full overflow-hidden">
|
|
||||||
<img
|
|
||||||
src={"file:///" + sanitizedImagePath}
|
|
||||||
alt="Original"
|
|
||||||
className="h-full w-full"
|
|
||||||
style={{
|
|
||||||
objectFit: "contain",
|
|
||||||
objectPosition: `${-lensPosition.x}px ${-lensPosition.y}px`,
|
|
||||||
transform: `scale(${parseInt(zoomAmount) / 100})`,
|
|
||||||
transformOrigin: "top left",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="h-full w-full overflow-hidden">
|
|
||||||
<img
|
|
||||||
src={"file:///" + sanitizedUpscaledImagePath}
|
|
||||||
alt="Upscaled"
|
|
||||||
className="h-full w-full"
|
|
||||||
style={{
|
|
||||||
objectFit: "contain",
|
|
||||||
objectPosition: `${-lensPosition.x}px ${-lensPosition.y}px`,
|
|
||||||
transform: `scale(${parseInt(zoomAmount) / 100})`,
|
|
||||||
transformOrigin: "top left",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</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>
|
|
||||||
)}
|
|
||||||
{/* COMPARISON SLIDER */}
|
|
||||||
{!batchMode &&
|
|
||||||
viewType === "slider" &&
|
|
||||||
imagePath.length > 0 &&
|
|
||||||
upscaledImagePath.length > 0 && (
|
|
||||||
<>
|
|
||||||
<ReactCompareSlider
|
|
||||||
itemOne={
|
|
||||||
<>
|
|
||||||
<p className="absolute bottom-1 left-1 rounded-md bg-black p-1 text-sm font-medium text-white opacity-30">
|
|
||||||
{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"
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
1
renderer/renderer.d.ts
vendored
1
renderer/renderer.d.ts
vendored
@ -2,6 +2,7 @@ import { IpcRenderer } from "electron";
|
|||||||
|
|
||||||
export interface IElectronAPI {
|
export interface IElectronAPI {
|
||||||
on: (command, func?) => IpcRenderer;
|
on: (command, func?) => IpcRenderer;
|
||||||
|
off: (command, func?) => IpcRenderer;
|
||||||
send: <T>(command, func?: T) => IpcRenderer;
|
send: <T>(command, func?: T) => IpcRenderer;
|
||||||
invoke: (command, func?) => any;
|
invoke: (command, func?) => any;
|
||||||
platform: "mac" | "win" | "linux";
|
platform: "mac" | "win" | "linux";
|
||||||
|
@ -11,7 +11,8 @@
|
|||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"@electron/*": ["./electron/*"],
|
"@electron/*": ["./electron/*"],
|
||||||
"@/*": ["./renderer/*"]
|
"@/*": ["./renderer/*"],
|
||||||
|
"@common/*": ["./common/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["./electron/**/*", "./common/**/*", "./renderer/**/*"],
|
"include": ["./electron/**/*", "./common/**/*", "./renderer/**/*"],
|
||||||
|
Loading…
Reference in New Issue
Block a user