1
0
mirror of https://github.com/upscayl/upscayl.git synced 2025-02-15 02:12:34 +01:00

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
This commit is contained in:
Nayam Amarshe 2024-09-25 07:32:56 +05:30
parent 5e5f60728b
commit b39d23c2ff
35 changed files with 506 additions and 382 deletions

View File

@ -11,11 +11,40 @@ 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";
const translations: Record<Locales, Translations> = {
en,
ru,
ja,
zh,
es,
fr,
};
// Create a type for nested key paths
type NestedKeyOf<Object> = Object extends object
? {
[Key in keyof Object]: Key extends string | number
? Key | `${Key}.${NestedKeyOf<Object[Key]>}`
: never;
}[keyof Object]
: never;
// Utility function to access nested translation keys // Utility function to access nested translation keys
const getNestedTranslation = (obj: Translations, key: string): string => { const getNestedTranslation = (
return ( obj: Translations,
key.split(".").reduce((acc, part) => acc && (acc as any)[part], obj) || key 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 // Atom to store the current locale
@ -24,16 +53,11 @@ export const localeAtom = atomWithStorage<Locales>("language", "en");
// Atom to get the translation function based on the current locale // Atom to get the translation function based on the current locale
export const translationAtom = atom((get) => { export const translationAtom = atom((get) => {
const locale = get(localeAtom); const locale = get(localeAtom);
const translations: Record<Locales, Translations> = {
en,
ru,
ja,
zh,
es,
fr,
};
return (key: string, params: Record<string, string> = {}): string => { return (
key: NestedKeyOf<Translations>,
params: Record<string, string> = {},
): string => {
const template = getNestedTranslation(translations[locale], key); const template = getNestedTranslation(translations[locale], key);
// Replace placeholders with parameters, e.g., {name} => John // Replace placeholders with parameters, e.g., {name} => John

View File

@ -1,6 +1,6 @@
import { FEATURE_FLAGS } 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 "./icons/Logo";
import { useAtomValue } from "jotai"; import { useAtomValue } from "jotai";
import { translationAtom } from "@/atoms/translations-atom"; import { translationAtom } from "@/atoms/translations-atom";
@ -16,7 +16,7 @@ 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")}{" "}

View File

@ -1,27 +1,66 @@
import { newsAtom, showNewsModalAtom } from "@/atoms/newsAtom";
import { translationAtom } from "@/atoms/translations-atom"; import { translationAtom } from "@/atoms/translations-atom";
import { GrayMatterFile } from "gray-matter"; import matter, { GrayMatterFile } from "gray-matter";
import { useAtomValue } from "jotai"; import { useAtom, useAtomValue } from "jotai";
import React from "react"; import React, { useEffect } from "react";
import Markdown from "react-markdown"; import Markdown from "react-markdown";
import remarkGfm from "remark-gfm"; import remarkGfm from "remark-gfm";
export const NewsModal = ({ export const NewsModal = () => {
show,
setShow,
news,
}: {
show: boolean;
setShow: React.Dispatch<React.SetStateAction<boolean>>;
news: GrayMatterFile<string>;
}) => {
const t = useAtomValue(translationAtom); 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 ( return (
<dialog className={`modal ${show && "modal-open"}`}> <dialog className={`modal ${showNewsModal && "modal-open"}`}>
<div className="modal-box flex flex-col items-center gap-4 text-center"> <div className="modal-box flex flex-col items-center gap-4 text-center">
<button <button
className="btn btn-circle absolute right-4 top-2" className="btn btn-circle absolute right-4 top-2"
onClick={() => setShow(false)} onClick={() => {
setShowNewsModal(false);
setNews((prev) => ({ ...prev, seen: true }));
}}
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@ -57,7 +96,12 @@ export const NewsModal = ({
</div> </div>
<form method="dialog" className="modal-backdrop"> <form method="dialog" className="modal-backdrop">
<button onClick={() => setShow(false)}> <button
onClick={() => {
setShowNewsModal(false);
setNews((prev) => ({ ...prev, seen: true }));
}}
>
{t("APP.DIALOG_BOX.CLOSE")} {t("APP.DIALOG_BOX.CLOSE")}
</button> </button>
</form> </form>

View File

@ -1,9 +1,9 @@
import ELECTRON_COMMANDS from "@common/commands"; import ELECTRON_COMMANDS from "@common/commands";
import { useEffect } from "react"; import { useEffect } from "react";
import useLog from "./useLog"; import useLogger from "./use-logger";
export const initCustomModels = () => { export const initCustomModels = () => {
const { logit } = useLog(); const logit = useLogger();
useEffect(() => { useEffect(() => {
const customModelsPath = JSON.parse( const customModelsPath = JSON.parse(

View File

@ -3,7 +3,7 @@ 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;

View 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;

View File

@ -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;

View File

@ -0,0 +1,28 @@
import React from "react";
import ImageViewSettings from "../upscayl-tab/view/ImageOptions";
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: 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"
/>
);
};
export default ImageViewer;

View File

@ -1,5 +1,5 @@
import useLog from "../hooks/useLog"; import useLogger from "../hooks/use-logger";
import { useState, useCallback, useMemo, useRef } from "react"; import { useState, useMemo } from "react";
import ELECTRON_COMMANDS from "../../../common/commands"; import ELECTRON_COMMANDS from "../../../common/commands";
import { useAtomValue, useSetAtom } from "jotai"; import { useAtomValue, useSetAtom } from "jotai";
import { import {
@ -12,15 +12,18 @@ import {
} from "../../atoms/userSettingsAtom"; } from "../../atoms/userSettingsAtom";
import { useToast } from "@/components/ui/use-toast"; import { useToast } from "@/components/ui/use-toast";
import { sanitizePath } from "@common/sanitize-path"; import { sanitizePath } from "@common/sanitize-path";
import { translationAtom } from "@/atoms/translations-atom";
import getDirectoryFromPath from "@common/get-directory-from-path"; import getDirectoryFromPath from "@common/get-directory-from-path";
import { FEATURE_FLAGS } from "@common/feature-flags"; import { FEATURE_FLAGS } from "@common/feature-flags";
import { VALID_IMAGE_FORMATS } from "@/lib/valid-formats"; import { VALID_IMAGE_FORMATS } from "@/lib/valid-formats";
import ProgressBar from "../upscayl-tab/view/ProgressBar"; import ProgressBar from "./progress-bar";
import RightPaneInfo from "../upscayl-tab/view/RightPaneInfo"; import InformationCard from "../upscayl-tab/view/RightPaneInfo";
import ImageOptions from "../upscayl-tab/view/ImageOptions"; import ImageViewSettings from "../upscayl-tab/view/ImageOptions";
import { ReactCompareSlider } from "react-compare-slider";
import useUpscaylVersion from "../hooks/use-upscayl-version"; 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";
("use client"); ("use client");
const MainContent = ({ const MainContent = ({
@ -53,16 +56,11 @@ const MainContent = ({
}> }>
>; >;
}) => { }) => {
const t = useAtomValue(translationAtom); const t = useTranslation();
const { logit } = useLog(); const logit = useLogger();
const { toast } = useToast(); const { toast } = useToast();
const version = useUpscaylVersion(); const version = useUpscaylVersion();
const upscaledImageRef = useRef<HTMLImageElement>(null);
const [backgroundPosition, setBackgroundPosition] = useState("0% 0%");
const [lensPosition, setLensPosition] = useState({ x: 0, y: 0 });
const setOutputPath = useSetAtom(savedOutputPathAtom); const setOutputPath = useSetAtom(savedOutputPathAtom);
const progress = useAtomValue(progressAtom); const progress = useAtomValue(progressAtom);
const batchMode = useAtomValue(batchModeAtom); const batchMode = useAtomValue(batchModeAtom);
@ -77,6 +75,22 @@ const MainContent = ({
[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 // DRAG AND DROP HANDLERS
const handleDragEnter = (e) => { const handleDragEnter = (e) => {
e.preventDefault(); e.preventDefault();
@ -92,7 +106,7 @@ const MainContent = ({
}; };
const openFolderHandler = (e) => { const openFolderHandler = (e) => {
const { logit } = useLog(); const logit = useLogger();
logit("📂 OPEN_FOLDER: ", upscaledBatchFolderPath); logit("📂 OPEN_FOLDER: ", upscaledBatchFolderPath);
window.electron.send( window.electron.send(
ELECTRON_COMMANDS.OPEN_FOLDER, ELECTRON_COMMANDS.OPEN_FOLDER,
@ -146,7 +160,7 @@ const MainContent = ({
} }
}; };
const handlePaste = (e) => { const handlePaste = (e: React.ClipboardEvent<HTMLDivElement>) => {
resetImagePaths(); resetImagePaths();
e.preventDefault(); e.preventDefault();
const type = e.clipboardData.items[0].type; const type = e.clipboardData.items[0].type;
@ -171,93 +185,48 @@ const MainContent = ({
} }
}; };
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 handleMouseMoveCompare = (e: React.MouseEvent) => {
if (upscaledImageRef.current) {
const { left, top, width, height } =
upscaledImageRef.current.getBoundingClientRect();
const x = e.clientX - left;
const y = e.clientY - top;
setLensPosition({
x: Math.max(0, Math.min(x - lensSize, width - lensSize * 2)),
y: Math.max(0, Math.min(y - lensSize / 2, height - lensSize)),
});
}
};
const stopHandler = () => {
window.electron.send(ELECTRON_COMMANDS.STOP);
logit("🛑 Stopping Upscayl");
resetImagePaths();
};
return ( return (
<div <div
className="relative flex h-screen w-full flex-col items-center justify-center" className="relative flex h-screen w-full flex-col items-center justify-center"
onDrop={(e) => handleDrop(e)} onDrop={handleDrop}
onDragOver={(e) => handleDragOver(e)} onDragOver={handleDragOver}
onDragEnter={(e) => handleDragEnter(e)} onDragEnter={handleDragEnter}
onDragLeave={(e) => handleDragLeave(e)} onDragLeave={handleDragLeave}
onDoubleClick={() => { onDoubleClick={batchMode ? selectFolderHandler : selectImageHandler}
if (batchMode) { onPaste={handlePaste}
selectFolderHandler();
} else {
selectImageHandler();
}
}}
onPaste={(e) => handlePaste(e)}
> >
{window.electron.platform === "mac" && ( <MacTitlebarDragRegion />
<div className="mac-titlebar absolute top-0 h-8 w-full"></div>
)}
{progress.length > 0 && {progress.length > 0 &&
upscaledImagePath.length === 0 && upscaledImagePath.length === 0 &&
upscaledBatchFolderPath.length === 0 ? ( upscaledBatchFolderPath.length === 0 && (
<ProgressBar <ProgressBar
batchMode={batchMode} batchMode={batchMode}
progress={progress} progress={progress}
doubleUpscaylCounter={doubleUpscaylCounter} doubleUpscaylCounter={doubleUpscaylCounter}
stopHandler={stopHandler} resetImagePaths={resetImagePaths}
/> />
) : null} )}
{/* DEFAULT PANE INFO */} {/* DEFAULT PANE INFO */}
{((!batchMode && {showInformationCard && (
imagePath.length === 0 && <InformationCard version={version} batchMode={batchMode} />
upscaledImagePath.length === 0) ||
(batchMode &&
batchFolderPath.length === 0 &&
upscaledBatchFolderPath.length === 0)) && (
<RightPaneInfo version={version} batchMode={batchMode} />
)} )}
<ImageViewSettings
zoomAmount={zoomAmount}
setZoomAmount={setZoomAmount}
resetImagePaths={resetImagePaths}
hideZoomOptions={
!batchMode && upscaledImagePath.length === 0 && imagePath.length > 0
}
/>
{/* SHOW SELECTED IMAGE */} {/* SHOW SELECTED IMAGE */}
{!batchMode && upscaledImagePath.length === 0 && imagePath.length > 0 && ( {!batchMode && upscaledImagePath.length === 0 && imagePath.length > 0 && (
<> <ImageViewer imagePath={imagePath} setDimensions={setDimensions} />
<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 */} {/* BATCH UPSCALE SHOW SELECTED FOLDER */}
{batchMode && {batchMode &&
upscaledBatchFolderPath.length === 0 && upscaledBatchFolderPath.length === 0 &&
@ -270,6 +239,7 @@ const MainContent = ({
</p> </p>
)} )}
{/* BATCH UPSCALE DONE INFO */} {/* BATCH UPSCALE DONE INFO */}
{batchMode && upscaledBatchFolderPath.length > 0 && ( {batchMode && upscaledBatchFolderPath.length > 0 && (
<div className="z-50 flex flex-col items-center"> <div className="z-50 flex flex-col items-center">
<p className="select-none py-4 font-bold text-base-content"> <p className="select-none py-4 font-bold text-base-content">
@ -283,121 +253,26 @@ const MainContent = ({
</button> </button>
</div> </div>
)} )}
<ImageOptions
zoomAmount={zoomAmount}
setZoomAmount={setZoomAmount}
resetImagePaths={resetImagePaths}
/>
{!batchMode && viewType === "lens" && upscaledImagePath && imagePath && ( {!batchMode && viewType === "lens" && upscaledImagePath && imagePath && (
<div <LensViewer
className="group relative h-full w-full overflow-hidden" zoomAmount={zoomAmount}
onMouseMove={handleMouseMoveCompare} lensSize={lensSize}
> sanitizedImagePath={sanitizedImagePath}
{/* UPSCALED IMAGE */} sanitizedUpscaledImagePath={sanitizedUpscaledImagePath}
<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 */} {/* COMPARISON SLIDER */}
{!batchMode && {!batchMode &&
viewType === "slider" && viewType === "slider" &&
imagePath.length > 0 && imagePath.length > 0 &&
upscaledImagePath.length > 0 && ( upscaledImagePath.length > 0 && (
<> <SliderView
<ReactCompareSlider sanitizedImagePath={sanitizedImagePath}
itemOne={ sanitizedUpscaledImagePath={sanitizedUpscaledImagePath}
<> zoomAmount={zoomAmount}
<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>
); );

View File

@ -0,0 +1,92 @@
import React, { useRef, useState } from "react";
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">
<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>
);
};
export default LensViewer;

View File

@ -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;

View File

@ -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/Logo";
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/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>

View 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;

View File

@ -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/userSettingsAtom";
import { modelsListAtom } from "../../atoms/modelsListAtom"; import { modelsListAtom } from "../../atoms/modelsListAtom";
import useLog from "../hooks/useLog"; import useLogger from "../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 "../UpscaylCloudModal";
import { ResetSettings } from "./ResetSettings"; import { ResetSettingsButton } from "./reset-settings-button";
import { FEATURE_FLAGS } 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: ",
@ -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,7 +240,7 @@ function SettingsTab({
/> />
{/* RESET SETTINGS */} {/* RESET SETTINGS */}
<ResetSettings /> <ResetSettingsButton />
{FEATURE_FLAGS.SHOW_UPSCAYL_CLOUD_INFO && ( {FEATURE_FLAGS.SHOW_UPSCAYL_CLOUD_INFO && (
<> <>

View File

@ -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) {

View File

@ -3,7 +3,7 @@ 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);

View File

@ -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 (

View File

@ -3,7 +3,7 @@ import { tileSizeAtom } from "@/atoms/userSettingsAtom";
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);

View File

@ -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">

View File

@ -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,

View File

@ -8,7 +8,7 @@ type ImageScaleSelectProps = {
hideInfo?: boolean; hideInfo?: boolean;
}; };
export function ImageScaleSelect({ export function SelectImageScale({
scale, scale,
setScale, setScale,
hideInfo, hideInfo,

View File

@ -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" },

View File

@ -15,27 +15,26 @@ import {
tileSizeAtom, tileSizeAtom,
showSidebarAtom, showSidebarAtom,
} from "../../atoms/userSettingsAtom"; } from "../../atoms/userSettingsAtom";
import useLog from "../../components/hooks/useLog"; import useLogger from "../hooks/use-logger";
import { import {
BatchUpscaylPayload, BatchUpscaylPayload,
DoubleUpscaylPayload, DoubleUpscaylPayload,
ImageUpscaylPayload, ImageUpscaylPayload,
} from "@common/types/types"; } from "@common/types/types";
import { newsAtom, showNewsModalAtom } from "@/atoms/newsAtom";
import { useToast } from "@/components/ui/use-toast"; import { useToast } from "@/components/ui/use-toast";
import Logo from "@/components/icons/Logo";
import { translationAtom } from "@/atoms/translations-atom";
import LeftPaneImageSteps from "../upscayl-tab/config/LeftPaneImageSteps"; import LeftPaneImageSteps from "../upscayl-tab/config/LeftPaneImageSteps";
import SettingsTab from "../settings-tab"; import SettingsTab from "../settings-tab";
import Footer from "../Footer"; import Footer from "../Footer";
import { NewsModal } from "../NewsModal"; import { NewsModal } from "../NewsModal";
import Tabs from "../Tabs"; import Tabs from "../Tabs";
import Header from "../Header"; import Header from "../Header";
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"; import { ChevronLeftIcon } from "lucide-react";
import { cn } from "@/lib/utils";
import { logAtom } from "@/atoms/logAtom"; import { logAtom } from "@/atoms/logAtom";
import ELECTRON_COMMANDS from "@common/commands"; import ELECTRON_COMMANDS from "@common/commands";
import useUpscaylVersion from "../hooks/use-upscayl-version"; 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 = ({ const Sidebar = ({
setUpscaledImagePath, setUpscaledImagePath,
@ -57,14 +56,13 @@ const Sidebar = ({
selectImageHandler: () => Promise<void>; selectImageHandler: () => Promise<void>;
selectFolderHandler: () => Promise<void>; selectFolderHandler: () => Promise<void>;
}) => { }) => {
const t = useAtomValue(translationAtom); const t = useTranslation();
const { logit } = useLog(); const logit = useLogger();
const { toast } = useToast(); const { toast } = useToast();
const version = useUpscaylVersion(); const version = useUpscaylVersion();
// LOCAL STATES // LOCAL STATES
// TODO: Add electron handler for os // TODO: Add electron handler for os
const [os, setOs] = useState<"linux" | "mac" | "win" | undefined>(undefined);
const [model, setModel] = useState("realesrgan-x4plus"); const [model, setModel] = useState("realesrgan-x4plus");
const [doubleUpscayl, setDoubleUpscayl] = useState(false); const [doubleUpscayl, setDoubleUpscayl] = useState(false);
const overwrite = useAtomValue(overwriteAtom); const overwrite = useAtomValue(overwriteAtom);
@ -83,8 +81,6 @@ const Sidebar = ({
const [scale] = useAtom(scaleAtom); const [scale] = useAtom(scaleAtom);
const setDontShowCloudModal = useSetAtom(dontShowCloudModalAtom); const setDontShowCloudModal = useSetAtom(dontShowCloudModalAtom);
const noImageProcessing = useAtomValue(noImageProcessingAtom); const noImageProcessing = useAtomValue(noImageProcessingAtom);
const [news, setNews] = useAtom(newsAtom);
const [showNewsModal, setShowNewsModal] = useAtom(showNewsModalAtom);
const customWidth = useAtomValue(customWidthAtom); const customWidth = useAtomValue(customWidthAtom);
const useCustomWidth = useAtomValue(useCustomWidthAtom); const useCustomWidth = useAtomValue(useCustomWidthAtom);
const tileSize = useAtomValue(tileSizeAtom); const tileSize = useAtomValue(tileSizeAtom);
@ -174,25 +170,13 @@ const Sidebar = ({
return ( return (
<> <>
{/* TOP LOGO WHEN SIDEBAR IS HIDDEN */} {/* TOP LOGO WHEN SIDEBAR IS HIDDEN */}
{!showSidebar && ( {!showSidebar && <UpscaylLogo />}
<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 ">
<Logo className="w-5" />
{t("TITLE")}
</div>
)}
{/* SIDEBAR BUTTON */} <SidebarToggleButton
<button showSidebar={showSidebar}
className={cn( setShowSidebar={setShowSidebar}
"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 <div
className={`relative flex h-screen min-w-[350px] max-w-[350px] flex-col bg-base-100 ${showSidebar ? "" : "hidden"}`} className={`relative flex h-screen min-w-[350px] max-w-[350px] flex-col bg-base-100 ${showSidebar ? "" : "hidden"}`}
> >
@ -203,22 +187,13 @@ const Sidebar = ({
<ChevronLeftIcon /> <ChevronLeftIcon />
</button> </button>
{/* MACOS TITLEBAR */}
{window.electron.platform === "mac" && ( {window.electron.platform === "mac" && (
<div className="mac-titlebar pt-8"></div> <div className="mac-titlebar pt-8"></div>
)} )}
{/* HEADER */}
<Header version={version} /> <Header version={version} />
{/* NEWS DIALOG */} <NewsModal />
<NewsModal
show={showNewsModal}
setShow={(val: boolean) => {
setShowNewsModal(val);
setNews((prev) => ({ ...prev, seen: true }));
}}
news={news}
/>
<Tabs selectedTab={selectedTab} setSelectedTab={setSelectedTab} /> <Tabs selectedTab={selectedTab} setSelectedTab={setSelectedTab} />
@ -252,13 +227,11 @@ const Sidebar = ({
saveImageAs={saveImageAs} saveImageAs={saveImageAs}
setSaveImageAs={setSaveImageAs} setSaveImageAs={setSaveImageAs}
logData={logData} logData={logData}
os={os}
show={showCloudModal} show={showCloudModal}
setShow={setShowCloudModal} setShow={setShowCloudModal}
setDontShowCloudModal={setDontShowCloudModal} setDontShowCloudModal={setDontShowCloudModal}
/> />
)} )}
{/* )} */}
<Footer /> <Footer />
</div> </div>
</> </>

View 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;

View File

@ -0,0 +1,15 @@
import UpscaylSVGLogo from "../icons/Logo";
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;

View File

@ -3,7 +3,7 @@ import React, { useCallback, useEffect, 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/modelsListAtom";
import useLog from "../../hooks/useLog"; import useLogger from "../../hooks/use-logger";
import { import {
noImageProcessingAtom, noImageProcessingAtom,
savedOutputPathAtom, savedOutputPathAtom,
@ -76,7 +76,7 @@ function LeftPaneImageSteps({
const [targetWidth, setTargetWidth] = useState<number>(null); const [targetWidth, setTargetWidth] = useState<number>(null);
const [targetHeight, setTargetHeight] = useState<number>(null); const [targetHeight, setTargetHeight] = useState<number>(null);
const { logit } = useLog(); const logit = useLogger();
const { toast } = useToast(); const { toast } = useToast();
const t = useAtomValue(translationAtom); const t = useAtomValue(translationAtom);

View File

@ -5,7 +5,7 @@ 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,
@ -113,4 +113,4 @@ const ImageOptions = ({
); );
}; };
export default ImageOptions; export default ImageViewSettings;

View File

@ -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 InformationCard({ 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 InformationCard;

View File

@ -9,12 +9,12 @@ import {
progressAtom, progressAtom,
rememberOutputFolderAtom, rememberOutputFolderAtom,
} from "../atoms/userSettingsAtom"; } from "../atoms/userSettingsAtom";
import useLog from "../components/hooks/useLog"; import useLogger from "../components/hooks/use-logger";
import { newsAtom, showNewsModalAtom } from "@/atoms/newsAtom"; import { newsAtom, showNewsModalAtom } from "@/atoms/newsAtom";
import matter from "gray-matter"; import matter from "gray-matter";
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/Logo";
import { translationAtom } from "@/atoms/translations-atom"; import { translationAtom } from "@/atoms/translations-atom";
import Sidebar from "@/components/sidebar"; import Sidebar from "@/components/sidebar";
import MainContent from "@/components/main-content"; import MainContent from "@/components/main-content";
@ -25,7 +25,7 @@ import { initCustomModels } from "@/components/hooks/use-custom-models";
const Home = () => { const Home = () => {
const t = useAtomValue(translationAtom); const t = useAtomValue(translationAtom);
const { logit } = useLog(); const logit = useLogger();
const { toast } = useToast(); const { toast } = useToast();
initCustomModels(); initCustomModels();
@ -279,47 +279,6 @@ const Home = () => {
); );
}, []); }, []);
// 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);
@ -341,7 +300,7 @@ const Home = () => {
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" />
); );
} }