parent
9bf050f990
commit
aa34f88cae
@ -0,0 +1,190 @@
|
|||||||
|
/**
|
||||||
|
* ============================================================================
|
||||||
|
* SVELTE 5 CONFIGURATION STORE
|
||||||
|
* ============================================================================
|
||||||
|
*
|
||||||
|
* Adapter que expone ConfigurationState usando Svelte 5 runes.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ConfigurableObject,
|
||||||
|
ConfigurationCatalog,
|
||||||
|
AttrID,
|
||||||
|
OptionID,
|
||||||
|
SectionID,
|
||||||
|
ViewID,
|
||||||
|
Section,
|
||||||
|
SectionView
|
||||||
|
} from '@/types';
|
||||||
|
import {
|
||||||
|
ConfigurationState,
|
||||||
|
type ConfigurationStateOptions
|
||||||
|
} from '@/state/ConfigurationState';
|
||||||
|
import type { ValidationResult } from '@/state/ValidationState';
|
||||||
|
import type { SelectionStateData } from '@/state/SelectionState';
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// STORE STATE INTERFACE
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export interface ConfigurationStoreState {
|
||||||
|
catalog : ConfigurationCatalog;
|
||||||
|
object : ConfigurableObject;
|
||||||
|
selection : SelectionStateData;
|
||||||
|
validation : ValidationResult | null;
|
||||||
|
currentSection : Section | null;
|
||||||
|
currentSectionId: SectionID | null;
|
||||||
|
currentView : SectionView | null;
|
||||||
|
currentViewId : ViewID | null;
|
||||||
|
isValid : boolean;
|
||||||
|
locale : string; // Idioma actual
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// CONFIGURATION STORE (SVELTE 5 RUNES)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crea un store de configuración reactivo para Svelte 5
|
||||||
|
*/
|
||||||
|
export function createConfigurationStore(options: ConfigurationStateOptions) {
|
||||||
|
// Estado core (framework agnostic)
|
||||||
|
const coreState = new ConfigurationState(options);
|
||||||
|
|
||||||
|
// Función helper para obtener el estado actual
|
||||||
|
function getStoreState(): ConfigurationStoreState {
|
||||||
|
return {
|
||||||
|
catalog : coreState.getCatalog(),
|
||||||
|
object : coreState.getObject(),
|
||||||
|
selection : coreState.getAllSelections(),
|
||||||
|
validation : coreState.getValidationResult(),
|
||||||
|
currentSection : coreState.getCurrentSection(),
|
||||||
|
currentSectionId: coreState.getCurrentSectionId(),
|
||||||
|
currentView : coreState.getCurrentView(),
|
||||||
|
currentViewId : coreState.getCurrentViewId(),
|
||||||
|
isValid : coreState.isValid(),
|
||||||
|
locale : coreState.getLocale()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ SVELTE 5: Usar $state() para reactividad
|
||||||
|
let state = $state<ConfigurationStoreState>(getStoreState());
|
||||||
|
|
||||||
|
// Suscribirse a cambios del estado core
|
||||||
|
coreState.subscribe(() => {
|
||||||
|
state = getStoreState();
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// PUBLIC API
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
return {
|
||||||
|
// ✅ SVELTE 5: Getter reactivo
|
||||||
|
get current() {
|
||||||
|
return state;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Selection actions
|
||||||
|
selectOption(attrId: AttrID, optionId: OptionID) {
|
||||||
|
coreState.selectOption(attrId, optionId);
|
||||||
|
},
|
||||||
|
|
||||||
|
clearSelection(attrId: AttrID) {
|
||||||
|
coreState.clearSelection(attrId);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Navigation actions
|
||||||
|
setCurrentSection(sectionId: SectionID) {
|
||||||
|
coreState.setCurrentSection(sectionId);
|
||||||
|
},
|
||||||
|
|
||||||
|
setCurrentView(viewId: ViewID) {
|
||||||
|
coreState.setCurrentView(viewId);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Locale actions
|
||||||
|
getLocale() {
|
||||||
|
return coreState.getLocale();
|
||||||
|
},
|
||||||
|
|
||||||
|
setLocale(locale: string) {
|
||||||
|
coreState.setLocale(locale as any);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Translation helper
|
||||||
|
t(i18nString: any) {
|
||||||
|
const locale = coreState.getLocale();
|
||||||
|
if (typeof i18nString === 'string') return i18nString;
|
||||||
|
return i18nString[locale] || i18nString.es || i18nString.en || '';
|
||||||
|
},
|
||||||
|
|
||||||
|
// Validation actions
|
||||||
|
validate() {
|
||||||
|
return coreState.validate();
|
||||||
|
},
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
getSelection(attrId: AttrID) {
|
||||||
|
return coreState.getSelection(attrId);
|
||||||
|
},
|
||||||
|
|
||||||
|
getAllowedValues(attrId: AttrID) {
|
||||||
|
return coreState.getAllowedValues(attrId);
|
||||||
|
},
|
||||||
|
|
||||||
|
isRequired(attrId: AttrID) {
|
||||||
|
return coreState.isRequired(attrId);
|
||||||
|
},
|
||||||
|
|
||||||
|
hasErrorsForAttribute(attrId: AttrID) {
|
||||||
|
return coreState.hasErrorsForAttribute(attrId);
|
||||||
|
},
|
||||||
|
|
||||||
|
getViolationsForAttribute(attrId: AttrID) {
|
||||||
|
return coreState.getViolationsForAttribute(attrId);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Persistence
|
||||||
|
toJSON() {
|
||||||
|
return coreState.toJSON();
|
||||||
|
},
|
||||||
|
|
||||||
|
fromJSON(data: ReturnType<typeof coreState.toJSON>) {
|
||||||
|
coreState.fromJSON(data);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Utilities
|
||||||
|
reset() {
|
||||||
|
coreState.reset();
|
||||||
|
},
|
||||||
|
|
||||||
|
undo() {
|
||||||
|
return coreState.undo();
|
||||||
|
},
|
||||||
|
|
||||||
|
// Direct access to state (for advanced use)
|
||||||
|
_coreState: coreState
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// DERIVED VALUES (SVELTE 5 $DERIVED)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper para crear valores derivados en Svelte 5
|
||||||
|
*
|
||||||
|
* Uso en componentes:
|
||||||
|
* ```ts
|
||||||
|
* const config = createConfigurationStore({...});
|
||||||
|
* const currentSection = $derived(config.current.currentSection);
|
||||||
|
* const isValid = $derived(config.current.isValid);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// TYPE EXPORTS
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export type ConfigurationStore = ReturnType<typeof createConfigurationStore>;
|
||||||
@ -1,197 +0,0 @@
|
|||||||
/**
|
|
||||||
* ============================================================================
|
|
||||||
* SVELTE CONFIGURATION STORE
|
|
||||||
* ============================================================================
|
|
||||||
*
|
|
||||||
* Adapter que expone ConfigurationState como Svelte store.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { writable, derived, type Readable } from 'svelte/store';
|
|
||||||
import type {
|
|
||||||
ConfigurableObject,
|
|
||||||
ConfigurationCatalog,
|
|
||||||
AttrID,
|
|
||||||
OptionID,
|
|
||||||
SectionID,
|
|
||||||
ViewID,
|
|
||||||
Section,
|
|
||||||
SectionView
|
|
||||||
} from '@/types';
|
|
||||||
import {
|
|
||||||
ConfigurationState,
|
|
||||||
type ConfigurationStateOptions
|
|
||||||
} from '@/state/ConfigurationState';
|
|
||||||
import type { ValidationResult } from '@/state/ValidationState';
|
|
||||||
import type { SelectionStateData } from '@/state/SelectionState';
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// STORE STATE INTERFACE
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
export interface ConfigurationStoreState {
|
|
||||||
catalog : ConfigurationCatalog;
|
|
||||||
object : ConfigurableObject;
|
|
||||||
selection : SelectionStateData;
|
|
||||||
validation : ValidationResult | null;
|
|
||||||
currentSection : Section | null;
|
|
||||||
currentSectionId: SectionID | null;
|
|
||||||
currentView : SectionView | null;
|
|
||||||
currentViewId : ViewID | null;
|
|
||||||
isValid : boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// CONFIGURATION STORE
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Crea un store de configuración reactivo para Svelte
|
|
||||||
*/
|
|
||||||
export function createConfigurationStore(options: ConfigurationStateOptions) {
|
|
||||||
// Estado core (framework agnostic)
|
|
||||||
const state = new ConfigurationState(options);
|
|
||||||
|
|
||||||
// Store interno con versión para forzar actualizaciones
|
|
||||||
const { subscribe, set, update } = writable(getStoreState());
|
|
||||||
|
|
||||||
// Función helper para obtener el estado actual del store
|
|
||||||
function getStoreState(): ConfigurationStoreState {
|
|
||||||
return {
|
|
||||||
catalog : state.getCatalog(),
|
|
||||||
object : state.getObject(),
|
|
||||||
selection : state.getAllSelections(),
|
|
||||||
validation : state.getValidationResult(),
|
|
||||||
currentSection : state.getCurrentSection(),
|
|
||||||
currentSectionId: state.getCurrentSectionId(),
|
|
||||||
currentView : state.getCurrentView(),
|
|
||||||
currentViewId : state.getCurrentViewId(),
|
|
||||||
isValid : state.isValid()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Suscribirse a cambios del estado core
|
|
||||||
state.subscribe(() => {
|
|
||||||
set(getStoreState());
|
|
||||||
});
|
|
||||||
|
|
||||||
// ========================================================================
|
|
||||||
// PUBLIC API
|
|
||||||
// ========================================================================
|
|
||||||
|
|
||||||
return {
|
|
||||||
subscribe,
|
|
||||||
|
|
||||||
// Selection actions
|
|
||||||
selectOption: (attrId: AttrID, optionId: OptionID) => {
|
|
||||||
state.selectOption(attrId, optionId);
|
|
||||||
},
|
|
||||||
|
|
||||||
clearSelection: (attrId: AttrID) => {
|
|
||||||
state.clearSelection(attrId);
|
|
||||||
},
|
|
||||||
|
|
||||||
// Navigation actions
|
|
||||||
setCurrentSection: (sectionId: SectionID) => {
|
|
||||||
state.setCurrentSection(sectionId);
|
|
||||||
},
|
|
||||||
|
|
||||||
setCurrentView: (viewId: ViewID) => {
|
|
||||||
state.setCurrentView(viewId);
|
|
||||||
},
|
|
||||||
|
|
||||||
// Validation actions
|
|
||||||
validate: () => {
|
|
||||||
return state.validate();
|
|
||||||
},
|
|
||||||
|
|
||||||
// Helpers
|
|
||||||
getSelection: (attrId: AttrID) => {
|
|
||||||
return state.getSelection(attrId);
|
|
||||||
},
|
|
||||||
|
|
||||||
getAllowedValues: (attrId: AttrID) => {
|
|
||||||
return state.getAllowedValues(attrId);
|
|
||||||
},
|
|
||||||
|
|
||||||
isRequired: (attrId: AttrID) => {
|
|
||||||
return state.isRequired(attrId);
|
|
||||||
},
|
|
||||||
|
|
||||||
hasErrorsForAttribute: (attrId: AttrID) => {
|
|
||||||
return state.hasErrorsForAttribute(attrId);
|
|
||||||
},
|
|
||||||
|
|
||||||
getViolationsForAttribute: (attrId: AttrID) => {
|
|
||||||
return state.getViolationsForAttribute(attrId);
|
|
||||||
},
|
|
||||||
|
|
||||||
// Persistence
|
|
||||||
toJSON: () => state.toJSON(),
|
|
||||||
|
|
||||||
fromJSON: (data: ReturnType<typeof state.toJSON>) => {
|
|
||||||
state.fromJSON(data);
|
|
||||||
},
|
|
||||||
|
|
||||||
// Utilities
|
|
||||||
reset: () => state.reset(),
|
|
||||||
undo: () => state.undo(),
|
|
||||||
|
|
||||||
// Direct access to state (for advanced use)
|
|
||||||
_state: state
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// DERIVED STORES (COMPUTED VALUES)
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Derived store para la sección actual
|
|
||||||
*/
|
|
||||||
export function deriveCurrentSection(
|
|
||||||
configStore: ReturnType<typeof createConfigurationStore>
|
|
||||||
): Readable<Section | null> {
|
|
||||||
return derived(configStore, $config => $config.currentSection);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Derived store para la vista actual
|
|
||||||
*/
|
|
||||||
export function deriveCurrentView(
|
|
||||||
configStore: ReturnType<typeof createConfigurationStore>
|
|
||||||
): Readable<SectionView | null> {
|
|
||||||
return derived(configStore, $config => $config.currentView);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Derived store para validación
|
|
||||||
*/
|
|
||||||
export function deriveValidation(
|
|
||||||
configStore: ReturnType<typeof createConfigurationStore>
|
|
||||||
): Readable<ValidationResult | null> {
|
|
||||||
return derived(configStore, $config => $config.validation);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Derived store para verificar si es válido
|
|
||||||
*/
|
|
||||||
export function deriveIsValid(
|
|
||||||
configStore: ReturnType<typeof createConfigurationStore>
|
|
||||||
): Readable<boolean> {
|
|
||||||
return derived(configStore, $config => $config.isValid);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Derived store para selección
|
|
||||||
*/
|
|
||||||
export function deriveSelection(
|
|
||||||
configStore: ReturnType<typeof createConfigurationStore>
|
|
||||||
): Readable<SelectionStateData> {
|
|
||||||
return derived(configStore, $config => $config.selection);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// TYPE EXPORTS
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
export type ConfigurationStore = ReturnType<typeof createConfigurationStore>;
|
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
import type {I18nString, ObjectID} from "@/types";
|
||||||
|
|
||||||
|
|
||||||
|
export const ERRORS_CONFIG = {
|
||||||
|
OBJECT_NOT_FOUND: (objectId: ObjectID): I18nString => ({
|
||||||
|
es: `Objeto '${objectId}' no encontrado en el catálogo`,
|
||||||
|
en: `Object '${objectId}' not found in catalog`
|
||||||
|
})
|
||||||
|
} as const;
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* ============================================================================
|
||||||
|
* WARNINGS - Configuration State
|
||||||
|
* ============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { I18nString, AttrID, OptionID, SectionID, ViewID } from '@/types';
|
||||||
|
|
||||||
|
export const WARNINGS_CONFIG = {
|
||||||
|
OPTION_NOT_ALLOWED: (optionId: OptionID, attrId: AttrID): I18nString => ({
|
||||||
|
es: `La opción '${optionId}' no está permitida para el atributo '${attrId}'`,
|
||||||
|
en: `Option '${optionId}' is not allowed for attribute '${attrId}'`
|
||||||
|
}),
|
||||||
|
|
||||||
|
SECTION_NOT_FOUND: (sectionId: SectionID): I18nString => ({
|
||||||
|
es: `Sección '${sectionId}' no encontrada`,
|
||||||
|
en: `Section '${sectionId}' not found`
|
||||||
|
}),
|
||||||
|
|
||||||
|
VIEW_NOT_FOUND: (viewId: ViewID): I18nString => ({
|
||||||
|
es: `Vista '${viewId}' no encontrada en la sección actual`,
|
||||||
|
en: `View '${viewId}' not found in current section`
|
||||||
|
})
|
||||||
|
} as const;
|
||||||
@ -0,0 +1,70 @@
|
|||||||
|
// LocaleState.ts
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ============================================================================
|
||||||
|
* LOCALE STATE - Framework Agnostic
|
||||||
|
* ============================================================================
|
||||||
|
*
|
||||||
|
* Gestiona el idioma actual de la aplicación.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { SupportedLocale } from '@/types';
|
||||||
|
|
||||||
|
export interface LocaleStateChangeEvent {
|
||||||
|
previousLocale : SupportedLocale;
|
||||||
|
newLocale : SupportedLocale;
|
||||||
|
timestamp : number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Estado del idioma actual
|
||||||
|
*/
|
||||||
|
export class LocaleState {
|
||||||
|
private currentLocale : SupportedLocale;
|
||||||
|
private listeners : Set<(event: LocaleStateChangeEvent) => void> = new Set();
|
||||||
|
|
||||||
|
constructor(initialLocale: SupportedLocale = 'es') {
|
||||||
|
this.currentLocale = initialLocale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtiene el idioma actual
|
||||||
|
*/
|
||||||
|
get(): SupportedLocale {
|
||||||
|
return this.currentLocale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Establece el idioma actual
|
||||||
|
*/
|
||||||
|
set(locale: SupportedLocale): void {
|
||||||
|
if (this.currentLocale === locale) return;
|
||||||
|
|
||||||
|
const event: LocaleStateChangeEvent = {
|
||||||
|
previousLocale : this.currentLocale,
|
||||||
|
newLocale : locale,
|
||||||
|
timestamp : Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
|
this.currentLocale = locale;
|
||||||
|
this.notifyListeners(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suscribe un listener a cambios de idioma
|
||||||
|
*/
|
||||||
|
subscribe(listener: (event: LocaleStateChangeEvent) => void): () => void {
|
||||||
|
this.listeners.add(listener);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
this.listeners.delete(listener);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifica a todos los listeners
|
||||||
|
*/
|
||||||
|
private notifyListeners(event: LocaleStateChangeEvent): void {
|
||||||
|
this.listeners.forEach(listener => listener(event));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
// ============================================================================
|
||||||
|
// HOTSPOT
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import type {AttrID, HotspotID, I18nString, JsonLogic} from "@/types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Punto interactivo en una vista
|
||||||
|
*/
|
||||||
|
export interface Hotspot {
|
||||||
|
/** ID único del hotspot */
|
||||||
|
id: HotspotID;
|
||||||
|
|
||||||
|
/** Nombre descriptivo */
|
||||||
|
name: I18nString;
|
||||||
|
/** Posición X en porcentaje (0-100) */
|
||||||
|
x: number;
|
||||||
|
|
||||||
|
/** Posición Y en porcentaje (0-100) */
|
||||||
|
y: number;
|
||||||
|
|
||||||
|
/** Radio del hotspot en píxeles */
|
||||||
|
radius?: number;
|
||||||
|
|
||||||
|
/** Color del hotspot */
|
||||||
|
color?: string;
|
||||||
|
|
||||||
|
/** Icono del hotspot */
|
||||||
|
icon?: string;
|
||||||
|
|
||||||
|
/** Tipo de scope al que referencia */
|
||||||
|
scopeType: 'global' | 'section';
|
||||||
|
|
||||||
|
/** ID del atributo que este hotspot controla */
|
||||||
|
targetAttributeId: AttrID;
|
||||||
|
|
||||||
|
/** Si es visible (puede ser condicional) */
|
||||||
|
visible?: boolean | JsonLogic;
|
||||||
|
|
||||||
|
/** Tooltip al hacer hover */
|
||||||
|
tooltip?: I18nString;
|
||||||
|
|
||||||
|
/** Orden z-index para superposición */
|
||||||
|
zIndex?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
|
||||||
|
export * from './logger'
|
||||||
|
|
||||||
Loading…
Reference in new issue