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.
294 lines
11 KiB
294 lines
11 KiB
import { createSession } from '@/vcen/lib/session.ts';
|
|
import { buildDependencyGraph } from '@/vcen/lib/graph.ts';
|
|
import type { Cat, Obj, Att, Sec, Opt, OptID } from '@/vcen/lib/types';
|
|
import type { Rule } from '@/vcen/lib/types';
|
|
import type { DependencyGraph } from '@/vcen/lib/types';
|
|
import type { Session } from '@/vcen/lib/session.ts';
|
|
import {evaluateCat} from "@/vcen/lib/engine.ts";
|
|
|
|
|
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
|
|
export type DomainKind = 'string' | 'number' | 'boolean' | 'option' | 'date' | 'time' | 'datetime' | 'currency';
|
|
|
|
// ─── Store ────────────────────────────────────────────────────────────────────
|
|
|
|
export function createDemoStore() {
|
|
|
|
// Cat mutable
|
|
let cat = $state<Cat>(makeFreshCat());
|
|
|
|
// Runtime session — se reconstruye cuando cambia el cat
|
|
let session = $state<Session | null>(null);
|
|
let graphError = $state<string | null>(null);
|
|
|
|
// Grafo para visualización
|
|
let graph = $state<DependencyGraph | null>(null);
|
|
|
|
// Resultado reactivo del motor — se actualiza con cada setValue
|
|
let result = $state(evaluateCat(cat));
|
|
|
|
// Panel activo
|
|
let activePanel = $state<'editor' | 'runtime' | 'graph' | 'json'>('editor');
|
|
|
|
// Att seleccionado en el editor
|
|
let selectedAttId = $state<string | null>(null);
|
|
|
|
// ── Rebuild session ───────────────────────────────────────────────────────
|
|
|
|
function rebuildSession() {
|
|
try {
|
|
graph = buildDependencyGraph(cat);
|
|
session = createSession(cat);
|
|
result = session.getResult();
|
|
graphError = null;
|
|
// Suscribir para mantener result reactivo
|
|
session.subscribe(r => { result = r; });
|
|
} catch (e: any) {
|
|
graphError = e.message ?? 'Error en el grafo de dependencias';
|
|
session = null;
|
|
graph = null;
|
|
}
|
|
}
|
|
|
|
// ── Cat mutations ─────────────────────────────────────────────────────────
|
|
|
|
function addAtt(kind: DomainKind) {
|
|
const id = `at_${Date.now()}` as Att['id'];
|
|
if (kind === 'option') {
|
|
const defaultStrs = ['opcion1', 'opcion2'];
|
|
const newOpts = defaultStrs.map(strToOpt);
|
|
const optIds = newOpts.map(o => o.id);
|
|
const att: Att = {
|
|
id,
|
|
name: 'Nuevo atributo',
|
|
value: { domain: makeDomain('option', optIds), value: optIds[0] ?? null },
|
|
};
|
|
cat = {
|
|
...cat,
|
|
opts: [...cat.opts, ...newOpts],
|
|
objs: cat.objs.map((o, i) => i === 0 ? { ...o, atts: [...o.atts, att] } : o),
|
|
};
|
|
} else {
|
|
const att: Att = {
|
|
id,
|
|
name: 'Nuevo atributo',
|
|
value: { domain: makeDomain(kind, []), value: defaultValue(kind) },
|
|
};
|
|
cat = {
|
|
...cat,
|
|
objs: cat.objs.map((o, i) => i === 0 ? { ...o, atts: [...o.atts, att] } : o),
|
|
};
|
|
}
|
|
selectedAttId = id;
|
|
rebuildSession();
|
|
}
|
|
|
|
function removeAtt(attId: string) {
|
|
cat = {
|
|
...cat,
|
|
objs: cat.objs.map(o => ({
|
|
...o,
|
|
atts: o.atts.filter(a => a.id !== attId),
|
|
secs: o.secs.map(s => ({
|
|
...s,
|
|
atts: s.atts.filter(a => a.id !== attId),
|
|
})),
|
|
})),
|
|
};
|
|
if (selectedAttId === attId) selectedAttId = null;
|
|
rebuildSession();
|
|
}
|
|
|
|
function updateAttName(attId: string, name: string) {
|
|
cat = updateAtt(cat, attId, a => ({ ...a, name }));
|
|
rebuildSession();
|
|
}
|
|
|
|
function updateAttDomain(attId: string, kind: DomainKind) {
|
|
if (kind === 'option') {
|
|
// Crear opts por defecto y referenciarlas en el dominio
|
|
const defaultStrs = ['opcion1', 'opcion2'];
|
|
const newOpts = defaultStrs.map(strToOpt);
|
|
const newOptIds = newOpts.map(o => o.id);
|
|
cat = {
|
|
...updateAtt(cat, attId, a => ({
|
|
...a,
|
|
value: { domain: makeDomain('option', newOptIds), value: newOptIds[0] ?? null },
|
|
})),
|
|
opts: [...cat.opts, ...newOpts],
|
|
};
|
|
} else {
|
|
cat = updateAtt(cat, attId, a => ({
|
|
...a,
|
|
value: { domain: makeDomain(kind, []), value: defaultValue(kind) },
|
|
}));
|
|
}
|
|
rebuildSession();
|
|
}
|
|
|
|
function updateAttOptions(attId: string, optStrs: string[]) {
|
|
// Convertir strings a Opt + ids
|
|
const existingByVal = new Map(cat.opts.map(o => [String(o.value.value), o]));
|
|
const updatedOpts: Opt[] = [];
|
|
const optIds: OptID[] = [];
|
|
for (const s of optStrs) {
|
|
const existing = existingByVal.get(s);
|
|
const opt = existing ?? strToOpt(s);
|
|
updatedOpts.push(opt);
|
|
optIds.push(opt.id);
|
|
}
|
|
// Merge: mantener opts no afectadas + agregar/sustituir las de este att
|
|
const affectedIds = new Set(optIds);
|
|
const otherOpts = cat.opts.filter(o => !affectedIds.has(o.id));
|
|
cat = {
|
|
...updateAtt(cat, attId, a => {
|
|
const currentVal = a.value.value as OptID | null;
|
|
const value = optIds.includes(currentVal as OptID) ? currentVal : (optIds[0] ?? null);
|
|
return { ...a, value: { domain: makeDomain('option', optIds), value } };
|
|
}),
|
|
opts: [...otherOpts, ...updatedOpts],
|
|
};
|
|
rebuildSession();
|
|
}
|
|
|
|
function updateAttDefaultValue(attId: string, value: unknown) {
|
|
cat = updateAtt(cat, attId, a => ({
|
|
...a,
|
|
value: { ...a.value, value: value as any },
|
|
}));
|
|
rebuildSession();
|
|
}
|
|
|
|
function setAttRules(attId: string, rules: Rule[]) {
|
|
cat = updateAtt(cat, attId, a => ({
|
|
...a,
|
|
rules: rules.length > 0 ? { rules } : undefined,
|
|
}));
|
|
rebuildSession();
|
|
}
|
|
|
|
// ── Runtime ───────────────────────────────────────────────────────────────
|
|
|
|
function setRuntimeValue(attPath: any, value: unknown) {
|
|
session?.setValue(attPath, value);
|
|
}
|
|
|
|
// ── Queries ───────────────────────────────────────────────────────────────
|
|
|
|
function getAtts(): Att[] {
|
|
return cat.objs[0]?.atts ?? [];
|
|
}
|
|
|
|
function getSelectedAtt(): Att | null {
|
|
if (!selectedAttId) return null;
|
|
return getAtts().find(a => a.id === selectedAttId) ?? null;
|
|
}
|
|
|
|
function getObj(): Obj {
|
|
return cat.objs[0]!;
|
|
}
|
|
|
|
// ── Init ──────────────────────────────────────────────────────────────────
|
|
rebuildSession();
|
|
|
|
return {
|
|
get cat() { return cat; },
|
|
get session() { return session; },
|
|
get result() { return result; },
|
|
get graph() { return graph; },
|
|
get graphError() { return graphError; },
|
|
get activePanel() { return activePanel; },
|
|
get selectedAttId(){ return selectedAttId; },
|
|
|
|
set activePanel(v: typeof activePanel) { activePanel = v; },
|
|
set selectedAttId(v: string | null) { selectedAttId = v; },
|
|
|
|
getAtts,
|
|
getSelectedAtt,
|
|
getObj,
|
|
addAtt,
|
|
removeAtt,
|
|
updateAttName,
|
|
updateAttDomain,
|
|
updateAttOptions,
|
|
setAttRules,
|
|
updateAttDefaultValue,
|
|
setRuntimeValue,
|
|
rebuildSession,
|
|
};
|
|
}
|
|
|
|
export type DemoStore = ReturnType<typeof createDemoStore>;
|
|
|
|
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
|
|
function strToOpt(s: string): Opt {
|
|
return {
|
|
id: `op_${s.replace(/\s+/g, '_')}` as OptID,
|
|
value: { kind: 'string', value: s },
|
|
};
|
|
}
|
|
|
|
function makeFreshCat(): Cat {
|
|
const colorOpts = ['rojo', 'azul', 'negro'].map(strToOpt);
|
|
const colorIds = colorOpts.map(o => o.id);
|
|
return {
|
|
opts : colorOpts,
|
|
optCategories: [],
|
|
id: 'ct_demo' as Cat['id'],
|
|
name: 'Catálogo demo',
|
|
lang: 'es',
|
|
langs: ['es'],
|
|
atts: [],
|
|
objs: [{
|
|
id: 'ob_demo' as Obj['id'],
|
|
name: 'Objeto demo',
|
|
atts: [
|
|
{
|
|
id: 'at_color' as Att['id'],
|
|
name: 'Color',
|
|
value: { domain: { kind: 'option', mode: 'list', list: colorIds.map(id => ({ optId: id })) }, value: colorIds[0] },
|
|
},
|
|
{
|
|
id: 'at_precio' as Att['id'],
|
|
name: 'Precio',
|
|
value: { domain: { kind: 'number', mode: 'input' }, value: 0 },
|
|
},
|
|
],
|
|
secs: [],
|
|
}],
|
|
};
|
|
}
|
|
|
|
function makeDomain(kind: DomainKind, optIds: OptID[] = []): any {
|
|
if (kind === 'option') return { kind, mode: 'list', list: optIds.map(id => ({ optId: id })) };
|
|
if (kind === 'number' || kind === 'currency') return { kind, mode: 'input' };
|
|
if (kind === 'boolean') return { kind, mode: 'input' };
|
|
if (kind === 'date' || kind === 'time' || kind === 'datetime') return { kind, mode: 'input' };
|
|
return { kind, mode: 'input' };
|
|
}
|
|
|
|
function defaultValue(kind: DomainKind): any {
|
|
if (kind === 'number' || kind === 'currency') return 0;
|
|
if (kind === 'boolean') return false;
|
|
if (kind === 'option') return null;
|
|
if (kind === 'date') return new Date().toISOString().split('T')[0];
|
|
if (kind === 'time') return '08:00';
|
|
if (kind === 'datetime') return new Date().toISOString().slice(0, 16);
|
|
return '';
|
|
}
|
|
|
|
function updateAtt(cat: Cat, attId: string, fn: (a: Att) => Att): Cat {
|
|
return {
|
|
...cat,
|
|
objs: cat.objs.map(o => ({
|
|
...o,
|
|
atts: o.atts.map(a => a.id === attId ? fn(a) : a),
|
|
secs: o.secs.map(s => ({
|
|
...s,
|
|
atts: s.atts.map(a => a.id === attId ? fn(a) : a),
|
|
})),
|
|
})),
|
|
};
|
|
} |