import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const currentDir = path.dirname(fileURLToPath(import.meta.url)); const distDir = path.resolve(currentDir, '../dist'); const manifestPath = path.join(distDir, 'dashboard-manifest.json'); const clockRegionPath = path.join(distDir, 'clock-region.json'); const themesSourcePath = path.resolve(currentDir, '../config/themes.json'); const themesDistPath = path.join(distDir, 'themes.json'); const themesDir = path.join(distDir, 'themes'); const kindleBackgroundsDir = path.resolve(currentDir, '../kindle-backgrounds'); const dashboardBaseUrl = 'https://shell.biboer.cn:20001'; const themesSource = JSON.parse(fs.readFileSync(themesSourcePath, 'utf8')); const generatedAt = new Date().toISOString(); const defaultTheme = themesSource.themes.find((theme) => theme.id === themesSource.defaultThemeId); const defaultVariant = defaultTheme?.variants?.[themesSource.defaultOrientation]; const defaultDeviceClock = defaultVariant ? buildRuntimeClock(defaultTheme.id, themesSource.defaultOrientation, defaultVariant) : null; const defaultClockRegion = defaultVariant ? { x: defaultDeviceClock.x, y: defaultDeviceClock.y, width: defaultDeviceClock.width, height: defaultDeviceClock.height, } : { x: 313, y: 0, width: 220, height: 220, }; const clockRegion = fs.existsSync(clockRegionPath) ? { ...defaultClockRegion, ...JSON.parse(fs.readFileSync(clockRegionPath, 'utf8')), } : defaultClockRegion; const manifest = { theme: { id: themesSource.defaultThemeId, orientation: themesSource.defaultOrientation, themesUrl: `${dashboardBaseUrl}/themes.json`, }, background: { path: 'kindlebg.png', url: `${dashboardBaseUrl}/kindlebg.png`, updatedAt: generatedAt, refreshIntervalMinutes: 120, }, clockRegion, clockFace: { path: 'assets/clock-face.png', managedOnKindle: true, designWidth: 220, designHeight: 220, }, clockHands: { hourPattern: 'assets/hour-hand/%03d.png', minutePattern: 'assets/minute-hand/%02d.png', refreshIntervalMinutes: 1, networkRequired: false, anchorMode: 'baked-into-patch', scaleWithClockFace: false, }, }; const themesIndex = { updatedAt: generatedAt, defaultThemeId: themesSource.defaultThemeId, defaultOrientation: themesSource.defaultOrientation, themes: themesSource.themes.map((theme) => ({ id: theme.id, label: theme.label, configUrl: `${dashboardBaseUrl}/themes/${theme.id}.json`, orientations: Object.keys(theme.variants), })), }; function toDeviceClock(variant, orientation) { if (orientation !== 'landscape') { return { ...variant.clock, rotationDegrees: 0, }; } return { ...variant.clock, x: variant.viewport.height - (variant.clock.y + variant.clock.height), y: variant.clock.x, width: variant.clock.height, height: variant.clock.width, rotationDegrees: 90, }; } function resolveVariantClock(themeId, orientation, variant) { const regionPath = path.join(distDir, 'themes', themeId, orientation, 'kindlebg.clock-region.json'); const exportedRegion = fs.existsSync(regionPath) ? JSON.parse(fs.readFileSync(regionPath, 'utf8')) : null; return { ...variant, clock: { ...variant.clock, ...(exportedRegion ? { x: exportedRegion.x, y: exportedRegion.y, width: exportedRegion.width, height: exportedRegion.height, } : {}), }, }; } function buildRuntimeClock(themeId, orientation, variant) { const resolvedVariant = resolveVariantClock(themeId, orientation, variant); const hasExportedRegion = resolvedVariant.clock.x !== variant.clock.x || resolvedVariant.clock.y !== variant.clock.y || resolvedVariant.clock.width !== variant.clock.width || resolvedVariant.clock.height !== variant.clock.height; if (orientation === 'landscape' && hasExportedRegion) { return { ...resolvedVariant.clock, rotationDegrees: 90, }; } return toDeviceClock(resolvedVariant, orientation); } function buildThemeConfig(theme) { return { id: theme.id, label: theme.label, updatedAt: generatedAt, variants: Object.fromEntries( Object.entries(theme.variants).map(([orientation, variant]) => { return [ orientation, { devicePlacement: variant.devicePlacement, background: { // Kindle 端统一走扁平目录,避免设备侧自己拼主题子目录规则。 path: `kindle-backgrounds/${theme.id}-${orientation}.png`, url: `${dashboardBaseUrl}/kindle-backgrounds/${theme.id}-${orientation}.png`, refreshIntervalMinutes: 120, }, clock: buildRuntimeClock(theme.id, orientation, variant), }, ]; }), ), }; } fs.mkdirSync(distDir, { recursive: true }); fs.mkdirSync(themesDir, { recursive: true }); fs.mkdirSync(kindleBackgroundsDir, { recursive: true }); fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8'); fs.writeFileSync(themesDistPath, `${JSON.stringify(themesIndex, null, 2)}\n`, 'utf8'); for (const theme of themesSource.themes) { const themePath = path.join(themesDir, `${theme.id}.json`); fs.writeFileSync(themePath, `${JSON.stringify(buildThemeConfig(theme), null, 2)}\n`, 'utf8'); } console.log(`Wrote ${manifestPath}`); console.log(`Wrote ${themesDistPath}`);