@screegen/cli 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/screegen.js +2 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +259 -0
- package/package.json +52 -0
- package/templates/index.html.template +14 -0
- package/templates/package.json.template +26 -0
- package/templates/screegen.config.ts.template +43 -0
- package/templates/src/App.tsx.template +72 -0
- package/templates/src/index.tsx.template +13 -0
- package/templates/src/screens/FeaturesScreen.module.scss.template +57 -0
- package/templates/src/screens/FeaturesScreen.tsx.template +36 -0
- package/templates/src/translations.ts.template +31 -0
- package/templates/src/vite-env.d.ts.template +6 -0
- package/templates/tsconfig.json.template +24 -0
- package/templates/vite.config.ts.template +9 -0
package/bin/screegen.js
ADDED
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
|
|
3
|
+
declare function createProgram(): Command;
|
|
4
|
+
declare function runCli(argv?: string[]): void;
|
|
5
|
+
declare function isMainModule(importMetaUrl: string, processArgv: string[]): boolean;
|
|
6
|
+
|
|
7
|
+
export { createProgram, isMainModule, runCli };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
|
|
4
|
+
// src/commands/init.ts
|
|
5
|
+
import fs from "fs/promises";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
import chalk from "chalk";
|
|
9
|
+
import prompts from "prompts";
|
|
10
|
+
var __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
var templatesDir = path.resolve(__dirname, "..", "templates");
|
|
12
|
+
async function copyTemplate(templateName, targetDir, targetName, replacements = {}) {
|
|
13
|
+
const templatePath = path.join(templatesDir, templateName);
|
|
14
|
+
const targetPath = path.join(targetDir, targetName);
|
|
15
|
+
let content = await fs.readFile(templatePath, "utf-8");
|
|
16
|
+
for (const [key, value] of Object.entries(replacements)) {
|
|
17
|
+
content = content.replace(new RegExp(`{{${key}}}`, "g"), value);
|
|
18
|
+
}
|
|
19
|
+
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
|
20
|
+
await fs.writeFile(targetPath, content);
|
|
21
|
+
}
|
|
22
|
+
async function initCommand(options) {
|
|
23
|
+
let projectName = options.name;
|
|
24
|
+
const directory = options.directory || process.cwd();
|
|
25
|
+
console.log("--------------------------------");
|
|
26
|
+
console.log("directory", directory);
|
|
27
|
+
console.log("projectName", projectName);
|
|
28
|
+
console.log("--------------------------------");
|
|
29
|
+
if (!projectName) {
|
|
30
|
+
const response = await prompts({
|
|
31
|
+
type: "text",
|
|
32
|
+
name: "projectName",
|
|
33
|
+
message: "Project name:",
|
|
34
|
+
initial: projectName || "my-screenshot-app"
|
|
35
|
+
});
|
|
36
|
+
projectName = response.projectName;
|
|
37
|
+
}
|
|
38
|
+
if (!projectName) {
|
|
39
|
+
console.log(chalk.red("Project name is required"));
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
const targetDir = path.resolve(directory, projectName);
|
|
43
|
+
console.log(chalk.blue(`
|
|
44
|
+
Creating screegen project: ${projectName}
|
|
45
|
+
`));
|
|
46
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
47
|
+
await fs.mkdir(path.join(targetDir, "src", "screens"), { recursive: true });
|
|
48
|
+
await fs.mkdir(path.join(targetDir, "public", "assets"), { recursive: true });
|
|
49
|
+
const replacements = { projectName };
|
|
50
|
+
await copyTemplate(
|
|
51
|
+
"package.json.template",
|
|
52
|
+
targetDir,
|
|
53
|
+
"package.json",
|
|
54
|
+
replacements
|
|
55
|
+
);
|
|
56
|
+
await copyTemplate("tsconfig.json.template", targetDir, "tsconfig.json");
|
|
57
|
+
await copyTemplate("vite.config.ts.template", targetDir, "vite.config.ts");
|
|
58
|
+
await copyTemplate(
|
|
59
|
+
"screegen.config.ts.template",
|
|
60
|
+
targetDir,
|
|
61
|
+
"screegen.config.ts"
|
|
62
|
+
);
|
|
63
|
+
await copyTemplate(
|
|
64
|
+
"index.html.template",
|
|
65
|
+
targetDir,
|
|
66
|
+
"index.html",
|
|
67
|
+
replacements
|
|
68
|
+
);
|
|
69
|
+
await copyTemplate("src/index.tsx.template", targetDir, "src/index.tsx");
|
|
70
|
+
await copyTemplate(
|
|
71
|
+
"src/vite-env.d.ts.template",
|
|
72
|
+
targetDir,
|
|
73
|
+
"src/vite-env.d.ts"
|
|
74
|
+
);
|
|
75
|
+
await copyTemplate("src/App.tsx.template", targetDir, "src/App.tsx");
|
|
76
|
+
await copyTemplate(
|
|
77
|
+
"src/translations.ts.template",
|
|
78
|
+
targetDir,
|
|
79
|
+
"src/translations.ts"
|
|
80
|
+
);
|
|
81
|
+
await copyTemplate(
|
|
82
|
+
"src/screens/FeaturesScreen.tsx.template",
|
|
83
|
+
targetDir,
|
|
84
|
+
"src/screens/FeaturesScreen.tsx"
|
|
85
|
+
);
|
|
86
|
+
await copyTemplate(
|
|
87
|
+
"src/screens/FeaturesScreen.module.scss.template",
|
|
88
|
+
targetDir,
|
|
89
|
+
"src/screens/FeaturesScreen.module.scss"
|
|
90
|
+
);
|
|
91
|
+
console.log(chalk.green("Project created successfully!\n"));
|
|
92
|
+
console.log(chalk.gray("Next steps:\n"));
|
|
93
|
+
console.log(chalk.white(` cd ${projectName}`));
|
|
94
|
+
console.log(chalk.white(" yarn install"));
|
|
95
|
+
console.log(chalk.white(" yarn dev # Start dev server"));
|
|
96
|
+
console.log(chalk.white(" screegen generate # Generate screenshots\n"));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// src/commands/generate.ts
|
|
100
|
+
import { chromium } from "playwright";
|
|
101
|
+
import { spawn } from "child_process";
|
|
102
|
+
import path2 from "path";
|
|
103
|
+
import fs2 from "fs/promises";
|
|
104
|
+
import chalk2 from "chalk";
|
|
105
|
+
import ora from "ora";
|
|
106
|
+
import getPort from "get-port";
|
|
107
|
+
async function fetchConfig(baseUrl, browser) {
|
|
108
|
+
const page = await browser.newPage();
|
|
109
|
+
await page.goto(`${baseUrl}/config`);
|
|
110
|
+
await page.waitForSelector("#screegen-config");
|
|
111
|
+
const configText = await page.$eval(
|
|
112
|
+
"#screegen-config",
|
|
113
|
+
/* v8 ignore next */
|
|
114
|
+
(el) => el.textContent
|
|
115
|
+
);
|
|
116
|
+
await page.close();
|
|
117
|
+
if (!configText) {
|
|
118
|
+
throw new Error("Failed to read config from /config page");
|
|
119
|
+
}
|
|
120
|
+
return JSON.parse(configText);
|
|
121
|
+
}
|
|
122
|
+
function startDevServer(port) {
|
|
123
|
+
return new Promise((resolve, reject) => {
|
|
124
|
+
const devProcess = spawn("yarn", ["dev", "--port", String(port)], {
|
|
125
|
+
cwd: process.cwd(),
|
|
126
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
127
|
+
shell: true
|
|
128
|
+
});
|
|
129
|
+
let resolved = false;
|
|
130
|
+
const timeout = setTimeout(() => {
|
|
131
|
+
if (!resolved) {
|
|
132
|
+
reject(new Error("Dev server failed to start within 30 seconds"));
|
|
133
|
+
}
|
|
134
|
+
}, 3e4);
|
|
135
|
+
devProcess.stdout?.on("data", (data) => {
|
|
136
|
+
const output = data.toString();
|
|
137
|
+
const match = output.match(/Local:\s+(http:\/\/localhost:\d+)/);
|
|
138
|
+
if (match && !resolved) {
|
|
139
|
+
resolved = true;
|
|
140
|
+
clearTimeout(timeout);
|
|
141
|
+
resolve({ process: devProcess, url: match[1] });
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
devProcess.stderr?.on("data", (data) => {
|
|
145
|
+
const output = data.toString();
|
|
146
|
+
const match = output.match(/Local:\s+(http:\/\/localhost:\d+)/);
|
|
147
|
+
if (match && !resolved) {
|
|
148
|
+
resolved = true;
|
|
149
|
+
clearTimeout(timeout);
|
|
150
|
+
resolve({ process: devProcess, url: match[1] });
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
devProcess.on("error", (err) => {
|
|
154
|
+
if (!resolved) {
|
|
155
|
+
clearTimeout(timeout);
|
|
156
|
+
reject(err);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
devProcess.on("exit", (code) => {
|
|
160
|
+
if (!resolved) {
|
|
161
|
+
clearTimeout(timeout);
|
|
162
|
+
reject(new Error(`Dev server exited with code ${code}`));
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
async function generateCommand(options) {
|
|
168
|
+
const spinner = ora("Starting dev server...").start();
|
|
169
|
+
let devProcess = null;
|
|
170
|
+
try {
|
|
171
|
+
const port = await getPort({ port: parseInt(options.port) });
|
|
172
|
+
const { process: serverProcess, url: baseUrl } = await startDevServer(port);
|
|
173
|
+
devProcess = serverProcess;
|
|
174
|
+
spinner.text = "Launching browser...";
|
|
175
|
+
const browser = await chromium.launch();
|
|
176
|
+
spinner.text = "Loading configuration...";
|
|
177
|
+
const config = await fetchConfig(baseUrl, browser);
|
|
178
|
+
if (config.languages.length === 0) {
|
|
179
|
+
spinner.fail("No languages found in config");
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
if (config.devices.length === 0) {
|
|
183
|
+
spinner.fail("No devices found in config");
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
spinner.succeed("Setup complete");
|
|
187
|
+
console.log(chalk2.blue("\nGenerating screenshots...\n"));
|
|
188
|
+
const outputDir = path2.resolve(process.cwd(), options.output);
|
|
189
|
+
await fs2.mkdir(outputDir, { recursive: true });
|
|
190
|
+
let screenshotCount = 0;
|
|
191
|
+
for (const device of config.devices) {
|
|
192
|
+
console.log(chalk2.cyan(`
|
|
193
|
+
${device.key}:`));
|
|
194
|
+
for (const screen of device.screens) {
|
|
195
|
+
for (const language of config.languages) {
|
|
196
|
+
const page = await browser.newPage();
|
|
197
|
+
await page.setViewportSize({
|
|
198
|
+
width: Math.floor(device.width),
|
|
199
|
+
height: Math.floor(device.height)
|
|
200
|
+
});
|
|
201
|
+
const url = `${baseUrl}/screens/${device.key}/${screen.key}/${language}`;
|
|
202
|
+
await page.goto(url);
|
|
203
|
+
await page.waitForLoadState("networkidle");
|
|
204
|
+
await page.waitForTimeout(500);
|
|
205
|
+
const langDir = path2.join(outputDir, language);
|
|
206
|
+
await fs2.mkdir(langDir, { recursive: true });
|
|
207
|
+
for (const fastlaneKey of device.fastlaneKeys) {
|
|
208
|
+
const screenIndex = device.screens.indexOf(screen) + 1;
|
|
209
|
+
const filename = `${screenIndex}_${fastlaneKey}_${screenIndex}.png`;
|
|
210
|
+
const filepath = path2.join(langDir, filename);
|
|
211
|
+
await page.screenshot({ path: filepath });
|
|
212
|
+
screenshotCount++;
|
|
213
|
+
console.log(chalk2.gray(` ${language}/${filename}`));
|
|
214
|
+
}
|
|
215
|
+
await page.close();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
await browser.close();
|
|
220
|
+
devProcess.kill();
|
|
221
|
+
devProcess = null;
|
|
222
|
+
console.log(
|
|
223
|
+
chalk2.green(`
|
|
224
|
+
Generated ${screenshotCount} screenshots to ${outputDir}`)
|
|
225
|
+
);
|
|
226
|
+
} catch (error) {
|
|
227
|
+
if (devProcess) {
|
|
228
|
+
devProcess.kill();
|
|
229
|
+
}
|
|
230
|
+
spinner.fail("Generation failed");
|
|
231
|
+
console.error(
|
|
232
|
+
chalk2.red(error instanceof Error ? error.message : String(error))
|
|
233
|
+
);
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// src/index.ts
|
|
239
|
+
function createProgram() {
|
|
240
|
+
const program = new Command();
|
|
241
|
+
program.name("screegen").description("Screenshot generation tool for app store assets").version("1.0.0");
|
|
242
|
+
program.command("init").description("Create a new screegen project").option("-n, --name <name>", "Project name").option("-d, --directory <dir>", "Target directory", process.cwd()).action(initCommand);
|
|
243
|
+
program.command("generate").description("Generate screenshots using Playwright").option("-o, --output <dir>", "Output directory", "screenshots").option("-p, --port <port>", "Dev server port", "3000").action(generateCommand);
|
|
244
|
+
return program;
|
|
245
|
+
}
|
|
246
|
+
function runCli(argv) {
|
|
247
|
+
createProgram().parse(argv);
|
|
248
|
+
}
|
|
249
|
+
function isMainModule(importMetaUrl, processArgv) {
|
|
250
|
+
return importMetaUrl === `file://${processArgv[1]}` || processArgv[1]?.endsWith("screegen.js") || false;
|
|
251
|
+
}
|
|
252
|
+
if (isMainModule(import.meta.url, process.argv)) {
|
|
253
|
+
runCli();
|
|
254
|
+
}
|
|
255
|
+
export {
|
|
256
|
+
createProgram,
|
|
257
|
+
isMainModule,
|
|
258
|
+
runCli
|
|
259
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@screegen/cli",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"bin": {
|
|
6
|
+
"screegen": "bin/screegen.js"
|
|
7
|
+
},
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"types": "dist/index.d.ts",
|
|
11
|
+
"files": [
|
|
12
|
+
"bin",
|
|
13
|
+
"dist",
|
|
14
|
+
"templates"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"dev": "tsup --watch",
|
|
18
|
+
"build": "tsup",
|
|
19
|
+
"lint": "eslint src",
|
|
20
|
+
"test": "vitest run --coverage",
|
|
21
|
+
"generate-templates": "tsx scripts/generate-templates.ts",
|
|
22
|
+
"publish": "npm publish --access=public"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"chalk": "^5.3.0",
|
|
26
|
+
"commander": "^12.1.0",
|
|
27
|
+
"get-port": "^7.1.0",
|
|
28
|
+
"ora": "^8.1.0",
|
|
29
|
+
"playwright": "^1.49.1",
|
|
30
|
+
"prompts": "^2.4.2",
|
|
31
|
+
"vite": "^6.0.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@eslint/js": "^9.39.2",
|
|
35
|
+
"@types/node": "^22.10.5",
|
|
36
|
+
"@types/prompts": "^2.4.9",
|
|
37
|
+
"@typescript-eslint/eslint-plugin": "^8.52.0",
|
|
38
|
+
"@typescript-eslint/parser": "^8.52.0",
|
|
39
|
+
"@vitest/coverage-v8": "^4.0.16",
|
|
40
|
+
"eslint": "^9.39.2",
|
|
41
|
+
"eslint-config-prettier": "^10.1.8",
|
|
42
|
+
"eslint-plugin-prettier": "^5.5.4",
|
|
43
|
+
"globals": "^17.0.0",
|
|
44
|
+
"jiti": "^2.6.1",
|
|
45
|
+
"prettier": "^3.7.4",
|
|
46
|
+
"tsup": "^8.3.5",
|
|
47
|
+
"tsx": "^4.21.0",
|
|
48
|
+
"typescript": "^5.7.2",
|
|
49
|
+
"typescript-eslint": "^8.52.0",
|
|
50
|
+
"vitest": "^4.0.16"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>{{projectName}}</title>
|
|
7
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Lexend:wght@300;400&display=swap" />
|
|
8
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css" />
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<div id="root"></div>
|
|
12
|
+
<script type="module" src="/src/index.tsx"></script>
|
|
13
|
+
</body>
|
|
14
|
+
</html>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{projectName}}",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "tsc && vite build",
|
|
9
|
+
"preview": "vite preview",
|
|
10
|
+
"generate": "screegen generate"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@screegen/components": "^1.0.0",
|
|
14
|
+
"react": "^18.3.1",
|
|
15
|
+
"react-dom": "^18.3.1",
|
|
16
|
+
"react-router-dom": "^6.28.2"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/react": "^18.3.17",
|
|
20
|
+
"@types/react-dom": "^18.3.5",
|
|
21
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
22
|
+
"sass": "^1.83.0",
|
|
23
|
+
"typescript": "^5.7.2",
|
|
24
|
+
"vite": "^6.0.0"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ProjectConfig, ScreenComponentProps } from "@screegen/components";
|
|
2
|
+
import { FeaturesScreen } from "./src/screens/FeaturesScreen";
|
|
3
|
+
import { t } from "./src/translations";
|
|
4
|
+
|
|
5
|
+
export type AppLanguageCode = "en-US" | "de-DE";
|
|
6
|
+
|
|
7
|
+
// Wrapper component that provides translations
|
|
8
|
+
function FeaturesScreenWrapper({ language, deviceKey }: ScreenComponentProps) {
|
|
9
|
+
return (
|
|
10
|
+
<FeaturesScreen
|
|
11
|
+
language={language}
|
|
12
|
+
deviceKey={deviceKey}
|
|
13
|
+
title={t(language, "title")}
|
|
14
|
+
subtitle={t(language, "subtitle")}
|
|
15
|
+
features={[
|
|
16
|
+
{ title: t(language, "feature1"), description: t(language, "feature1Desc") },
|
|
17
|
+
{ title: t(language, "feature2"), description: t(language, "feature2Desc") },
|
|
18
|
+
]}
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const config: ProjectConfig<AppLanguageCode> = {
|
|
24
|
+
languages: ["en-US", "de-DE"],
|
|
25
|
+
devices: [
|
|
26
|
+
{
|
|
27
|
+
key: "iphone",
|
|
28
|
+
fastlaneKeys: ["APP_IPHONE_67"],
|
|
29
|
+
width: 1290,
|
|
30
|
+
height: 2796,
|
|
31
|
+
screens: [{ key: "features", component: FeaturesScreenWrapper }],
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
key: "ipad",
|
|
35
|
+
fastlaneKeys: ["APP_IPAD_PRO_129"],
|
|
36
|
+
width: 2048,
|
|
37
|
+
height: 2732,
|
|
38
|
+
screens: [{ key: "features", component: FeaturesScreenWrapper }],
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export default config;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Routes, Route, useParams } from 'react-router-dom';
|
|
2
|
+
import {
|
|
3
|
+
Screen,
|
|
4
|
+
OverviewGrid,
|
|
5
|
+
ScreengenConfig,
|
|
6
|
+
useColorScheme,
|
|
7
|
+
useUrlState,
|
|
8
|
+
ColorScheme,
|
|
9
|
+
} from '@screegen/components';
|
|
10
|
+
import config, { AppLanguageCode } from '../screegen.config';
|
|
11
|
+
|
|
12
|
+
function ScreenPage() {
|
|
13
|
+
const { deviceKey, screenKey, language } = useParams<{
|
|
14
|
+
deviceKey: string;
|
|
15
|
+
screenKey: string;
|
|
16
|
+
language: string;
|
|
17
|
+
}>();
|
|
18
|
+
|
|
19
|
+
if (!deviceKey || !screenKey || !language) {
|
|
20
|
+
return <div>Invalid screen parameters</div>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<Screen
|
|
25
|
+
config={config}
|
|
26
|
+
deviceKey={deviceKey}
|
|
27
|
+
screenKey={screenKey}
|
|
28
|
+
language={language as AppLanguageCode}
|
|
29
|
+
/>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function OverviewPage() {
|
|
34
|
+
const [language, setLanguage] = useUrlState<AppLanguageCode>(
|
|
35
|
+
'language',
|
|
36
|
+
config.languages[0]
|
|
37
|
+
);
|
|
38
|
+
const [scale, setScale] = useUrlState<string>('scale', '0.25');
|
|
39
|
+
const [colorSchemeParam, setColorScheme] = useUrlState<ColorScheme | ''>(
|
|
40
|
+
'colorScheme',
|
|
41
|
+
''
|
|
42
|
+
);
|
|
43
|
+
const systemColorScheme = useColorScheme();
|
|
44
|
+
const colorScheme = colorSchemeParam || systemColorScheme;
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<OverviewGrid
|
|
48
|
+
config={config}
|
|
49
|
+
language={language}
|
|
50
|
+
scale={parseFloat(scale)}
|
|
51
|
+
colorScheme={colorScheme}
|
|
52
|
+
onLanguageChange={setLanguage}
|
|
53
|
+
onScaleChange={(s) => setScale(String(s))}
|
|
54
|
+
onColorSchemeChange={setColorScheme}
|
|
55
|
+
/>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function App() {
|
|
60
|
+
return (
|
|
61
|
+
<Routes>
|
|
62
|
+
<Route path="/" element={<OverviewPage />} />
|
|
63
|
+
<Route path="/config" element={<ScreengenConfig config={config} />} />
|
|
64
|
+
<Route
|
|
65
|
+
path="/screens/:deviceKey/:screenKey/:language"
|
|
66
|
+
element={<ScreenPage />}
|
|
67
|
+
/>
|
|
68
|
+
</Routes>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export default App;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import ReactDOM from 'react-dom/client';
|
|
3
|
+
import { BrowserRouter } from 'react-router-dom';
|
|
4
|
+
import '@screegen/components/styles.css';
|
|
5
|
+
import App from './App';
|
|
6
|
+
|
|
7
|
+
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
8
|
+
<React.StrictMode>
|
|
9
|
+
<BrowserRouter>
|
|
10
|
+
<App />
|
|
11
|
+
</BrowserRouter>
|
|
12
|
+
</React.StrictMode>
|
|
13
|
+
);
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
.container {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
align-items: center;
|
|
5
|
+
justify-content: center;
|
|
6
|
+
min-height: 100%;
|
|
7
|
+
padding: 60px 40px;
|
|
8
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
9
|
+
color: white;
|
|
10
|
+
font-family: "Lexend", system-ui, sans-serif;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.header {
|
|
14
|
+
text-align: center;
|
|
15
|
+
margin-bottom: 60px;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.title {
|
|
19
|
+
font-size: 64px;
|
|
20
|
+
font-weight: 400;
|
|
21
|
+
margin: 0 0 16px 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.subtitle {
|
|
25
|
+
font-size: 28px;
|
|
26
|
+
font-weight: 300;
|
|
27
|
+
opacity: 0.9;
|
|
28
|
+
margin: 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.features {
|
|
32
|
+
display: flex;
|
|
33
|
+
flex-direction: column;
|
|
34
|
+
gap: 32px;
|
|
35
|
+
width: 100%;
|
|
36
|
+
max-width: 800px;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.feature {
|
|
40
|
+
background: rgba(255, 255, 255, 0.15);
|
|
41
|
+
border-radius: 16px;
|
|
42
|
+
padding: 32px;
|
|
43
|
+
backdrop-filter: blur(10px);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.featureTitle {
|
|
47
|
+
font-size: 24px;
|
|
48
|
+
font-weight: 400;
|
|
49
|
+
margin: 0 0 8px 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.featureDescription {
|
|
53
|
+
font-size: 18px;
|
|
54
|
+
font-weight: 300;
|
|
55
|
+
opacity: 0.9;
|
|
56
|
+
margin: 0;
|
|
57
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ScreenComponentProps } from "@screegen/components";
|
|
2
|
+
import styles from "./FeaturesScreen.module.scss";
|
|
3
|
+
|
|
4
|
+
interface Feature {
|
|
5
|
+
title: string;
|
|
6
|
+
description: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface FeaturesScreenProps extends ScreenComponentProps {
|
|
10
|
+
title: string;
|
|
11
|
+
subtitle: string;
|
|
12
|
+
features: Feature[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function FeaturesScreen({
|
|
16
|
+
title,
|
|
17
|
+
subtitle,
|
|
18
|
+
features,
|
|
19
|
+
}: FeaturesScreenProps) {
|
|
20
|
+
return (
|
|
21
|
+
<div className={styles.container}>
|
|
22
|
+
<div className={styles.header}>
|
|
23
|
+
<h1 className={styles.title}>{title}</h1>
|
|
24
|
+
<p className={styles.subtitle}>{subtitle}</p>
|
|
25
|
+
</div>
|
|
26
|
+
<div className={styles.features}>
|
|
27
|
+
{features.map((feature, index) => (
|
|
28
|
+
<div key={index} className={styles.feature}>
|
|
29
|
+
<h3 className={styles.featureTitle}>{feature.title}</h3>
|
|
30
|
+
<p className={styles.featureDescription}>{feature.description}</p>
|
|
31
|
+
</div>
|
|
32
|
+
))}
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translations for your app screenshots.
|
|
3
|
+
* Add your supported languages and translation keys here.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const translations: Record<string, Record<string, string>> = {
|
|
7
|
+
"en-US": {
|
|
8
|
+
title: "Your App Name",
|
|
9
|
+
subtitle: "The best app for your needs",
|
|
10
|
+
feature1: "Amazing Feature",
|
|
11
|
+
feature1Desc: "Description of this amazing feature",
|
|
12
|
+
feature2: "Another Feature",
|
|
13
|
+
feature2Desc: "Description of another great feature",
|
|
14
|
+
},
|
|
15
|
+
"de-DE": {
|
|
16
|
+
title: "Deine App",
|
|
17
|
+
subtitle: "Die beste App für deine Bedürfnisse",
|
|
18
|
+
feature1: "Tolle Funktion",
|
|
19
|
+
feature1Desc: "Beschreibung dieser tollen Funktion",
|
|
20
|
+
feature2: "Weitere Funktion",
|
|
21
|
+
feature2Desc: "Beschreibung einer weiteren Funktion",
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Helper function to get a translation for a given language and key.
|
|
27
|
+
* Falls back to en-US if the key is not found in the requested language.
|
|
28
|
+
*/
|
|
29
|
+
export function t(language: string, key: string): string {
|
|
30
|
+
return translations[language]?.[key] ?? translations["en-US"]?.[key] ?? key;
|
|
31
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"allowImportingTsExtensions": true,
|
|
10
|
+
"isolatedModules": true,
|
|
11
|
+
"moduleDetection": "force",
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"jsx": "react-jsx",
|
|
14
|
+
"strict": true,
|
|
15
|
+
"noUnusedLocals": true,
|
|
16
|
+
"noUnusedParameters": true,
|
|
17
|
+
"noFallthroughCasesInSwitch": true,
|
|
18
|
+
"forceConsistentCasingInFileNames": true,
|
|
19
|
+
"esModuleInterop": true,
|
|
20
|
+
"allowSyntheticDefaultImports": true,
|
|
21
|
+
"resolveJsonModule": true
|
|
22
|
+
},
|
|
23
|
+
"include": ["src", "screegen.config.ts"]
|
|
24
|
+
}
|