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.
v/src/vcen-edit/demos/demo.store.svelte.ts

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),
})),
})),
};
}

Powered by TurnKey Linux.