@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["svelte.svelte-vscode"]
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>web-local</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "web-local",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
||||
"@tsconfig/svelte": "^5.0.5",
|
||||
"@types/node": "^24.6.0",
|
||||
"svelte": "^5.39.6",
|
||||
"svelte-check": "^4.3.2",
|
||||
"typescript": "~5.9.3",
|
||||
"vite": "^7.1.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.1.14",
|
||||
"jspdf": "^3.0.3",
|
||||
"tailwindcss": "^4.1.14"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 340 KiB |
|
After Width: | Height: | Size: 305 KiB |
|
After Width: | Height: | Size: 326 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 2.3 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 2.5 MiB |
|
After Width: | Height: | Size: 2.5 MiB |
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 2.4 MiB |
|
After Width: | Height: | Size: 2.4 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 2.5 MiB |
|
After Width: | Height: | Size: 2.6 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 2.1 MiB |
|
After Width: | Height: | Size: 2.8 MiB |
|
After Width: | Height: | Size: 2.7 MiB |
|
After Width: | Height: | Size: 374 KiB |
|
After Width: | Height: | Size: 370 KiB |
|
After Width: | Height: | Size: 384 KiB |
|
After Width: | Height: | Size: 423 KiB |
|
After Width: | Height: | Size: 242 KiB |
|
After Width: | Height: | Size: 309 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 326 KiB |
|
After Width: | Height: | Size: 335 KiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 9.4 MiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 4.5 MiB |
|
After Width: | Height: | Size: 269 KiB |
@ -0,0 +1,19 @@
|
||||
<script lang="ts" xmlns="http://www.w3.org/1999/html">
|
||||
import StoreTestTW from './lib/components/StoreTestTW.svelte'
|
||||
import PdfTest from "./lib/components/PdfTest.svelte";
|
||||
</script>
|
||||
|
||||
<main>
|
||||
|
||||
|
||||
<div>
|
||||
<StoreTestTW />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<PdfTest/>
|
||||
</div>
|
||||
|
||||
|
||||
</main>
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
@ -0,0 +1,21 @@
|
||||
import { type Empresa } from './data_types'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export const empresaData : Empresa = {
|
||||
nombre: "Ebrosa",
|
||||
logos : {
|
||||
'default' : "/imgs/logo_blanco.png",
|
||||
'blanco ' : "/imgs/logo_blanco.png",
|
||||
'negro' : "/imgs/logo_negro.png",
|
||||
'living' : "/imgs/logo_ebrosa_living.png",
|
||||
'living_negro' : "/imgs/logo_ebrosa_living_negro.png",
|
||||
'living_blanco': "/imgs/logo_ebrosa_living_blanco.png"
|
||||
},
|
||||
textos : {
|
||||
'slogan' : "Slogan empresa",
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,297 @@
|
||||
import { type Proyecto } from "./data_types";
|
||||
|
||||
|
||||
|
||||
|
||||
export const proyecto : Proyecto = {
|
||||
nombre : 'Quaddro2',
|
||||
imagesEstancias : 'imgs/quaddro2/est/',
|
||||
logos : {
|
||||
'default' : "/imgs/quaddro2/logo_default.png"
|
||||
},
|
||||
textos : {
|
||||
'slogan' : "Living",
|
||||
'descripcion' : "Esto es la descripción"
|
||||
},
|
||||
|
||||
materiales: {
|
||||
'suelo_viggo_fresno' : {
|
||||
nombre : 'Viggo Fresno',
|
||||
ref : 'suelo_viggo_fresno',
|
||||
imagen : '/imgs/quaddro2/mat/suelo_vigo_fresno.jpg'
|
||||
},
|
||||
'suelo_viggo_roble' : {
|
||||
nombre : 'Viggo Roble',
|
||||
ref : 'suelo_viggo_roble',
|
||||
imagen : '/imgs/quaddro2/mat/suelo_vigo_roble.jpg'
|
||||
},
|
||||
'mueble_roble' : {
|
||||
nombre : "Mueble Roble",
|
||||
ref : 'mueble_roble',
|
||||
imagen : '/imgs/quaddro2/mat/mueble_roble.jpg'
|
||||
},
|
||||
'mueble_blanco' : {
|
||||
nombre : 'Mueble Blanco',
|
||||
ref : 'mueble_blanco',
|
||||
imagen : '/imgs/quaddro2/mat/mueble_blanco.jpg'
|
||||
},
|
||||
'mueble_topo' : {
|
||||
nombre : 'Mueble_Blanco',
|
||||
ref : 'mueble_topo',
|
||||
imagen : '/imgs/quaddro2/mat/mueble_topo.jpg'
|
||||
},
|
||||
'ducha_viggo_roble' : {
|
||||
nombre : 'Ducha Viggo Roble',
|
||||
ref : 'ducha_viggo_roble',
|
||||
imagen : '/imgs/quaddro2/mat/ducha_viggo_roble.jpg'
|
||||
},
|
||||
'ducha_viggo_fresno': {
|
||||
nombre : 'Ducha Viggo Fresno',
|
||||
ref : 'ducha_viggo_fresno',
|
||||
imagen : '/imgs/quaddro2/mat/ducha_viggo_fresno.jpg'
|
||||
},
|
||||
'alicatado_agar' : {
|
||||
nombre : 'Alicatado Agar Mate',
|
||||
ref : 'alicatado_agar',
|
||||
imagen : '/imgs/quaddro2/mat/alicatado_agar.jpg'
|
||||
},
|
||||
'madera_palilleria' : {
|
||||
nombre : 'Palilleria',
|
||||
ref : 'madera_palilleria',
|
||||
imagen : '/imgs/quaddro2/mat/madera_palilleria.png'
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
estancias : {
|
||||
|
||||
// Salon
|
||||
'salon' : {
|
||||
nombre : 'Salón',
|
||||
imagen : null,
|
||||
opciones: [
|
||||
{
|
||||
nombre : "Suelos",
|
||||
opciones: [
|
||||
{
|
||||
nombre : 'Suelo Viggo Fresno',
|
||||
value : 'sufr',
|
||||
material: 'suelo_viggo_fresno',
|
||||
},
|
||||
{
|
||||
nombre : 'Suelo Viggo Roble',
|
||||
value : 'suro',
|
||||
material: 'suelo_viggo_fresno',
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// Cocina
|
||||
'cocina' : {
|
||||
nombre : 'Cocina',
|
||||
imagen : null,
|
||||
opciones: [
|
||||
{
|
||||
nombre : 'Disposiciones',
|
||||
opciones: [
|
||||
{
|
||||
nombre : 'Cocina Cerrada',
|
||||
value : 'dice',
|
||||
material: null,
|
||||
},
|
||||
{
|
||||
nombre : 'Cocina Abierta',
|
||||
value : 'diab',
|
||||
material: null,
|
||||
closed : true,
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
nombre : 'Suelos',
|
||||
opciones: [
|
||||
{
|
||||
nombre : 'Suelo Viggo Fresno',
|
||||
value : 'sufr',
|
||||
material: 'suelo_viggo_fresno',
|
||||
},
|
||||
{
|
||||
nombre : 'Suelo Viggo Roble',
|
||||
value : 'suro',
|
||||
material: 'suelo_viggo_roble'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
nombre : 'Encimeras',
|
||||
opciones: [
|
||||
{
|
||||
nombre : 'Encimera Negra',
|
||||
value : 'enne',
|
||||
material: null,
|
||||
},
|
||||
{
|
||||
nombre : 'Encimera Blanca',
|
||||
value : 'enbl',
|
||||
material: null,
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
nombre : 'Revestimiento',
|
||||
opciones: [
|
||||
{
|
||||
nombre : 'Sin Palilleria',
|
||||
value : 'resi',
|
||||
material: null,
|
||||
},
|
||||
{
|
||||
nombre : 'Con Palilleria',
|
||||
value : 'repa',
|
||||
material: null,
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
nombre : 'Mobiliario',
|
||||
opciones: [
|
||||
{
|
||||
nombre : 'Mueble Blanco',
|
||||
value : 'mubl',
|
||||
material: null,
|
||||
},
|
||||
{
|
||||
nombre : 'Mueble Topo',
|
||||
value : 'muto',
|
||||
material: null,
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
},
|
||||
|
||||
// Baño Principal
|
||||
'bano_principal' : {
|
||||
nombre : 'Baño Principal',
|
||||
imagen : null,
|
||||
opciones: [
|
||||
{
|
||||
nombre : 'Disposiciónes',
|
||||
opciones: [
|
||||
{
|
||||
nombre : 'Normal',
|
||||
value : 'dino',
|
||||
material: null,
|
||||
},
|
||||
{
|
||||
nombre : 'Kit Sanitario',
|
||||
value : 'diks',
|
||||
material: null,
|
||||
closed : true,
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
nombre : 'Suelos',
|
||||
opciones: [
|
||||
{
|
||||
nombre : 'Suelo Viggo Fresno',
|
||||
value : 'sufr',
|
||||
material: 'suelo_viggo_fresno',
|
||||
},
|
||||
{
|
||||
nombre : 'Suelo Viggo Roble',
|
||||
material: 'suelo_viggo_Roble',
|
||||
value : 'suro'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
nombre : 'Alicatados Ducha',
|
||||
opciones: [
|
||||
{
|
||||
nombre : 'Imitación Madera',
|
||||
material: null,
|
||||
value : 'alma',
|
||||
},
|
||||
{
|
||||
nombre : 'Agar Mate',
|
||||
material: null,
|
||||
value : 'alag',
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
nombre : "Mobiliario Baño",
|
||||
opciones: [
|
||||
{
|
||||
nombre : 'Mueble Blanco',
|
||||
material: null,
|
||||
value : 'mobl',
|
||||
},
|
||||
{
|
||||
nombre : 'Mueble Madera',
|
||||
material: null,
|
||||
value : 'moma',
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
},
|
||||
|
||||
// Baño Secundario
|
||||
'bano_secundario' : {
|
||||
nombre : 'Baño Secundario',
|
||||
imagen : null,
|
||||
opciones: [
|
||||
{
|
||||
nombre : 'Disposiciones',
|
||||
opciones: [
|
||||
{
|
||||
nombre: 'Con Ducha',
|
||||
value : 'didu',
|
||||
material: null,
|
||||
},
|
||||
{
|
||||
nombre: 'Con Bañera',
|
||||
value : 'diba',
|
||||
material: null,
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
nombre : 'Suelos',
|
||||
opciones: [
|
||||
{
|
||||
nombre: 'Suelo Viggo Fresno',
|
||||
value : 'sufr',
|
||||
material: 'suelo_viggo_fresno',
|
||||
},
|
||||
{
|
||||
nombre: 'Suelo Viggo Roble',
|
||||
value : 'suro',
|
||||
material: 'suelo_viggo_roble',
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
nombre : "Mobiliario Baño",
|
||||
opciones: [
|
||||
{
|
||||
nombre : 'Mueble Blanco',
|
||||
material: null,
|
||||
value : 'mobl',
|
||||
},
|
||||
{
|
||||
nombre : 'Mueble Madera',
|
||||
material: null,
|
||||
value : 'moma',
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export interface Texto {
|
||||
id : string,
|
||||
text: string,
|
||||
}
|
||||
|
||||
|
||||
export interface Empresa {
|
||||
nombre: string;
|
||||
logos : Record<string, string>;
|
||||
textos: Record<string, string>;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export interface Material {
|
||||
nombre : string;
|
||||
ref : string;
|
||||
imagen?: string;
|
||||
}
|
||||
|
||||
|
||||
export interface Opcion {
|
||||
nombre : string;
|
||||
value : string;
|
||||
material: string|null;
|
||||
texto? : string;
|
||||
closed? : true,
|
||||
}
|
||||
|
||||
|
||||
export interface OpcionesSet {
|
||||
nombre : string;
|
||||
texto? : string;
|
||||
opciones : Opcion[];
|
||||
}
|
||||
|
||||
|
||||
export interface Estancia {
|
||||
nombre : string;
|
||||
imagen : string|null;
|
||||
opciones: OpcionesSet[];
|
||||
}
|
||||
|
||||
|
||||
export interface Proyecto {
|
||||
nombre : string;
|
||||
imagesEstancias : string;
|
||||
logos : Record<string, string>;
|
||||
textos : Record<string, string>;
|
||||
|
||||
estancias : Record<string, Estancia>;
|
||||
materiales: Record<string, Material>;
|
||||
}
|
||||
|
||||
@ -0,0 +1,38 @@
|
||||
<script lang="ts">
|
||||
|
||||
import { jsPDF } from 'jspdf';
|
||||
|
||||
|
||||
function generatePdf () {
|
||||
const doc = new jsPDF();
|
||||
doc.text("Proyecto Quaddro2",10,10);
|
||||
let img = new Image();
|
||||
img.src = 'imgs/quaddro2/est/salon/sufr.jpg';
|
||||
|
||||
img.onload = () => {
|
||||
// La imagen solo se añade DESPUÉS de que el navegador la cargó completamente
|
||||
doc.addImage(img, 'JPEG', 10, 20, 180, 180); // Ajusta las dimensiones si 400x400 era muy grande
|
||||
doc.save("quaddro2.pdf");
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
// **Añadir un Handler onerror**
|
||||
img.onerror = () => {
|
||||
console.error("Error al cargar la imagen. Revisa la ruta.");
|
||||
doc.text("ERROR: Imagen no encontrada o no cargada.", 10, 30);
|
||||
doc.save("quaddro2_error.pdf");
|
||||
};
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div>
|
||||
|
||||
<button onclick={generatePdf} type='button'>Generar Pdf </button>
|
||||
</div>
|
||||
@ -0,0 +1,97 @@
|
||||
<script lang="ts">
|
||||
// Volvemos a importar las piezas individuales desde el archivo .svelte.ts
|
||||
import { selections, updateSelection } from '../stores/selection.svelte.js';
|
||||
import { proyecto } from '../../data/data_proyecto.js';
|
||||
|
||||
// Tus tipos están bien
|
||||
interface Opcion { value: string; nombre: string; closed?: boolean; }
|
||||
interface Grupo { nombre: string; opciones: Opcion[]; }
|
||||
|
||||
// La función getAvailableGroups usa `selections` directamente (sin prefijo $)
|
||||
function getAvailableGroups(estanciaKey: string): Grupo[] {
|
||||
const currentSelections = selections[estanciaKey];
|
||||
if (!currentSelections) return [];
|
||||
|
||||
const availableGroups: Grupo[] = [];
|
||||
let stop = false;
|
||||
|
||||
proyecto.estancias[estanciaKey].opciones.forEach((grupo: Grupo) => {
|
||||
if (stop) return;
|
||||
availableGroups.push(grupo);
|
||||
const selectedValue = currentSelections[grupo.nombre];
|
||||
const selectedOption = grupo.opciones.find((opt) => opt.value === selectedValue);
|
||||
if (selectedOption?.closed) {
|
||||
stop = true;
|
||||
}
|
||||
});
|
||||
|
||||
return availableGroups;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 3. El estado derivado se crea con $derived, usando el estado `selections`
|
||||
const imageUrls = $derived.by(() => {
|
||||
const urls: Record<string, string> = {};
|
||||
for (const estanciaKey in selections) {
|
||||
// Obtenemos los valores de las opciones seleccionadas para esta estancia
|
||||
const selectedOptions = proyecto.estancias[estanciaKey].opciones.map(grupo => selections[estanciaKey][grupo.nombre]).filter(Boolean);
|
||||
|
||||
const imageName = selectedOptions.length > 0 ? selectedOptions.join('_') + '.jpg' : 'default.jpg';
|
||||
urls[estanciaKey] = `${proyecto.imagesEstancias}${estanciaKey}/${imageName}`;
|
||||
}
|
||||
return urls;
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<h1>Verificador del Store</h1>
|
||||
|
||||
{#each Object.keys(proyecto.estancias) as estanciaKey}
|
||||
<section>
|
||||
<h2>{proyecto.estancias[estanciaKey].nombre}</h2>
|
||||
|
||||
{#each getAvailableGroups(estanciaKey) as grupo}
|
||||
<div>
|
||||
<h3>{grupo.nombre}</h3>
|
||||
{#each grupo.opciones as opcion}
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
name={estanciaKey + '-' + grupo.nombre}
|
||||
value={opcion.value}
|
||||
checked={selections[estanciaKey]?.[grupo.nombre] === opcion.value}
|
||||
onchange={() => updateSelection(estanciaKey, grupo.nombre, opcion.value)}
|
||||
/>
|
||||
{opcion.nombre}
|
||||
{#if opcion.closed}(Cerrado){/if}
|
||||
</label>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<p>URL de la imagen: {imageUrls[estanciaKey]}</p>
|
||||
<img
|
||||
src={imageUrls[estanciaKey]}
|
||||
alt={"Imagen de " + estanciaKey}
|
||||
style="max-width: 100%;"
|
||||
/>
|
||||
</section>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* ... tus estilos no cambian ... */
|
||||
section {
|
||||
margin-bottom: 2em;
|
||||
border: 1px solid #ccc;
|
||||
padding: 1em;
|
||||
}
|
||||
div {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,89 @@
|
||||
<script lang="ts">
|
||||
import { selections, updateSelection, setEstanciaSeleccionada, appState, getAvailableGroups, imageSrc} from '../stores/sel.svelte';
|
||||
import { proyecto } from '../../data/data_proyecto';
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<div class="flex min-h-screen w-full bg-gray-50">
|
||||
<!-- Contenido Central -->
|
||||
<main class="flex-1 flex items-center justify-center p-8">
|
||||
<div class="w-full h-full flex flex-col items-center justify-center">
|
||||
<h1 class="text-3xl font-bold text-gray-800 mb-4">
|
||||
{proyecto.estancias[appState.estanciaSeleccionada].nombre}
|
||||
</h1>
|
||||
|
||||
<div class="relative w-full max-w-5xl bg-white rounded-lg shadow-lg overflow-hidden">
|
||||
<img
|
||||
src={imageSrc()[appState.estanciaSeleccionada]}
|
||||
alt={`Vista de ${proyecto.estancias[appState.estanciaSeleccionada].nombre}`}
|
||||
class="w-full h-auto max-h-[70vh] object-contain"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p class="mt-4 text-sm text-gray-500 font-mono">
|
||||
{imageSrc()[appState.estanciaSeleccionada]}
|
||||
</p>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Sidebar Derecho -->
|
||||
<aside class="w-96 flex-shrink-0 bg-white border-l border-gray-200 shadow-lg overflow-y-auto max-h-screen">
|
||||
<div class="p-6 sticky top-0 bg-white z-10 border-b border-gray-100">
|
||||
<h2 class="text-xl font-semibold text-gray-800 mb-4">Configuración</h2>
|
||||
|
||||
<!-- Selector de Estancias -->
|
||||
<div class="mb-6">
|
||||
<h3 class="text-sm font-medium text-gray-700 mb-3">Estancias</h3>
|
||||
<div class="space-y-2">
|
||||
{#each Object.keys(proyecto.estancias) as estanciaKey}
|
||||
<button
|
||||
class="w-full text-left px-4 py-2 rounded-lg transition-colors {appState.estanciaSeleccionada === estanciaKey
|
||||
? 'bg-blue-500 text-white'
|
||||
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'}"
|
||||
onclick={() => setEstanciaSeleccionada(estanciaKey) }
|
||||
>
|
||||
{proyecto.estancias[estanciaKey].nombre}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Opciones de la Estancia Seleccionada -->
|
||||
<div class="p-6 pt-0">
|
||||
<h3 class="text-sm font-medium text-gray-700 mb-4 mt-4">Personalización</h3>
|
||||
|
||||
{#each getAvailableGroups(appState.estanciaSeleccionada) as grupo}
|
||||
<div class="mb-6">
|
||||
<h4 class="text-sm font-semibold text-gray-800 mb-3">
|
||||
{grupo.nombre}
|
||||
</h4>
|
||||
|
||||
<div class="space-y-2">
|
||||
{#each grupo.opciones as opcion}
|
||||
<label class="flex items-center cursor-pointer group">
|
||||
<input
|
||||
type="radio"
|
||||
name={`${appState.estanciaSeleccionada}-${grupo.nombre}`}
|
||||
value={opcion.value}
|
||||
checked={selections[appState.estanciaSeleccionada]?.[grupo.nombre] === opcion.value}
|
||||
onchange={() => updateSelection(appState.estanciaSeleccionada, grupo.nombre, opcion.value)}
|
||||
class="w-4 h-4 text-blue-500 border-gray-300 focus:ring-blue-500"
|
||||
/>
|
||||
<span class="ml-3 text-sm text-gray-700 group-hover:text-gray-900">
|
||||
{opcion.nombre}
|
||||
{#if opcion.closed}
|
||||
<span class="ml-1 text-xs text-gray-500 italic">(Cerrado)</span>
|
||||
{/if}
|
||||
</span>
|
||||
</label>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
@ -0,0 +1,134 @@
|
||||
<script lang="ts">
|
||||
import { selections, updateSelection } from '../stores/selection.svelte';
|
||||
import { proyecto } from '../../data/data_proyecto';
|
||||
|
||||
interface Opcion {
|
||||
value: string;
|
||||
nombre: string;
|
||||
closed?: boolean;
|
||||
}
|
||||
|
||||
interface Grupo {
|
||||
nombre: string;
|
||||
opciones: Opcion[];
|
||||
}
|
||||
|
||||
// Estado local para la estancia seleccionada
|
||||
let estanciaSeleccionada = $state<string>(Object.keys(proyecto.estancias)[0]);
|
||||
|
||||
// Función para obtener grupos disponibles según la lógica 'closed'
|
||||
function getAvailableGroups(estanciaKey: string): Grupo[] {
|
||||
const currentSelections = selections[estanciaKey];
|
||||
if (!currentSelections) return [];
|
||||
|
||||
const availableGroups: Grupo[] = [];
|
||||
let stop = false;
|
||||
|
||||
proyecto.estancias[estanciaKey].opciones.forEach((grupo: Grupo) => {
|
||||
if (stop) return;
|
||||
availableGroups.push(grupo);
|
||||
const selectedValue = currentSelections[grupo.nombre];
|
||||
const selectedOption = grupo.opciones.find((opt) => opt.value === selectedValue);
|
||||
if (selectedOption?.closed) {
|
||||
stop = true;
|
||||
}
|
||||
});
|
||||
|
||||
return availableGroups;
|
||||
}
|
||||
|
||||
// Estado derivado para las URLs de las imágenes
|
||||
const imageUrls = $derived.by(() => {
|
||||
const urls: Record<string, string> = {};
|
||||
for (const estanciaKey in selections) {
|
||||
const selectedOptions = proyecto.estancias[estanciaKey].opciones
|
||||
.map(grupo => selections[estanciaKey][grupo.nombre])
|
||||
.filter(Boolean);
|
||||
const imageName = selectedOptions.length > 0 ? selectedOptions.join('_') + '.jpg' : 'default.jpg';
|
||||
urls[estanciaKey] = `${proyecto.imagesEstancias}${estanciaKey}/${imageName}`;
|
||||
}
|
||||
return urls;
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<div class="flex min-h-screen w-full bg-gray-50">
|
||||
<!-- Contenido Central -->
|
||||
<main class="flex-1 flex items-center justify-center p-8">
|
||||
<div class="w-full h-full flex flex-col items-center justify-center">
|
||||
<h1 class="text-3xl font-bold text-gray-800 mb-4">
|
||||
{proyecto.estancias[estanciaSeleccionada].nombre}
|
||||
</h1>
|
||||
|
||||
<div class="relative w-full max-w-5xl bg-white rounded-lg shadow-lg overflow-hidden">
|
||||
<img
|
||||
src={imageUrls[estanciaSeleccionada]}
|
||||
alt={`Vista de ${proyecto.estancias[estanciaSeleccionada].nombre}`}
|
||||
class="w-full h-auto max-h-[70vh] object-contain"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p class="mt-4 text-sm text-gray-500 font-mono">
|
||||
{imageUrls[estanciaSeleccionada]}
|
||||
</p>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Sidebar Derecho -->
|
||||
<aside class="w-96 flex-shrink-0 bg-white border-l border-gray-200 shadow-lg overflow-y-auto max-h-screen">
|
||||
<div class="p-6 sticky top-0 bg-white z-10 border-b border-gray-100">
|
||||
<h2 class="text-xl font-semibold text-gray-800 mb-4">Configuración</h2>
|
||||
|
||||
<!-- Selector de Estancias -->
|
||||
<div class="mb-6">
|
||||
<h3 class="text-sm font-medium text-gray-700 mb-3">Estancias</h3>
|
||||
<div class="space-y-2">
|
||||
{#each Object.keys(proyecto.estancias) as estanciaKey}
|
||||
<button
|
||||
class="w-full text-left px-4 py-2 rounded-lg transition-colors {estanciaSeleccionada === estanciaKey
|
||||
? 'bg-blue-500 text-white'
|
||||
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'}"
|
||||
onclick={() => estanciaSeleccionada = estanciaKey}
|
||||
>
|
||||
{proyecto.estancias[estanciaKey].nombre}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Opciones de la Estancia Seleccionada -->
|
||||
<div class="p-6 pt-0">
|
||||
<h3 class="text-sm font-medium text-gray-700 mb-4 mt-4">Personalización</h3>
|
||||
|
||||
{#each getAvailableGroups(estanciaSeleccionada) as grupo}
|
||||
<div class="mb-6">
|
||||
<h4 class="text-sm font-semibold text-gray-800 mb-3">
|
||||
{grupo.nombre}
|
||||
</h4>
|
||||
|
||||
<div class="space-y-2">
|
||||
{#each grupo.opciones as opcion}
|
||||
<label class="flex items-center cursor-pointer group">
|
||||
<input
|
||||
type="radio"
|
||||
name={`${estanciaSeleccionada}-${grupo.nombre}`}
|
||||
value={opcion.value}
|
||||
checked={selections[estanciaSeleccionada]?.[grupo.nombre] === opcion.value}
|
||||
onchange={() => updateSelection(estanciaSeleccionada, grupo.nombre, opcion.value)}
|
||||
class="w-4 h-4 text-blue-500 border-gray-300 focus:ring-blue-500"
|
||||
/>
|
||||
<span class="ml-3 text-sm text-gray-700 group-hover:text-gray-900">
|
||||
{opcion.nombre}
|
||||
{#if opcion.closed}
|
||||
<span class="ml-1 text-xs text-gray-500 italic">(Cerrado)</span>
|
||||
{/if}
|
||||
</span>
|
||||
</label>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
@ -0,0 +1,106 @@
|
||||
// stores/selection.svelte.ts
|
||||
import { proyecto } from '../../data/data_proyecto';
|
||||
|
||||
interface Opcion {
|
||||
value: string;
|
||||
nombre: string;
|
||||
closed?: boolean;
|
||||
}
|
||||
|
||||
interface Grupo {
|
||||
nombre: string;
|
||||
opciones: Opcion[];
|
||||
}
|
||||
|
||||
// La función auxiliar para inicializar no cambia
|
||||
function initializeSelections(): Record<string, Record<string, string>> {
|
||||
const selections: Record<string, Record<string, string>> = {};
|
||||
Object.keys(proyecto.estancias).forEach((estanciaKey) => {
|
||||
const estancia = proyecto.estancias[estanciaKey];
|
||||
selections[estanciaKey] = {};
|
||||
estancia.opciones.forEach((grupo) => {
|
||||
const defaultOption = grupo.opciones[0];
|
||||
selections[estanciaKey][grupo.nombre] = defaultOption.value;
|
||||
});
|
||||
});
|
||||
return selections;
|
||||
}
|
||||
|
||||
// 1. El estado se define directamente con $state a nivel de módulo
|
||||
export const selections = $state(initializeSelections());
|
||||
|
||||
// 2. La lógica de actualización es ahora una función exportada que muta `selections`
|
||||
export function updateSelection(estanciaKey: string, grupoNombre: string, newValue: string) {
|
||||
const estancia = proyecto.estancias[estanciaKey];
|
||||
if (!estancia) return;
|
||||
|
||||
selections[estanciaKey][grupoNombre] = newValue;
|
||||
|
||||
const grupoIndex = estancia.opciones.findIndex((g) => g.nombre === grupoNombre);
|
||||
if (grupoIndex === -1) return;
|
||||
|
||||
const grupo = estancia.opciones[grupoIndex];
|
||||
const selectedOption = grupo.opciones.find((opt) => opt.value === newValue);
|
||||
|
||||
if (selectedOption?.closed) {
|
||||
for (let i = grupoIndex + 1; i < estancia.opciones.length; i++) {
|
||||
const nextGrupo = estancia.opciones[i];
|
||||
delete selections[estanciaKey][nextGrupo.nombre];
|
||||
}
|
||||
} else {
|
||||
for (let i = grupoIndex + 1; i < estancia.opciones.length; i++) {
|
||||
const nextGrupo = estancia.opciones[i];
|
||||
if (!selections[estanciaKey][nextGrupo.nombre]) {
|
||||
selections[estanciaKey][nextGrupo.nombre] = nextGrupo.opciones[0].value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Función para obtener grupos disponibles según la lógica 'closed' (ahora exportada y usando el estado global)
|
||||
export function getAvailableGroups(estanciaKey: string): Grupo[] {
|
||||
const currentSelections = selections[estanciaKey];
|
||||
if (!currentSelections) return [];
|
||||
|
||||
const availableGroups: Grupo[] = [];
|
||||
let stop = false;
|
||||
|
||||
proyecto.estancias[estanciaKey].opciones.forEach((grupo: Grupo) => {
|
||||
if (stop) return;
|
||||
availableGroups.push(grupo);
|
||||
const selectedValue = currentSelections[grupo.nombre];
|
||||
const selectedOption = grupo.opciones.find((opt) => opt.value === selectedValue);
|
||||
if (selectedOption?.closed) {
|
||||
stop = true;
|
||||
}
|
||||
});
|
||||
|
||||
return availableGroups;
|
||||
}
|
||||
|
||||
// Estado derivado global para las URLs de las imágenes
|
||||
const imageUrls = $derived.by(() => {
|
||||
const urls: Record<string, string> = {};
|
||||
for (const estanciaKey in selections) {
|
||||
const selectedOptions = proyecto.estancias[estanciaKey].opciones
|
||||
.map(grupo => selections[estanciaKey][grupo.nombre])
|
||||
.filter(Boolean);
|
||||
const imageName = selectedOptions.length > 0 ? selectedOptions.join('_') + '.jpg' : 'default.jpg';
|
||||
urls[estanciaKey] = `${proyecto.imagesEstancias}${estanciaKey}/${imageName}`;
|
||||
}
|
||||
return urls;
|
||||
});
|
||||
|
||||
|
||||
export const appState = $state({
|
||||
estanciaSeleccionada : Object.keys(proyecto.estancias)[0]
|
||||
});
|
||||
|
||||
export function imageSrc () : Record<string, string> {
|
||||
return imageUrls
|
||||
}
|
||||
|
||||
export function setEstanciaSeleccionada(value : string) {
|
||||
appState.estanciaSeleccionada = value;
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
// stores/selection.svelte.ts
|
||||
import { proyecto } from '../../data/data_proyecto';
|
||||
|
||||
// La función auxiliar para inicializar no cambia
|
||||
function initializeSelections(): Record<string, Record<string, string>> {
|
||||
const selections: Record<string, Record<string, string>> = {};
|
||||
Object.keys(proyecto.estancias).forEach((estanciaKey) => {
|
||||
const estancia = proyecto.estancias[estanciaKey];
|
||||
selections[estanciaKey] = {};
|
||||
estancia.opciones.forEach((grupo) => {
|
||||
const defaultOption = grupo.opciones[0];
|
||||
selections[estanciaKey][grupo.nombre] = defaultOption.value;
|
||||
});
|
||||
});
|
||||
return selections;
|
||||
}
|
||||
|
||||
// 1. El estado se define directamente con $state a nivel de módulo
|
||||
export const selections = $state(initializeSelections());
|
||||
|
||||
// 2. La lógica de actualización es ahora una función exportada que muta `selections`
|
||||
export function updateSelection(estanciaKey: string, grupoNombre: string, newValue: string) {
|
||||
const estancia = proyecto.estancias[estanciaKey];
|
||||
if (!estancia) return;
|
||||
|
||||
selections[estanciaKey][grupoNombre] = newValue;
|
||||
|
||||
const grupoIndex = estancia.opciones.findIndex((g) => g.nombre === grupoNombre);
|
||||
if (grupoIndex === -1) return;
|
||||
|
||||
const grupo = estancia.opciones[grupoIndex];
|
||||
const selectedOption = grupo.opciones.find((opt) => opt.value === newValue);
|
||||
|
||||
if (selectedOption?.closed) {
|
||||
for (let i = grupoIndex + 1; i < estancia.opciones.length; i++) {
|
||||
const nextGrupo = estancia.opciones[i];
|
||||
delete selections[estanciaKey][nextGrupo.nombre];
|
||||
}
|
||||
} else {
|
||||
for (let i = grupoIndex + 1; i < estancia.opciones.length; i++) {
|
||||
const nextGrupo = estancia.opciones[i];
|
||||
if (!selections[estanciaKey][nextGrupo.nombre]) {
|
||||
selections[estanciaKey][nextGrupo.nombre] = nextGrupo.opciones[0].value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
import { mount } from 'svelte'
|
||||
import './app.css'
|
||||
import App from './App.svelte'
|
||||
|
||||
const app = mount(App, {
|
||||
target: document.getElementById('app')!,
|
||||
})
|
||||
|
||||
export default app
|
||||
@ -0,0 +1,8 @@
|
||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
|
||||
|
||||
/** @type {import("@sveltejs/vite-plugin-svelte").SvelteConfig} */
|
||||
export default {
|
||||
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
|
||||
// for more information about preprocessors
|
||||
preprocess: vitePreprocess(),
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
{
|
||||
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "ES2022",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"types": ["svelte", "vite/client"],
|
||||
"noEmit": true,
|
||||
/**
|
||||
* Typecheck JS in `.svelte` and `.js` files by default.
|
||||
* Disable checkJs if you'd like to use dynamic types in JS.
|
||||
* Note that setting allowJs false does not prevent the use
|
||||
* of JS in `.svelte` files.
|
||||
*/
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"moduleDetection": "force"
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"]
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
{
|
||||
"allowArbitraryExtensions": true,
|
||||
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "ES2023",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"types": ["node"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
import {defineConfig} from 'vite'
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import {svelte} from '@sveltejs/vite-plugin-svelte'
|
||||
|
||||
|
||||
|
||||
export default defineConfig({
|
||||
base: './',
|
||||
plugins: [svelte(),tailwindcss()],
|
||||
build: {
|
||||
rollupOptions: {
|
||||
output: {
|
||||
inlineDynamicImports: true,
|
||||
manualChunks: undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||