diff --git a/src/get-user/get-user.service.ts b/src/get-user/get-user.service.ts index 27f84cd..fd3facc 100644 --- a/src/get-user/get-user.service.ts +++ b/src/get-user/get-user.service.ts @@ -2,6 +2,7 @@ import { Injectable, NotFoundException } from '@nestjs/common'; import { iranDaysUntil } from '../common/iran-time.util'; import { PrismaService } from '../prisma/prisma.service'; import { applyKhanegiNavSample } from './khanegi-nav-sample'; +import { applyKhanegiLayoutVariants } from './khanegi-layout-variants'; import { rewriteLegacyUploadUrl } from '../uploads/uploads.util'; import { PLUGIN_STORE_SETTING_KEY } from '../program-settings/runtime-setting-keys'; import { RuntimeSettingsService } from '../notifications/runtime-settings.service'; @@ -346,6 +347,8 @@ export class GetUserService { header.variant = 'default'; } + applyKhanegiLayoutVariants(app); + const navigation = app.navigation; if (!navigation || typeof navigation !== 'object') { app.navigation = { type: 'both' }; diff --git a/src/get-user/khanegi-layout-variants.ts b/src/get-user/khanegi-layout-variants.ts new file mode 100644 index 0000000..00a5329 --- /dev/null +++ b/src/get-user/khanegi-layout-variants.ts @@ -0,0 +1,131 @@ +/** + * چیدمان المان‌های تم جدید. + * با controlpanel_user/src/config/khanegiLayoutVariants.ts یکی بماند. + * variant شناسهٔ themeN است. جزئیات المان داخل options می‌ماند. + */ + +export const LAYOUT_VARIANT_ID = /^theme[1-9]\d*$/ + +export type LayoutThemeDef = { + id: string + label: string +} + +export type LayoutElementConfig = { + id: string + variant: string + options: Record +} + +export type KhanegiLayoutVariants = { + schemaId: 'layout-variants' + version: '1.0.0' + themes: LayoutThemeDef[] + header: LayoutElementConfig + footer: LayoutElementConfig + home: Record +} + +function isRecord(value: unknown): value is Record { + return Boolean(value) && typeof value === 'object' && !Array.isArray(value) +} + +function isLayoutVariantId(value: unknown): value is string { + return typeof value === 'string' && LAYOUT_VARIANT_ID.test(value) +} + +function defaultThemes(): LayoutThemeDef[] { + return [ + { id: 'theme1', label: 'تم ۱' }, + { id: 'theme2', label: 'تم ۲' }, + ] +} + +function element(id: string): LayoutElementConfig { + return { id, variant: 'theme1', options: {} } +} + +export function mergeLayoutThemes(stored: unknown, usedIds: string[] = []): LayoutThemeDef[] { + const parsed: LayoutThemeDef[] = [] + const seen = new Set() + if (Array.isArray(stored)) { + for (const row of stored) { + if (!isRecord(row) || !isLayoutVariantId(row.id) || seen.has(row.id)) continue + seen.add(row.id) + const label = typeof row.label === 'string' && row.label.trim() + ? row.label.trim().slice(0, 40) + : row.id + parsed.push({ id: row.id, label }) + } + } + const list = parsed.length ? parsed : defaultThemes() + for (const id of usedIds) { + if (!isLayoutVariantId(id) || list.some((theme) => theme.id === id)) continue + list.push({ id, label: id }) + } + return list +} + +export function createDefaultKhanegiLayoutVariants(): KhanegiLayoutVariants { + return { + schemaId: 'layout-variants', + version: '1.0.0', + themes: defaultThemes(), + header: element('header'), + footer: element('footer'), + home: { + assets: element('assets'), + livePrice: element('livePrice'), + quickAccess: element('quickAccess'), + recentTransactions: element('recentTransactions'), + }, + } +} + +export function mergeLayoutElement(id: string, stored: unknown): LayoutElementConfig { + const base = element(id) + if (!isRecord(stored)) return base + const options = isRecord(stored.options) ? { ...stored.options } : {} + return { + ...stored, + id, + variant: isLayoutVariantId(stored.variant) ? stored.variant : 'theme1', + options, + } +} + +/** تم انتخاب‌شده و کلید options پاک نمی‌شود. فهرست تم خالی به تم ۱ و ۲ برمی‌گردد. */ +export function mergeKhanegiLayoutVariants(stored: unknown): KhanegiLayoutVariants { + const defaults = createDefaultKhanegiLayoutVariants() + const src = isRecord(stored) ? stored : {} + const homeSrc = isRecord(src.home) ? src.home : {} + const knownHome = Object.keys(defaults.home) + const home: Record = { ...defaults.home } + + for (const key of Object.keys(homeSrc)) { + if (knownHome.includes(key)) continue + home[key] = mergeLayoutElement(key, homeSrc[key]) + } + for (const key of knownHome) { + home[key] = mergeLayoutElement(key, homeSrc[key]) + } + + const header = mergeLayoutElement('header', src.header) + const footer = mergeLayoutElement('footer', src.footer) + const usedIds = [header.variant, footer.variant, ...Object.values(home).map((item) => item.variant)] + + return { + ...src, + schemaId: 'layout-variants', + version: '1.0.0', + themes: mergeLayoutThemes(src.themes, usedIds), + header, + footer, + home, + } +} + +export function applyKhanegiLayoutVariants(app: { layoutVariants?: unknown } | null | undefined) { + if (!app || typeof app !== 'object') return + app.layoutVariants = mergeKhanegiLayoutVariants(app.layoutVariants) +}