diff --git a/next.config.js b/next.config.js index 8835879..2e59a24 100644 --- a/next.config.js +++ b/next.config.js @@ -10,6 +10,9 @@ const nextConfig = { experimental: { externalDir: true, }, + compiler: { + removeConsole: process.env.NODE_ENV === "production", + }, }; module.exports = nextConfig; diff --git a/package.json b/package.json index 19e7c84..aaad44d 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "clean": "rimraf dist renderer/.next renderer/out", "start": "tsc && electron .", "dev": "tsc && electron .", - "build": "tsc && next build renderer", + "build": "tsc && npm run validate-schema && next build renderer", "tsc": "tsc", "pack-app": "tsc && npm run build && electron-builder --dir", "dist": "tsc && npm run build && cross-env DEBUG=* electron-builder", @@ -66,7 +66,8 @@ "publish-mac-arm-app": "tsc && npm run build && electron-builder --mac --arm64 --publish always", "dist:appstore": "CSC_KEY_PASSWORD=$PASSWORD CSC_LINK=$(openssl base64 -in $CERTIFICATE_PATH) npm run dist:mac-mas", "enable-store": "sed -i '' -e 's/APP_STORE_BUILD: false,/APP_STORE_BUILD: true,/' common/feature-flags.ts", - "disable-store": "sed -i '' -e 's/APP_STORE_BUILD: true,/APP_STORE_BUILD: false,/' common/feature-flags.ts" + "disable-store": "sed -i '' -e 's/APP_STORE_BUILD: true,/APP_STORE_BUILD: false,/' common/feature-flags.ts", + "validate-schema": "node scripts/validate-schema.js" }, "build": { "productName": "Upscayl", diff --git a/renderer/components/main-content/onboarding-dialog.tsx b/renderer/components/main-content/onboarding-dialog.tsx new file mode 100644 index 0000000..0a43508 --- /dev/null +++ b/renderer/components/main-content/onboarding-dialog.tsx @@ -0,0 +1,145 @@ +import { useState } from "react"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "../ui/dialog"; + +type OnboardingStep = { + title: string; + description: string; + type: "info" | "settings"; + settings?: { + type: "switch" | "input"; + label: string; + key: string; + }[]; +}; + +const onboardingSteps: OnboardingStep[] = [ + { + title: "Welcome to Upscayl 🎉", + description: "Let's get you started with a few quick steps.", + type: "info", + }, + { + title: "Choose Your Preferences", + description: "Configure your initial settings.", + type: "settings", + settings: [ + { + type: "switch", + label: "Enable automatic updates", + key: "autoUpdate", + }, + { + type: "input", + label: "Default output folder", + key: "outputFolder", + }, + ], + }, + { + title: "You're All Set!", + description: "You can now start upscaling your images.", + type: "info", + }, +]; + +export function OnboardingDialog({ + open, + onOpenChange, +}: { + open: boolean; + onOpenChange: (open: boolean) => void; +}) { + const [currentStep, setCurrentStep] = useState(0); + const [settings, setSettings] = useState>({}); + + const currentStepData = onboardingSteps[currentStep]; + const isLastStep = currentStep === onboardingSteps.length - 1; + const isFirstStep = currentStep === 0; + + const handleNext = () => { + if (isLastStep) { + onOpenChange(false); + // Here you can handle saving the settings + console.log("Final settings:", settings); + } else { + setCurrentStep((prev) => prev + 1); + } + }; + + const handleSettingChange = (key: string, value: any) => { + setSettings((prev) => ({ ...prev, [key]: value })); + }; + + return ( + + + + + {currentStepData.title} + + + {currentStepData.description} + + + + {currentStepData.type === "settings" && currentStepData.settings && ( +
+ {currentStepData.settings.map((setting) => ( +
+ + {setting.type === "switch" && ( + + handleSettingChange(setting.key, e.target.checked) + } + /> + )} + {setting.type === "input" && ( + + handleSettingChange(setting.key, e.target.value) + } + /> + )} +
+ ))} +
+ )} + + + + {!isFirstStep && ( + + )} + +
+
+ ); +} diff --git a/renderer/components/sidebar/upscayl-tab/select-model.tsx b/renderer/components/sidebar/upscayl-tab/select-model-dialog.tsx similarity index 97% rename from renderer/components/sidebar/upscayl-tab/select-model.tsx rename to renderer/components/sidebar/upscayl-tab/select-model-dialog.tsx index 76d85d1..8bb366f 100644 --- a/renderer/components/sidebar/upscayl-tab/select-model.tsx +++ b/renderer/components/sidebar/upscayl-tab/select-model-dialog.tsx @@ -17,18 +17,15 @@ import { selectedModelIdAtom } from "@/atoms/user-settings-atom"; import { customModelIdsAtom } from "@/atoms/models-list-atom"; import useTranslation from "@/components/hooks/use-translation"; -export default function SelectModel() { +const SelectModelDialog = () => { const t = useTranslation(); const [selectedModelId, setSelectedModelId] = useAtom(selectedModelIdAtom); - console.log("🚀 => selectedModelId:", selectedModelId); const customModelIds = useAtomValue(customModelIdsAtom); const [open, setOpen] = useState(false); const [zoomedModel, setZoomedModel] = useState(null); const handleModelSelect = (model: ModelId | string) => { - console.log("🚀 => model:", model); - setSelectedModelId(model); setOpen(false); }; @@ -160,4 +157,6 @@ export default function SelectModel() { ); -} +}; + +export default SelectModelDialog; diff --git a/renderer/components/sidebar/upscayl-tab/upscayl-steps.tsx b/renderer/components/sidebar/upscayl-tab/upscayl-steps.tsx index cc74e98..50d41ca 100644 --- a/renderer/components/sidebar/upscayl-tab/upscayl-steps.tsx +++ b/renderer/components/sidebar/upscayl-tab/upscayl-steps.tsx @@ -16,7 +16,7 @@ import { ELECTRON_COMMANDS } from "@common/electron-commands"; import { useToast } from "@/components/ui/use-toast"; import { translationAtom } from "@/atoms/translations-atom"; import { SelectImageScale } from "../settings-tab/select-image-scale"; -import SelectModel from "./select-model"; +import SelectModelDialog from "./select-model-dialog"; import { ImageFormat } from "@/lib/valid-formats"; interface IProps { @@ -156,7 +156,7 @@ function UpscaylSteps({

{t("APP.MODEL_SELECTION.TITLE")}

{t("APP.MODEL_SELECTION.DESCRIPTION")}

- + {!batchMode && ( diff --git a/renderer/pages/index.tsx b/renderer/pages/index.tsx index 00d37ac..b0cecdc 100644 --- a/renderer/pages/index.tsx +++ b/renderer/pages/index.tsx @@ -20,6 +20,7 @@ import getDirectoryFromPath from "@common/get-directory-from-path"; import { FEATURE_FLAGS } from "@common/feature-flags"; import { ImageFormat, VALID_IMAGE_FORMATS } from "@/lib/valid-formats"; import { initCustomModels } from "@/components/hooks/use-custom-models"; +import { OnboardingDialog } from "@/components/main-content/onboarding-dialog"; const Home = () => { const t = useAtomValue(translationAtom); @@ -310,6 +311,7 @@ const Home = () => { doubleUpscaylCounter={doubleUpscaylCounter} setDimensions={setDimensions} /> + {}} /> ); }; diff --git a/scripts/generate-schema.js b/scripts/generate-schema.js new file mode 100644 index 0000000..34899db --- /dev/null +++ b/scripts/generate-schema.js @@ -0,0 +1,52 @@ +function generateSchema(json) { + if (json === null) { + return { type: "null" }; + } + + const type = Array.isArray(json) ? "array" : typeof json; + const schema = { type }; + + switch (type) { + case "object": + const properties = {}; + const required = []; + + for (const [key, value] of Object.entries(json)) { + properties[key] = generateSchema(value); + required.push(key); + } + + schema.properties = properties; + if (required.length > 0) { + schema.required = required; + } + break; + + case "array": + if (json.length > 0) { + // Assume all items in array are of the same type as the first item + schema.items = generateSchema(json[0]); + } + break; + } + + return schema; +} + +module.exports = { generateSchema }; + +// Example usage: +/* +const obj = { + name: "John", + age: 30, + address: { + street: "123 Main St", + city: "Boston" + }, + hobbies: ["reading", "swimming"] +}; + +const schema = generateSchema(obj); +console.log(JSON.stringify(schema, null, 2)); +*/ diff --git a/scripts/validate-schema.js b/scripts/validate-schema.js new file mode 100644 index 0000000..ffa7986 --- /dev/null +++ b/scripts/validate-schema.js @@ -0,0 +1,36 @@ +const Ajv = require("ajv"); +const path = require("path"); +const fs = require("fs"); +const { generateSchema } = require("./generate-schema"); + +const ajv = new Ajv(); + +console.log("Validating language files..."); + +// Load the base language file (en.json) +const enJsonPath = path.join(__dirname, "..", "renderer", "locales", "en.json"); +const enJson = JSON.parse(fs.readFileSync(enJsonPath, "utf-8")); + +// Generate the schema for en.json +const enSchema = generateSchema(enJson); +const validate = ajv.compile(enSchema); + +// Validate other language files +const localesDir = path.join(__dirname, "..", "renderer", "locales"); +const languageFiles = fs + .readdirSync(localesDir) + .filter((file) => file !== "en.json"); + +languageFiles.forEach((file) => { + const filePath = path.join(localesDir, file); + const jsonData = JSON.parse(fs.readFileSync(filePath, "utf-8")); + + const valid = validate(jsonData); + + if (!valid) { + console.error(`Errors in ${file}:`); + console.error(validate.errors); + } else { + console.log(`${file} is valid.`); + } +}); diff --git a/tsconfig.json b/tsconfig.json index b5e2cd7..32941c9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,6 +15,12 @@ "@common/*": ["./common/*"] } }, - "include": ["./electron/**/*", "./common/**/*", "./renderer/**/*"], + "include": [ + "./electron/**/*", + "./common/**/*", + "./renderer/**/*", + "scripts/generate-schema.js", + "scripts/validate-schema.js" + ], "exclude": ["node_modules", "public", "renderer"] }