Add language validation

This commit is contained in:
Nayam Amarshe 2024-12-15 14:25:18 +05:30
parent f3af1d08d8
commit c7bf9c14f8
9 changed files with 254 additions and 10 deletions

View File

@ -10,6 +10,9 @@ const nextConfig = {
experimental: {
externalDir: true,
},
compiler: {
removeConsole: process.env.NODE_ENV === "production",
},
};
module.exports = nextConfig;

View File

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

View File

@ -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<Record<string, any>>({});
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 (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="flex h-[80%] w-full max-w-[80%] flex-col items-center justify-center">
<DialogHeader>
<DialogTitle className="text-center text-4xl">
{currentStepData.title}
</DialogTitle>
<DialogDescription className="text-center text-lg">
{currentStepData.description}
</DialogDescription>
</DialogHeader>
{currentStepData.type === "settings" && currentStepData.settings && (
<div className="grid gap-4 py-4">
{currentStepData.settings.map((setting) => (
<div
key={setting.key}
className="grid grid-cols-2 items-center gap-4"
>
<label htmlFor={setting.key} className="text-right">
{setting.label}
</label>
{setting.type === "switch" && (
<input
type="checkbox"
className="toggle"
defaultChecked
id={setting.key}
checked={settings[setting.key] || false}
onChange={(e) =>
handleSettingChange(setting.key, e.target.checked)
}
/>
)}
{setting.type === "input" && (
<input
className="input input-bordered"
id={setting.key}
value={settings[setting.key] || ""}
onChange={(e) =>
handleSettingChange(setting.key, e.target.value)
}
/>
)}
</div>
))}
</div>
)}
<DialogFooter>
<button onClick={handleNext} className="btn btn-secondary">
{isLastStep ? "Get Started" : "Next"}
</button>
{!isFirstStep && (
<button
onClick={() => setCurrentStep((prev) => prev - 1)}
className="btn btn-primary"
>
Back
</button>
)}
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View File

@ -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<ModelId | null>(null);
const handleModelSelect = (model: ModelId | string) => {
console.log("🚀 => model:", model);
setSelectedModelId(model);
setOpen(false);
};
@ -160,4 +157,6 @@ export default function SelectModel() {
</Dialog>
</div>
);
}
};
export default SelectModelDialog;

View File

@ -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({
<p className="step-heading">{t("APP.MODEL_SELECTION.TITLE")}</p>
<p className="mb-2 text-sm">{t("APP.MODEL_SELECTION.DESCRIPTION")}</p>
<SelectModel />
<SelectModelDialog />
</div>
{!batchMode && (

View File

@ -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}
/>
<OnboardingDialog open={false} onOpenChange={() => {}} />
</div>
);
};

View File

@ -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));
*/

View File

@ -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.`);
}
});

View File

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