@seatsyoo/vue 0.0.0-stage → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Opciones que, si cambian, vuelven a montar el visor: deciden qué se carga y cómo se vende. Los arrays y los
3
+ * objetos se comparan por su valor, no por la referencia: `:channels="['web']"` en cada render no remonta.
4
+ */
5
+ export const OPCIONES_DE_IDENTIDAD = [
6
+ "server",
7
+ "publicKey",
8
+ "event",
9
+ "link",
10
+ "document",
11
+ "season",
12
+ "seasonEvents",
13
+ "channels",
14
+ "pricing",
15
+ "currency",
16
+ "locale",
17
+ "language",
18
+ "maxSelected",
19
+ "session",
20
+ "holdToken",
21
+ "mode",
22
+ "showLegend",
23
+ "showSelectionBar",
24
+ "floor",
25
+ "sectors",
26
+ "view3d",
27
+ "entrance",
28
+ "texts",
29
+ ];
30
+ /**
31
+ * Opciones que se aplican al visor ya montado, sin volver a montarlo: theme y colorBy (setTheme y setColorBy) y, para
32
+ * el modo «manager», los estilos de los estados y las marcas sobre plazas libres (setStatusStyles y setMarks). Se
33
+ * comparan por valor, como las de identidad: un objeto nuevo con lo mismo no llama a nada.
34
+ */
35
+ export const OPCIONES_EN_VIVO = ["theme", "colorBy", "statusStyles", "marks"];
36
+ /** Avisos del visor: siempre llaman a la última función recibida, sin volver a montar. */
37
+ export const AVISOS = [
38
+ "onReady",
39
+ "onChange",
40
+ "onSelect",
41
+ "onDeselect",
42
+ "onError",
43
+ "onHoldExpired",
44
+ "onHoldTokenChange",
45
+ "onFloorChange",
46
+ ];
47
+ export function identidadDe(opciones) {
48
+ const identidad = { load3d: typeof opciones.load3d === "function" };
49
+ for (const clave of OPCIONES_DE_IDENTIDAD) {
50
+ const valor = opciones[clave];
51
+ // El plano puede ser muy grande: no se copia (se compara por valor solo si llega otro objeto).
52
+ identidad[clave] = clave === "document" ? valor : copiaSuperficial(valor);
53
+ }
54
+ return identidad;
55
+ }
56
+ function copiaSuperficial(valor) {
57
+ if (Array.isArray(valor))
58
+ return [...valor];
59
+ if (valor !== null && typeof valor === "object")
60
+ return { ...valor };
61
+ return valor;
62
+ }
63
+ /** Igualdad por valor de datos tipo JSON (cadenas, números, booleanos, null, undefined, arrays y objetos). */
64
+ export function igual(a, b) {
65
+ if (Object.is(a, b))
66
+ return true;
67
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null)
68
+ return false;
69
+ if (Array.isArray(a)) {
70
+ if (!Array.isArray(b) || a.length !== b.length)
71
+ return false;
72
+ for (let i = 0; i < a.length; i++)
73
+ if (!igual(a[i], b[i]))
74
+ return false;
75
+ return true;
76
+ }
77
+ if (Array.isArray(b))
78
+ return false;
79
+ const x = a;
80
+ const y = b;
81
+ const claves = Object.keys(x);
82
+ if (claves.length !== Object.keys(y).length)
83
+ return false;
84
+ for (const k of claves) {
85
+ if (!Object.prototype.hasOwnProperty.call(y, k) || !igual(x[k], y[k]))
86
+ return false;
87
+ }
88
+ return true;
89
+ }
90
+ /**
91
+ * Copia normalizada de datos tipo JSON (listas y objetos, a cualquier profundidad): los objetos salen con
92
+ * Object.prototype (también un diccionario hecho con `Object.create(null)`) y sin las claves que valen `undefined`
93
+ * (`{ color, icon: undefined }` es lo mismo que `{ color }`: el visor no pinta icono en los dos casos).
94
+ *
95
+ * Es la regla de statusStyles y marks, la misma en React, Vue y Angular: lo aplicado en vivo se guarda así copiado, lo
96
+ * que llega se copia igual antes de compararlo, y el visor recibe esa copia. Así cambiarlo en el sitio también cuenta y
97
+ * el visor no comparte el objeto de la página (setMarks repinta lo que difiere de lo que tenía, y si fuera el mismo
98
+ * objeto ya cambiado no vería nada).
99
+ */
100
+ export function copiaDeDatos(valor) {
101
+ if (Array.isArray(valor))
102
+ return valor.map((v) => copiaDeDatos(v));
103
+ if (valor !== null && typeof valor === "object") {
104
+ const entradas = Object.entries(valor).filter(([, v]) => v !== undefined);
105
+ return Object.fromEntries(entradas.map(([k, v]) => [k, copiaDeDatos(v)]));
106
+ }
107
+ return valor;
108
+ }
109
+ /**
110
+ * Las opciones en vivo con que se monta un visor. Sin theme ni colorBy, los del visor por defecto; sin statusStyles ni
111
+ * marks, ninguno (`{}`, que para el visor es lo mismo que no pasarlas).
112
+ */
113
+ export function enVivoDe(opciones) {
114
+ return {
115
+ theme: opciones.theme ?? "light",
116
+ colorBy: opciones.colorBy ?? "category",
117
+ statusStyles: copiaDeDatos(opciones.statusStyles ?? {}),
118
+ marks: copiaDeDatos(opciones.marks ?? {}),
119
+ };
120
+ }
121
+ /** Lo que hay que añadir a las opciones de `render` para montar con estas opciones en vivo (las copias guardadas). */
122
+ export function opcionesEnVivo(opciones, enVivo) {
123
+ return sinIndefinidos({
124
+ theme: opciones.theme,
125
+ colorBy: opciones.colorBy,
126
+ statusStyles: opciones.statusStyles && enVivo.statusStyles,
127
+ marks: opciones.marks && enVivo.marks,
128
+ });
129
+ }
130
+ /**
131
+ * Lleva al visor montado las opciones en vivo que han cambiado (por valor) desde `aplicadas`, y las apunta en
132
+ * `aplicadas`. Quitar una es volver a la de por defecto: el tema claro, el color por categoría, sin estilos ni marcas.
133
+ */
134
+ export function aplicarEnVivo(visor, aplicadas, opciones) {
135
+ const tema = opciones.theme ?? "light";
136
+ if (tema !== aplicadas.theme) {
137
+ visor.setTheme(tema);
138
+ aplicadas.theme = tema;
139
+ }
140
+ const color = opciones.colorBy ?? "category";
141
+ if (color !== aplicadas.colorBy) {
142
+ visor.setColorBy(color);
143
+ aplicadas.colorBy = color;
144
+ }
145
+ // statusStyles y marks: la copia normalizada de lo que llega (copiaDeDatos) contra la de lo aplicado. Un
146
+ // diccionario sin prototipo o con claves que valen undefined, con lo mismo, no llama a nada (como en Angular).
147
+ const estilos = copiaDeDatos(opciones.statusStyles ?? {});
148
+ if (!igual(estilos, aplicadas.statusStyles)) {
149
+ aplicadas.statusStyles = estilos;
150
+ visor.setStatusStyles(estilos);
151
+ }
152
+ const marcas = copiaDeDatos(opciones.marks ?? {});
153
+ if (!igual(marcas, aplicadas.marks)) {
154
+ aplicadas.marks = marcas;
155
+ visor.setMarks(marcas);
156
+ }
157
+ }
158
+ /** Las opciones sin las que valen `undefined` (el visor usa entonces su valor por defecto). */
159
+ export function sinIndefinidos(opciones) {
160
+ return Object.fromEntries(Object.entries(opciones).filter(([, v]) => v !== undefined));
161
+ }
162
+ /** Lo que hace el visor con un error si nadie escucha `onError`: escribirlo en la consola. */
163
+ export function avisarEnConsola(error) {
164
+ console.warn("[seatsyoo]", error.code, error.message);
165
+ }
166
+ /**
167
+ * `render` lanza si faltan opciones (qué enseñar, la clave pública…). En un componente no hay quien lo
168
+ * recoja, así que se convierte en un error con el código `invalid-options`.
169
+ */
170
+ export function errorDeOpciones(err) {
171
+ return { code: "invalid-options", message: err instanceof Error ? err.message : String(err) };
172
+ }
173
+ /**
174
+ * Qué compra el visor montado con estas opciones: el evento, o la temporada (con los eventos del abono parcial, en
175
+ * cualquier orden, como los guarda el visor); `null` si solo enseña el plano (`link`, `document`: no retiene nada).
176
+ */
177
+ function destino(i) {
178
+ // Con el mismo orden que el visor: document, link, season y event.
179
+ if (i.document || i.link)
180
+ return null;
181
+ if (!i.season)
182
+ return `evento:${i.event ?? ""}`;
183
+ const abono = i.seasonEvents?.length ? `@${[...i.seasonEvents].sort().join(",")}` : "";
184
+ return `temporada:${i.season}${abono}`;
185
+ }
186
+ /**
187
+ * Al volver a montar (cambia una opción de identidad), ¿hay que soltar lo que el comprador tiene retenido en el
188
+ * visor que se va?
189
+ *
190
+ * destroy() no suelta lo retenido: así el visor que se monte después en el mismo evento lo recupera (con
191
+ * `session: "continue"`, guarda su token en sessionStorage). Pero si el nuevo no lo va a recuperar (otro evento u
192
+ * otra temporada, otro servidor o clave pública, `session: "start"`, un `holdToken` nuevo, modo «manager»), esas
193
+ * plazas se quedarían bloqueadas para los demás hasta que caduque la retención, sin que la página pueda venderlas
194
+ * ni soltarlas: el comprador ya no las ve y el token del visor nuevo es otro. Entonces se sueltan.
195
+ *
196
+ * Lo retenido con un `holdToken` de la página no se suelta nunca: es de su token y lo lleva su servidor (por
197
+ * ejemplo, un carrito con varias funciones). Tampoco hay nada que soltar en el modo «manager» ni al solo ver.
198
+ */
199
+ export function sueltaAlCambiar(antes, despues) {
200
+ if (antes.holdToken !== undefined || antes.mode === "manager" || destino(antes) === null)
201
+ return false;
202
+ const recupera = despues.holdToken === undefined &&
203
+ despues.mode !== "manager" &&
204
+ (antes.session ?? "continue") === "continue" &&
205
+ (despues.session ?? "continue") === "continue" &&
206
+ antes.server === despues.server &&
207
+ antes.publicKey === despues.publicKey &&
208
+ destino(antes) === destino(despues);
209
+ return !recupera;
210
+ }
package/package.json CHANGED
@@ -1,6 +1,41 @@
1
1
  {
2
2
  "name": "@seatsyoo/vue",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "1.1.0",
4
+ "description": "Componente de Vue para el visor de mapas de asientos de Seatsyoo: elegir butacas, retenerlas y venderlas en tiempo real.",
5
+ "license": "MIT",
6
+ "keywords": [
7
+ "seatsyoo",
8
+ "vue",
9
+ "mapa de asientos",
10
+ "butacas",
11
+ "entradas",
12
+ "seating chart",
13
+ "seat map",
14
+ "tickets"
15
+ ],
16
+ "type": "module",
17
+ "sideEffects": false,
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "default": "./dist/index.js"
22
+ },
23
+ "./package.json": "./package.json"
24
+ },
25
+ "files": [
26
+ "dist/*.js",
27
+ "dist/*.d.ts"
28
+ ],
29
+ "dependencies": {
30
+ "@seatsyoo/visor": "^1.1.0"
31
+ },
32
+ "peerDependencies": {
33
+ "vue": "^3.3.0"
34
+ },
35
+ "main": "./dist/index.js",
36
+ "module": "./dist/index.js",
37
+ "types": "./dist/index.d.ts",
38
+ "publishConfig": {
39
+ "access": "public"
40
+ }
6
41
  }