You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
268 lines
11 KiB
268 lines
11 KiB
// ─────────────────────────────────────────────────────────────────────────────
|
|
// TYPE GUARDS - vcen-edit/lib/guards.ts
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
/**
|
|
* Type guards específicos para el editor de catálogos vcen-edit.
|
|
* Proporciona validación adicional para estructuras usadas en la UI del editor.
|
|
*/
|
|
|
|
import type { AttDomain, AttValue, ViewConfig, Visual, Hotspot } from '@/vcen/lib';
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// ATTRIBUTE VALUE GUARDS
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Guard para validar AttValue
|
|
*/
|
|
export function isAttValue(value: unknown): value is AttValue {
|
|
if (typeof value !== 'object' || value === null) return false;
|
|
const v = value as Record<string, unknown>;
|
|
return 'value' in v && 'domain' in v;
|
|
}
|
|
|
|
/**
|
|
* Guard para validar que un AttDomain tiene modo 'fixed'
|
|
*/
|
|
export function isAttDomainWithFixedMode(value: unknown): value is AttDomain & { mode: 'fixed'; fixed?: string | number | boolean } {
|
|
if (typeof value !== 'object' || value === null) return false;
|
|
const d = value as Record<string, unknown>;
|
|
|
|
return typeof d.kind === 'string' &&
|
|
d.mode === 'fixed' &&
|
|
(d.fixed === undefined ||
|
|
typeof d.fixed === 'string' ||
|
|
typeof d.fixed === 'number' ||
|
|
typeof d.fixed === 'boolean');
|
|
}
|
|
|
|
/**
|
|
* Guard para validar AttDomain con modo específico
|
|
*/
|
|
export function isAttDomainWithMode<K extends AttDomain['mode']>(
|
|
value: unknown,
|
|
mode: K
|
|
): value is AttDomain & { mode: K } {
|
|
if (typeof value !== 'object' || value === null) return false;
|
|
const d = value as AttDomain;
|
|
|
|
return d.mode === mode;
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// VIEW EDITOR GUARDS
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Guard para validar ViewConfig con estrategia 'static'
|
|
*/
|
|
export function isViewConfigStatic(value: unknown): value is ViewConfig & { strategy: 'static'; url: string } {
|
|
if (typeof value !== 'object' || value === null) return false;
|
|
const config = value as ViewConfig;
|
|
|
|
return config.strategy === 'static' && 'url' in config && typeof config.url === 'string';
|
|
}
|
|
|
|
/**
|
|
* Guard para validar ViewConfig con estrategia 'dynamic'
|
|
*/
|
|
export function isViewConfigDynamic(value: unknown): value is ViewConfig & { strategy: 'dynamic'; template: string | object } {
|
|
if (typeof value !== 'object' || value === null) return false;
|
|
const config = value as ViewConfig;
|
|
|
|
return config.strategy === 'dynamic' && 'template' in config;
|
|
}
|
|
|
|
/**
|
|
* Guard para validar ViewConfig con estrategia 'layers'
|
|
*/
|
|
export function isViewConfigLayers(value: unknown): value is ViewConfig & { strategy: 'layers'; layers: any[] } {
|
|
if (typeof value !== 'object' || value === null) return false;
|
|
const config = value as ViewConfig;
|
|
|
|
return config.strategy === 'layers' && 'layers' in config && Array.isArray(config.layers);
|
|
}
|
|
|
|
/**
|
|
* Guard para validar ViewConfig con estrategia 'api'
|
|
*/
|
|
export function isViewConfigApi(value: unknown): value is ViewConfig & { strategy: 'api'; endpoint: string | object } {
|
|
if (typeof value !== 'object' || value === null) return false;
|
|
const config = value as ViewConfig;
|
|
|
|
return config.strategy === 'api' && 'endpoint' in config;
|
|
}
|
|
|
|
/**
|
|
* Guard para validar ViewConfig con estrategia 'd3'
|
|
*/
|
|
export function isViewConfigD3(value: unknown): value is ViewConfig & { strategy: 'd3'; modelUrl: string } {
|
|
if (typeof value !== 'object' || value === null) return false;
|
|
const config = value as ViewConfig;
|
|
|
|
return config.strategy === 'd3' && 'modelUrl' in config && typeof config.modelUrl === 'string';
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// VISUAL EDITOR GUARDS
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Guard para validar Visual con vistas
|
|
*/
|
|
export function isVisualWithViews(value: unknown): value is Visual & { views: any[] } {
|
|
if (typeof value !== 'object' || value === null) return false;
|
|
const visual = value as Visual;
|
|
|
|
return 'views' in visual && Array.isArray(visual.views);
|
|
}
|
|
|
|
/**
|
|
* Guard para validar Visual con basePath
|
|
*/
|
|
export function isVisualWithBasePath(value: unknown): value is Visual & { basePath: string } {
|
|
if (typeof value !== 'object' || value === null) return false;
|
|
const visual = value as Visual;
|
|
|
|
return 'basePath' in visual && typeof visual.basePath === 'string';
|
|
}
|
|
|
|
/**
|
|
* Guard para validar Visual con fallback
|
|
*/
|
|
export function isVisualWithFallback(value: unknown): value is Visual & { fallback: string } {
|
|
if (typeof value !== 'object' || value === null) return false;
|
|
const visual = value as Visual;
|
|
|
|
return 'fallback' in visual && typeof visual.fallback === 'string';
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// HOTSPOT EDITOR GUARDS
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Guard para validar Hotspot con targetAtt
|
|
*/
|
|
export function isHotspotWithTargetAtt(value: unknown): value is Hotspot & { targetAtt: string } {
|
|
if (typeof value !== 'object' || value === null) return false;
|
|
const hotspot = value as Hotspot;
|
|
|
|
return 'targetAtt' in hotspot && typeof hotspot.targetAtt === 'string';
|
|
}
|
|
|
|
/**
|
|
* Guard para validar Hotspot con tooltip
|
|
*/
|
|
export function isHotspotWithTooltip(value: unknown): value is Hotspot & { tooltip: Record<string, string> } {
|
|
if (typeof value !== 'object' || value === null) return false;
|
|
const hotspot = value as Hotspot;
|
|
|
|
return 'tooltip' in hotspot &&
|
|
typeof hotspot.tooltip === 'object' &&
|
|
hotspot.tooltip !== null;
|
|
}
|
|
|
|
/**
|
|
* Guard para validar Hotspot con condición de visibilidad
|
|
*/
|
|
export function isHotspotWithVisibilityCondition(value: unknown): value is Hotspot & { visible: boolean | object } {
|
|
if (typeof value !== 'object' || value === null) return false;
|
|
const hotspot = value as Hotspot;
|
|
|
|
return 'visible' in hotspot &&
|
|
(typeof hotspot.visible === 'boolean' ||
|
|
(typeof hotspot.visible === 'object' && hotspot.visible !== null));
|
|
}
|
|
|
|
/**
|
|
* Guard para validar Hotspot con color
|
|
*/
|
|
export function isHotspotWithColor(value: unknown): value is Hotspot & { color: string } {
|
|
if (typeof value !== 'object' || value === null) return false;
|
|
const hotspot = value as Hotspot;
|
|
|
|
return 'color' in hotspot && typeof hotspot.color === 'string';
|
|
}
|
|
|
|
/**
|
|
* Guard para validar Hotspot con zIndex
|
|
*/
|
|
export function isHotspotWithZIndex(value: unknown): value is Hotspot & { zIndex: number } {
|
|
if (typeof value !== 'object' || value === null) return false;
|
|
const hotspot = value as Hotspot;
|
|
|
|
return 'zIndex' in hotspot && typeof hotspot.zIndex === 'number';
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// UI STATE GUARDS
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Guard para validar selección de vista en el editor visual
|
|
*/
|
|
export interface VisualSelection {
|
|
kind: 'general' | 'view' | 'hotspot';
|
|
viewId?: string;
|
|
hotspotId?: string;
|
|
}
|
|
|
|
export function isVisualSelection(value: unknown): value is VisualSelection {
|
|
if (typeof value !== 'object' || value === null) return false;
|
|
const sel = value as Record<string, unknown>;
|
|
|
|
return typeof sel.kind === 'string' &&
|
|
['general', 'view', 'hotspot'].includes(sel.kind);
|
|
}
|
|
|
|
/**
|
|
* Guard para validar selección de vista específica
|
|
*/
|
|
export function isViewSelection(value: unknown): value is VisualSelection & { kind: 'view'; viewId: string } {
|
|
if (!isVisualSelection(value)) return false;
|
|
return value.kind === 'view' && typeof value.viewId === 'string';
|
|
}
|
|
|
|
/**
|
|
* Guard para validar selección de hotspot específico
|
|
*/
|
|
export function isHotspotSelection(value: unknown): value is VisualSelection & { kind: 'hotspot'; viewId: string; hotspotId: string } {
|
|
if (!isVisualSelection(value)) return false;
|
|
return value.kind === 'hotspot' &&
|
|
typeof value.viewId === 'string' &&
|
|
typeof value.hotspotId === 'string';
|
|
}
|
|
|
|
/**
|
|
* Guard para validar selección general (configuración del visual)
|
|
*/
|
|
export function isGeneralSelection(value: unknown): value is VisualSelection & { kind: 'general' } {
|
|
if (!isVisualSelection(value)) return false;
|
|
return value.kind === 'general';
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// FORM STATE GUARDS
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Guard para validar estado de formulario con errores
|
|
*/
|
|
export interface FormErrors {
|
|
[key: string]: string | undefined;
|
|
}
|
|
|
|
export function isFormErrors(value: unknown): value is FormErrors {
|
|
if (typeof value !== 'object' || value === null) return false;
|
|
const errors = value as Record<string, unknown>;
|
|
|
|
return Object.values(errors).every(v => v === undefined || typeof v === 'string');
|
|
}
|
|
|
|
/**
|
|
* Guard para validar estado de formulario válido
|
|
*/
|
|
export function isFormValid(errors: FormErrors): boolean {
|
|
return Object.values(errors).every(v => !v);
|
|
} |