feat: add previous configuration handling for user selections in quotes
- Implemented session storage management for the last configuration choices made by the user. - Added functionality to prompt users when changing models to retain their previous selections. - Introduced methods to apply previous choices to the newly loaded model, including finishes, handles, glass types, and options. - Enhanced user experience by ensuring selections are automatically applied where applicable.
This commit is contained in:
+12
-9
@@ -1,5 +1,7 @@
|
||||
import { gsap } from 'gsap';
|
||||
|
||||
const impostato = (valore) => valore !== null && valore !== undefined && valore !== '';
|
||||
|
||||
var APP = {
|
||||
|
||||
Player: function (canvas_container) {
|
||||
@@ -442,8 +444,9 @@ var APP = {
|
||||
let texture = null;
|
||||
let bump = null;
|
||||
|
||||
texture = data.texture_map ? await new THREE.TextureLoader().loadAsync(`./assets/data/immagini/${window.sistema}/${data.folder}/${data.texture_folder}/${data.id}/${data.texture_map}`) : null;
|
||||
bump = data.texture_bump ? await new THREE.TextureLoader().loadAsync(`./assets/data/immagini/${window.sistema}/${data.folder}/${data.texture_folder}/${data.id}/${data.texture_bump}`) : null;
|
||||
// URL assoluti delle immagini servite dal backoffice (con CORS, richiesto da WebGL).
|
||||
texture = data.texture_map ? await new THREE.TextureLoader().loadAsync(data.texture_map) : null;
|
||||
bump = data.texture_bump ? await new THREE.TextureLoader().loadAsync(data.texture_bump) : null;
|
||||
|
||||
// A newer selection for this same group may have been made while the texture above was loading.
|
||||
// Discard this stale result so the last user/init action always wins, regardless of load order.
|
||||
@@ -466,12 +469,12 @@ var APP = {
|
||||
}
|
||||
|
||||
object.material.needsUpdate = true;
|
||||
if (data.roughness_valore != "") object.material.roughness = data.roughness_valore;
|
||||
if (data.metalness_valore != "") object.material.metalness = data.metalness_valore;
|
||||
if (data.opacity_valore != "") object.material.opacity = data.opacity_valore;
|
||||
if (data.bump_valore != "") object.material.bumpScale = data.bump_valore;
|
||||
//if(data.color_valore != "") object.material.color.setHex(parseInt(data.color_valore.replace("#", "0x")));
|
||||
if (data.color_valore != "") object.material.color = new THREE.Color(data.color_valore);
|
||||
// Valori assenti (null) o vuoti lasciano quelli del materiale.
|
||||
if (impostato(data.roughness_valore)) object.material.roughness = data.roughness_valore;
|
||||
if (impostato(data.metalness_valore)) object.material.metalness = data.metalness_valore;
|
||||
if (impostato(data.opacity_valore)) object.material.opacity = data.opacity_valore;
|
||||
if (impostato(data.bump_valore)) object.material.bumpScale = data.bump_valore;
|
||||
if (impostato(data.color_valore)) object.material.color = new THREE.Color(data.color_valore);
|
||||
|
||||
object.material.needsUpdate = true;
|
||||
}
|
||||
@@ -479,7 +482,7 @@ var APP = {
|
||||
});
|
||||
},
|
||||
this.changeColor = async (data) => {
|
||||
if (data.color_valore_corpo_base != "") {
|
||||
if (impostato(data.color_valore_corpo_base)) {
|
||||
scene.traverse((object) => {
|
||||
if (object.isMesh) {
|
||||
if (data.categories.includes(object.userData.categoria)) {
|
||||
|
||||
+62
-137
@@ -1,4 +1,5 @@
|
||||
let shared_data = null;
|
||||
let timerPrezzo = null;
|
||||
|
||||
const Configurator = {
|
||||
init: (priceCalculator, config_data) => {
|
||||
@@ -6,181 +7,105 @@ const Configurator = {
|
||||
shared_data = config_data;
|
||||
},
|
||||
enable: () => {
|
||||
$(("#larghezza, #altezza, select, input[name=vetro], input[type=radio]")).on('input', (e) => {
|
||||
|
||||
|
||||
Configurator.checkTextures()
|
||||
$(("#larghezza, #altezza, select, input[name=vetro], input[type=radio], input[type=checkbox]")).on('input', (e) => {
|
||||
|
||||
let res = Configurator.generatePrice();
|
||||
|
||||
if(res == null) return;
|
||||
|
||||
res = Configurator.generateConfigData(res);
|
||||
|
||||
$(".prezzo").html(`€ ${res.prezzo.toFixed(2)}<span>IVA esclusa</span>`);
|
||||
window.configurazione = res;
|
||||
// Un solo calcolo per raffica di modifiche (es. digitazione delle misure).
|
||||
clearTimeout(timerPrezzo);
|
||||
timerPrezzo = setTimeout(Configurator.aggiornaPrezzo, 150);
|
||||
|
||||
if($(e.target).data('model')){
|
||||
caricaModello($(e.target).data('model'));
|
||||
}
|
||||
});
|
||||
},
|
||||
generatePrice: () => {
|
||||
/** Scelte correnti (solo codici): il backoffice ne calcola il prezzo e la descrizione. */
|
||||
raccogliScelte: () => {
|
||||
const scelte = {
|
||||
larghezza: parseInt($('#larghezza').val()),
|
||||
altezza: parseInt($('#altezza').val()),
|
||||
opzioni: {},
|
||||
};
|
||||
|
||||
const config_data = {};
|
||||
config_data.height = $('#altezza').val();
|
||||
config_data.width = $('#larghezza').val();
|
||||
config_data.finitura = $('#prezzi_finiture').val();
|
||||
config_data.percentuale_accessori = 0;
|
||||
|
||||
$('input.percentuale:checked').each((i, el) => {
|
||||
let perc = $(el).data('percentuale');
|
||||
|
||||
perc = perc == undefined ? 0 : parseFloat(perc);
|
||||
perc = isNaN(perc) ? 0 : perc;
|
||||
config_data.percentuale_accessori += (perc / 100);
|
||||
if($(el).hasClass('check_finitura')){
|
||||
|
||||
config_data.override_supplemento_finitura = true;
|
||||
}
|
||||
['finitura_interna', 'finitura_esterna', 'maniglia', 'vetro'].forEach((nome) => {
|
||||
const valore = $(`input[name=${nome}]:checked`).val();
|
||||
if (valore != null) scelte[nome] = valore;
|
||||
});
|
||||
//config_data.finitura_interna = $('input[name=finitura_interna]:checked').val();
|
||||
//config_data.finitura_esterna = $('input[name=finitura_esterna]:checked').val();
|
||||
config_data.maniglia = $('input[name=maniglia]:checked').val();
|
||||
|
||||
//config_data.finitura_cassonetto = $('input[name=cassonetto]:checked').val();
|
||||
config_data.finitura_avvolgibile = $('input[name=avvolgibile]:checked').val();
|
||||
for (const gruppo in shared_data.form.radios) {
|
||||
const valore = $(`input[name=${gruppo}]:checked`).val();
|
||||
if (valore != null) scelte.opzioni[gruppo] = valore;
|
||||
}
|
||||
|
||||
config_data.vetro = $('input[name=vetro]:checked').val();
|
||||
config_data.cerniere_scomparsa = $('.cerniere_a_scomparsa').val();
|
||||
config_data.altezza_maniglia = $('.altezza_maniglia').val();
|
||||
config_data.azionamento = $('.azionamento').val();
|
||||
config_data.tipo_ispezione = $('.tipo_ispezione').val();
|
||||
config_data.apertura_portoncino = $('.apertura_portoncino option:selected').text();
|
||||
config_data.tipo_ispezione = $('.tipo_ispezione option:selected').text();
|
||||
// Scelta multipla: lista dei codici selezionati (anche vuota).
|
||||
for (const gruppo in shared_data.form.multiple ?? {}) {
|
||||
scelte.opzioni[gruppo] = $(`input.opzione-multipla[name=${gruppo}]:checked`).map((i, el) => el.value).get();
|
||||
}
|
||||
|
||||
let price = Configurator.priceCalculator.getGridPrice(config_data.width, config_data.height);
|
||||
for (const gruppo in shared_data.form.select) {
|
||||
const valore = $(`select.${gruppo}`).val();
|
||||
if (valore) scelte.opzioni[gruppo] = valore;
|
||||
}
|
||||
|
||||
if(price < 0){
|
||||
return scelte;
|
||||
},
|
||||
aggiornaPrezzo: async () => {
|
||||
const res = await Configurator.priceCalculator.calcola(shared_data.sistema, shared_data.modello.id, Configurator.raccogliScelte());
|
||||
if (res.annullata) return;
|
||||
|
||||
if (res.errore) {
|
||||
window.configurazione = null;
|
||||
$(".errore-misure-cnt").removeClass('d-none');
|
||||
$(".prezzo").addClass('invisible');
|
||||
$(".cart-button").addClass('disabled');
|
||||
}else{
|
||||
$(".errore-misure-cnt").addClass('d-none');
|
||||
$(".prezzo").removeClass('invisible');
|
||||
$(".cart-button").removeClass('disabled');
|
||||
return;
|
||||
}
|
||||
|
||||
config_data.prezzo_base = price;
|
||||
$(".errore-misure-cnt").addClass('d-none');
|
||||
$(".prezzo").removeClass('invisible');
|
||||
$(".cart-button").removeClass('disabled');
|
||||
$(".prezzo").html(`€ ${res.prezzo.toFixed(2)}<span>IVA esclusa</span>`);
|
||||
|
||||
const config = Configurator.priceCalculator.getConfigurationPrice(config_data);
|
||||
config.configurazione.height = config_data.height;
|
||||
config.configurazione.width = config_data.width;
|
||||
config.configurazione.nome = shared_data.nome_esteso;
|
||||
|
||||
if(config_data.apertura_portoncino != "")
|
||||
config.configurazione.apertura_portoncino = config_data.apertura_portoncino;
|
||||
|
||||
if(config_data.tipo_ispezione != "")
|
||||
config.configurazione.tipo_ispezione = config_data.tipo_ispezione;
|
||||
|
||||
return config;
|
||||
window.configurazione = {
|
||||
prezzo: res.prezzo,
|
||||
scelte: res.scelte,
|
||||
configurazione: { ...Configurator.vociDescrittive(res.configurazione.height), ...res.configurazione },
|
||||
};
|
||||
},
|
||||
generateConfigData: (config) =>{
|
||||
let input_finiture = $(`.finitura input[type=radio]:checked`);
|
||||
|
||||
//console.log(input_finiture);
|
||||
input_finiture.each((i, el) => {
|
||||
let id_finitura = $(el).val();
|
||||
let nome_finitura = $(el).attr('name');
|
||||
let finitura = shared_data.data_finiture.find(f => f.id == id_finitura);
|
||||
config.configurazione[nome_finitura] = finitura.descrizione;
|
||||
});
|
||||
/** Voci senza effetto sul prezzo, ricavate dal menu e salvate così nel preventivo. */
|
||||
vociDescrittive: (altezzaSerramento) => {
|
||||
const voci = {};
|
||||
|
||||
//se è un cassonetto
|
||||
if(shared_data.data_avvolgibile != null && shared_data.data_avvolgibile.length > 0){
|
||||
|
||||
let id_avvolgibile = $(`input[name=avvolgibile]:checked`).val();
|
||||
let avvolgibile = shared_data.data_avvolgibile.find(a => a.id == id_avvolgibile);
|
||||
config.configurazione.avvolgibile = avvolgibile.descrizione;
|
||||
|
||||
let id_cassonetto = $(`input[name=avvolgibile]:checked`).val();
|
||||
let cassonetto = shared_data.data_finiture.find(a => a.id == id_cassonetto);
|
||||
config.configurazione.cassonetto = cassonetto.descrizione;
|
||||
if (avvolgibile) voci.avvolgibile = avvolgibile.descrizione;
|
||||
|
||||
let cassonetto = shared_data.data_finiture.find(a => a.id == id_avvolgibile);
|
||||
if (cassonetto) voci.cassonetto = cassonetto.descrizione;
|
||||
}
|
||||
|
||||
let val_maniglia = $(`input[name=maniglia]:checked`).val();
|
||||
let finitura_maniglia = shared_data.data_maniglie.find(m => m.id == val_maniglia);
|
||||
// Prima fascia della tabella con altezza (mm) >= a quella del serramento.
|
||||
let altezza = parseFloat(config.configurazione.height);
|
||||
let altezza = parseFloat(altezzaSerramento);
|
||||
let altezza_maniglia = [...shared_data.altezza_maniglia]
|
||||
.sort((a, b) => a.altezza - b.altezza)
|
||||
.find((m) => m.altezza >= altezza);
|
||||
|
||||
.find((m) => m.altezza >= altezza) ?? shared_data.altezza_maniglia[0];
|
||||
|
||||
if(altezza_maniglia != null){
|
||||
config.configurazione.altezza_maniglia = altezza_maniglia.altezza_maniglia;
|
||||
voci.altezza_maniglia = altezza_maniglia.altezza_maniglia;
|
||||
}
|
||||
|
||||
if(altezza_maniglia == null && shared_data.altezza_maniglia.length > 0){
|
||||
config.configurazione.altezza_maniglia = shared_data.altezza_maniglia[0].altezza_maniglia;
|
||||
}
|
||||
|
||||
if(finitura_maniglia != null)
|
||||
config.configurazione.maniglia = finitura_maniglia.descrizione;
|
||||
|
||||
$('input.auto-radio:checked').each((i, el) => {
|
||||
|
||||
let nome = $(el).attr('name');
|
||||
if($(`.${nome}`).hasClass('d-none')) return;
|
||||
config.configurazione[nome] = $(el).val();
|
||||
});
|
||||
|
||||
return config;
|
||||
},
|
||||
checkTextures: () =>{
|
||||
|
||||
let $interna = $("input[name=finitura_interna]:checked");
|
||||
let $esterna = $("input[name=finitura_esterna]:checked");
|
||||
|
||||
let interna = $interna.val();
|
||||
// modelli senza rivestimento esterno separato (tipo 'uniforme'): segue l'interno
|
||||
let esterna = $esterna.length ? $esterna.val() : interna;
|
||||
let internaPredefinita = $interna.data('predefinito') === true;
|
||||
let tipo = $interna.data('tipo');
|
||||
let percentuale = $interna.data('percentuale');
|
||||
|
||||
// bianco: stesso colore predefinito su interno ed esterno -> nessun sovrapprezzo
|
||||
// uniforme: stesso colore (non predefinito) su interno ed esterno -> sovrapprezzo "uniforme"
|
||||
// bicolore: colori interno ed esterno diversi tra loro -> sovrapprezzo "bicolore"
|
||||
if(interna === esterna){
|
||||
$("#prezzi_finiture").val(internaPredefinita ? 'bianco' : 'uniforme');
|
||||
}else{
|
||||
$("#prezzi_finiture").val('bicolore');
|
||||
}
|
||||
|
||||
if(percentuale != undefined && percentuale.length > 0){
|
||||
$("#prezzi_finiture").val('');
|
||||
}
|
||||
|
||||
if(tipo == 'uniforme' && internaPredefinita){
|
||||
$("#prezzi_finiture").val('bianco');
|
||||
return;
|
||||
}
|
||||
|
||||
if(tipo == 'uniforme' && !internaPredefinita){
|
||||
$("#prezzi_finiture").val('uniforme');
|
||||
return;
|
||||
}
|
||||
|
||||
if(shared_data.data_avvolgibile != null && shared_data.data_avvolgibile.length > 0){
|
||||
$("#prezzi_finiture").val('bicolore');
|
||||
}
|
||||
const apertura_portoncino = $('.apertura_portoncino option:selected').text();
|
||||
if (apertura_portoncino != "") voci.apertura_portoncino = apertura_portoncino;
|
||||
|
||||
const tipo_ispezione = $('.tipo_ispezione option:selected').text();
|
||||
if (tipo_ispezione != "") voci.tipo_ispezione = tipo_ispezione;
|
||||
|
||||
return voci;
|
||||
},
|
||||
priceCalculator: null
|
||||
|
||||
|
||||
}
|
||||
|
||||
export default Configurator;
|
||||
export default Configurator;
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
import $ from 'jquery';
|
||||
import ParamsParser from './params_parser.js';
|
||||
|
||||
// Scelte dell'ultimo infisso aggiunto al preventivo: al cambio modello si chiede se riportarle
|
||||
// sul nuovo (finiture, maniglia, vetro, opzioni). Il cambio modello è una navigazione di pagina,
|
||||
// quindi le scelte da applicare passano per sessionStorage.
|
||||
const KEY_ULTIMA = 'kreios_ultima_configurazione';
|
||||
const KEY_DA_APPLICARE = 'kreios_configurazione_da_applicare';
|
||||
|
||||
const leggi = (storage, key) => {
|
||||
try {
|
||||
const raw = storage.getItem(key);
|
||||
return raw ? JSON.parse(raw) : null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const scrivi = (storage, key, value) => {
|
||||
try {
|
||||
if (value == null) storage.removeItem(key);
|
||||
else storage.setItem(key, JSON.stringify(value));
|
||||
} catch (e) {
|
||||
// Storage non disponibile (es. iframe con cookie di terze parti bloccati): si va avanti senza.
|
||||
}
|
||||
};
|
||||
|
||||
const radio = (nome, valore) =>
|
||||
document.querySelector(`input[type=radio][name="${CSS.escape(nome)}"][value="${CSS.escape(String(valore))}"]`);
|
||||
|
||||
// Click nativo: genera click/input/change come una scelta dell'utente (texture, prezzo, apertura...).
|
||||
const seleziona = (el) => {
|
||||
if (el && !el.checked) el.click();
|
||||
};
|
||||
|
||||
const chiediConferma = () => new Promise((resolve) => {
|
||||
const $modal = $('#modal-configurazione-precedente');
|
||||
const chiudi = (risposta) => {
|
||||
$modal.addClass('d-none').off('click.cp');
|
||||
resolve(risposta);
|
||||
};
|
||||
|
||||
$modal.removeClass('d-none')
|
||||
.on('click.cp', '[data-risposta]', function () { chiudi($(this).data('risposta')); })
|
||||
.on('click.cp', function (e) { if (e.target === this) chiudi('annulla'); });
|
||||
});
|
||||
|
||||
const ConfigurazionePrecedente = {
|
||||
/** Da chiamare dopo un'aggiunta al preventivo andata a buon fine. */
|
||||
salva: (sistema, type, scelte) => {
|
||||
scrivi(sessionStorage, KEY_ULTIMA, { sistema, type, scelte });
|
||||
},
|
||||
|
||||
/** Intercetta i link verso un altro modello/sistema per chiedere se mantenere le scelte. */
|
||||
intercettaCambioModello: () => {
|
||||
$(document).on('click', '.lista-modelli a, .menu-header-sistema a', async function (e) {
|
||||
const ultima = leggi(sessionStorage, KEY_ULTIMA);
|
||||
if (!ultima || e.metaKey || e.ctrlKey || e.shiftKey) return;
|
||||
|
||||
const destinazione = new URL(this.href, window.location.href);
|
||||
const params = ParamsParser.getQueryParams();
|
||||
if (destinazione.searchParams.get('type') === params.type
|
||||
&& destinazione.searchParams.get('sistema') === params.sistema) return;
|
||||
|
||||
e.preventDefault();
|
||||
const risposta = await chiediConferma();
|
||||
if (risposta === 'annulla') return;
|
||||
|
||||
scrivi(sessionStorage, KEY_DA_APPLICARE, risposta === 'si' ? ultima.scelte : null);
|
||||
// Si chiede una volta sola per ogni infisso aggiunto al preventivo.
|
||||
scrivi(sessionStorage, KEY_ULTIMA, null);
|
||||
window.location.href = destinazione.href;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Riporta sul modello appena caricato le scelte del precedente, dove esistono (stesso codice).
|
||||
* Le misure no: dipendono dal listino del modello.
|
||||
*/
|
||||
applica: () => {
|
||||
const scelte = leggi(sessionStorage, KEY_DA_APPLICARE);
|
||||
scrivi(sessionStorage, KEY_DA_APPLICARE, null);
|
||||
if (!scelte) return;
|
||||
|
||||
window.rotate = false;
|
||||
|
||||
// Prima le finiture: se un'opzione cambia variante 3D (caricaModello) riapplica quelle selezionate.
|
||||
['finitura_interna', 'finitura_esterna', 'maniglia', 'vetro'].forEach((nome) => {
|
||||
if (scelte[nome] != null) seleziona(radio(nome, scelte[nome]));
|
||||
});
|
||||
|
||||
for (const [gruppo, valore] of Object.entries(scelte.opzioni ?? {})) {
|
||||
if (Array.isArray(valore)) {
|
||||
document.querySelectorAll(`input.opzione-multipla[name="${CSS.escape(gruppo)}"]`).forEach((el) => {
|
||||
if (el.checked !== valore.includes(el.value)) el.click();
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const select = document.querySelector(`select.${CSS.escape(gruppo)}`);
|
||||
if (select) {
|
||||
if (select.value === String(valore) || ![...select.options].some(o => o.value === String(valore))) continue;
|
||||
select.value = valore;
|
||||
select.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
select.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
continue;
|
||||
}
|
||||
|
||||
seleziona(radio(gruppo, valore));
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
window.rotate = true;
|
||||
}, 1000);
|
||||
},
|
||||
};
|
||||
|
||||
export default ConfigurazionePrecedente;
|
||||
+66
-56
@@ -19,19 +19,10 @@ const MenuBuilder = {
|
||||
data_vetri = data.data_vetri;
|
||||
listino = data.listino;
|
||||
|
||||
const price_data = {
|
||||
misure: listino,
|
||||
finiture: params.prezzi_finiture,
|
||||
cerniere: params.select.cerniere_a_scomparsa,
|
||||
//altezza_maniglia: params.select.altezza_maniglia,
|
||||
azionamento: params.select.azionamento,
|
||||
vetri: data_vetri
|
||||
}
|
||||
|
||||
window.addEventListener('orientationchange', () => {
|
||||
location.reload();
|
||||
});
|
||||
PriceCalculator.init(price_data);
|
||||
PriceCalculator.init({ misure: listino });
|
||||
let template_file = params.template != null ? params.template : 'menu.html';
|
||||
let template = await fetch(`./assets/_${template_file}?rnd=${Math.random()}`);
|
||||
|
||||
@@ -123,6 +114,11 @@ const MenuBuilder = {
|
||||
|
||||
|
||||
|
||||
// Gruppi senza markup fisso nel template: riga creata nella sezione scelta dal backoffice.
|
||||
for (const key in params.gruppi ?? {}) {
|
||||
assicuraContenitore(key, params.gruppi[key]);
|
||||
}
|
||||
|
||||
for (const key in params.radios) {
|
||||
let options = params.radios[key];
|
||||
|
||||
@@ -143,6 +139,25 @@ const MenuBuilder = {
|
||||
|
||||
}
|
||||
|
||||
// Gruppi a scelta multipla (es. profili extra): pulsanti attivabili insieme, anche nessuno.
|
||||
for (const key in params.multiple ?? {}) {
|
||||
let options = params.multiple[key];
|
||||
if (!options.length) continue;
|
||||
|
||||
container.querySelector(`.menu-options.${key}`).innerHTML = buildMultiple(options, key);
|
||||
$(`.cnt-${key}`).removeClass('d-none');
|
||||
}
|
||||
|
||||
// Le opzioni con la stessa "alternativa" si escludono a vicenda (es. coprifili piatti / scatolati).
|
||||
$(container).on('change', 'input.opzione-multipla', function () {
|
||||
const alternativa = $(this).data('alternativa');
|
||||
if (this.checked && alternativa) {
|
||||
$(container).find(`input.opzione-multipla[name="${this.name}"]`)
|
||||
.filter((i, el) => el !== this && $(el).data('alternativa') === alternativa)
|
||||
.prop('checked', false);
|
||||
}
|
||||
});
|
||||
|
||||
if (isMobile) {
|
||||
$(".sistema-menu, .finiture-menu, .accessori-menu,.vetro-menu, .misure-menu").addClass('d-none');
|
||||
|
||||
@@ -152,7 +167,7 @@ const MenuBuilder = {
|
||||
$('.apertura input').on('change', () => {
|
||||
let val = $('.apertura input:checked').val();
|
||||
|
||||
let p = params.radios.apertura.find(r => r.label == val);
|
||||
let p = params.radios.apertura.find(r => r.valore == val);
|
||||
|
||||
cambiaApertura('infisso', p.scale_x);
|
||||
|
||||
@@ -160,9 +175,11 @@ const MenuBuilder = {
|
||||
|
||||
|
||||
/* scorro i tipi di finitura nella form e se è uniforme nascondo i tasti non necessari */
|
||||
// Senza finiture esterne (modello uniforme o nessuna finitura attiva sull'esterno) il menu è unico.
|
||||
const senzaEsterne = !params.finiture.some(f => f.nome == 'finitura_esterna');
|
||||
params.finiture.forEach((f) => {
|
||||
buildTextureRadio(model, f.nome, f.cartella_anteprime, f.cartella, f.categorie, data[f.data], f.tipo);
|
||||
if(f.nome == 'finitura_interna' && f.tipo == 'uniforme'){
|
||||
buildTextureRadio(f.nome, f.categorie, data[f.data], f.tipo);
|
||||
if(f.nome == 'finitura_interna' && (f.tipo == 'uniforme' || senzaEsterne)){
|
||||
//buildTextureRadio(model, 'finitura_esterna', f.cartella_anteprime, f.cartella, f.categorie, data[f.data]);
|
||||
$('.finiture-esterne-menu').addClass('d-none');
|
||||
$('.btn-opzioni.finiture-esterne').addClass('d-none');
|
||||
@@ -242,33 +259,6 @@ const buildListaModelli = (modelli, modello, categoria, sistema) => {
|
||||
$('.lista-modelli').html(html);
|
||||
}
|
||||
|
||||
const buildSelectFiniture = (options) => {
|
||||
let select = container.querySelector(`#prezzi_finiture`);
|
||||
select.innerHTML = ``;
|
||||
options.forEach(option => {
|
||||
select.innerHTML += `<option value="${option.finitura}">${option.label}</option>`;
|
||||
});
|
||||
|
||||
select.addEventListener('change', (e) => {
|
||||
|
||||
let val = e.target.value;
|
||||
if (val != 'bicolore' && val != 'uniforme') {
|
||||
|
||||
$(container).find(`.menu-container.finitura`).each((i, el) => {
|
||||
|
||||
if (!$(el).hasClass(val) && val != 'uniforme') {
|
||||
$(el).find('.menu-options input').first().trigger('click');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$('.menu-container.finitura').addClass('d-none');
|
||||
document.querySelectorAll(`.finitura.${val}`).forEach(el => {
|
||||
el.classList.remove('d-none');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const buildSelect = (options, parent) => {
|
||||
let select = container.querySelector(`.${parent}`);
|
||||
|
||||
@@ -281,7 +271,6 @@ const buildSelect = (options, parent) => {
|
||||
select.addEventListener('change', (e) => {
|
||||
let val = e.target.value;
|
||||
const el = options.find(o => o.valore == val);
|
||||
const prezzo = el.prezzo ?? 0;
|
||||
|
||||
if (el.func != null) {
|
||||
window[el.func](el.categoria);
|
||||
@@ -295,7 +284,7 @@ const buildSelect = (options, parent) => {
|
||||
$(`.cnt-${parent}`).removeClass('d-none');
|
||||
}
|
||||
|
||||
const buildTextureRadio = async (model, parent, icon_folder, texture_folder, categories, data, tipo = '') => {
|
||||
const buildTextureRadio = async (parent, categories, data, tipo = '') => {
|
||||
|
||||
let html = '';
|
||||
|
||||
@@ -306,16 +295,16 @@ const buildTextureRadio = async (model, parent, icon_folder, texture_folder, cat
|
||||
let popup_link = option.pop_up != null ? option.pop_up : '';
|
||||
|
||||
if (popup_link != '') {
|
||||
popup_link = `<a href="javascript:;" class="info-popup" data-image-url="./assets/data/immagini/${window.sistema}/${model}/pop_up_vetri/${popup_link}" ><img width="20" src="./assets/img/menu/icona_info.png" /></a>`;
|
||||
popup_link = `<a href="javascript:;" class="info-popup" data-image-url="${popup_link}" ><img width="20" src="./assets/img/menu/icona_info.png" /></a>`;
|
||||
}
|
||||
|
||||
let checked = i == defaultIndex && parent != 'maniglia' ? 'checked' : '';
|
||||
html =
|
||||
`<div class="d-inline-block texture-radio text-center">
|
||||
<label class="menu-option">
|
||||
<input type="radio" ${checked} class="texture-button ${option.percentuale != null && option.percentuale !== '' ? 'percentuale' : ''} check_finitura" data-percentuale="${option.percentuale ?? ''}" data-tipo="${tipo}" data-predefinito="${i == defaultIndex}" name="${parent}" value="${option.id}">
|
||||
<input type="radio" ${checked} class="texture-button" data-tipo="${tipo}" data-predefinito="${i == defaultIndex}" name="${parent}" value="${option.id}">
|
||||
<div class="option-content">
|
||||
<img src="./assets/data/immagini/${window.sistema}/${model}/${icon_folder}/${option.anteprima_img}" alt="${option.descrizione}">
|
||||
<img src="${option.anteprima ?? ''}" alt="${option.descrizione}">
|
||||
<span class="checkmark"></span>
|
||||
</div>
|
||||
</label>
|
||||
@@ -334,8 +323,6 @@ const buildTextureRadio = async (model, parent, icon_folder, texture_folder, cat
|
||||
const isStale = () => textureRequestVersion[parent] !== requestVersion;
|
||||
|
||||
option.categories = categories;
|
||||
option.folder = model;
|
||||
option.texture_folder = texture_folder;
|
||||
option.isStale = isStale;
|
||||
$(".mobile-popup-backdrop, .mobile-popup").addClass("d-none");
|
||||
|
||||
@@ -357,15 +344,43 @@ const buildTextureRadio = async (model, parent, icon_folder, texture_folder, cat
|
||||
//container.querySelector(`.${parent}`).innerHTML = html;
|
||||
};
|
||||
|
||||
const assicuraContenitore = (key, gruppo) => {
|
||||
if (container.querySelector(`.${key}`)) return;
|
||||
|
||||
const controllo = gruppo.ui === 'select'
|
||||
? `<div class="menu-options"><select class="form-control form-select ${key}"></select></div>`
|
||||
: `<div class="menu-options ${key}"></div>`;
|
||||
|
||||
$(container).find(`.opzioni-${gruppo.sezione}`).append(`<div class="row cnt-${key} d-none">
|
||||
<div class="col-sm-12">
|
||||
<div class="menu-container${gruppo.ui === 'select' ? ' mb-3' : ''}">
|
||||
<h6>${gruppo.etichetta}</h6>
|
||||
${controllo}
|
||||
</div>
|
||||
</div>
|
||||
</div>`);
|
||||
|
||||
if (gruppo.sezione === 'sistema') $('.sistema-menu').removeClass('d-none');
|
||||
};
|
||||
|
||||
const buildMultiple = (options, key) => options.map(option => `<div class="d-inline-block radio-button text-center">
|
||||
<label class="menu-option">
|
||||
<input type="checkbox" class="opzione-multipla" name="${key}" value="${option.valore}" data-alternativa="${option.alternativa ?? ''}">
|
||||
<div class="option-content">
|
||||
<img src="${option.icon ?? ''}" alt="${option.label}">
|
||||
<span class="checkmark"></span>
|
||||
</div>
|
||||
</label>
|
||||
<div class="option-label">${option.label}</div>
|
||||
</div>`).join('');
|
||||
|
||||
const buildRadios = (options, key, model) => {
|
||||
let html = '';
|
||||
let has_sub_options = false;
|
||||
|
||||
options.forEach((option, i) => {
|
||||
|
||||
let percentuale = '';
|
||||
let modello = '';
|
||||
let class_percentuale = '';
|
||||
let has_options = false;
|
||||
let sub_option_container = null;
|
||||
let html_func = '';
|
||||
@@ -391,11 +406,6 @@ const buildRadios = (options, key, model) => {
|
||||
modello = `data-model="${option.model}"`;
|
||||
}
|
||||
|
||||
if (option.percentuale != null) {
|
||||
percentuale = `data-percentuale="${option.percentuale}"`;
|
||||
class_percentuale = 'percentuale';
|
||||
}
|
||||
|
||||
if (has_options) {
|
||||
has_options_class = 'has-options';
|
||||
html_func = `onclick="toggleSubOption('${sub_option_container.id}',event)"`;
|
||||
@@ -407,9 +417,9 @@ const buildRadios = (options, key, model) => {
|
||||
html +=
|
||||
`<div class="d-inline-block radio-button text-center">
|
||||
<label class="menu-option">
|
||||
<input type="radio" ${checked} ${percentuale} ${modello} class="${has_options_class} ${class_percentuale} auto-radio" ${container_id} name="${key}" value="${option.label}">
|
||||
<input type="radio" ${checked} ${modello} class="${has_options_class} auto-radio" ${container_id} name="${key}" value="${option.valore ?? option.label}">
|
||||
<div class="option-content">
|
||||
<img src="./assets/data/immagini/${window.sistema}/${model}/${option.icon}" alt="${option.label}">
|
||||
<img src="${option.icon ?? ''}" alt="${option.label}">
|
||||
<span class="checkmark"></span>
|
||||
|
||||
</div>
|
||||
|
||||
+31
-116
@@ -1,126 +1,41 @@
|
||||
import { API_BASE } from './api_base.js';
|
||||
|
||||
let listini = null;
|
||||
|
||||
const toPercentuale = (valore) => {
|
||||
const perc = parseFloat(valore);
|
||||
return isNaN(perc) ? 0 : perc / 100;
|
||||
};
|
||||
|
||||
const toPrezzo = (valore) => {
|
||||
const prezzo = parseFloat(valore);
|
||||
return isNaN(prezzo) ? 0 : prezzo;
|
||||
};
|
||||
// Il prezzo è calcolato dal backoffice (POST .../prezzo) con le regole configurate lì:
|
||||
// qui resta solo la richiesta, più le misure del listino per i limiti dei campi.
|
||||
let misure = [];
|
||||
let richiestaCorrente = null;
|
||||
|
||||
const PriceCalculator = {
|
||||
init: async (data) => {
|
||||
|
||||
listini = data;
|
||||
|
||||
|
||||
init: (data) => {
|
||||
misure = data.misure ?? [];
|
||||
},
|
||||
getMinSize: () => {
|
||||
getMinSize: () => misure.find(item => item.prezzo !== 0) ?? misure[0],
|
||||
/**
|
||||
* Prezzo della configurazione. Una richiesta più recente annulla la precedente, così
|
||||
* vince sempre l'ultima scelta dell'utente. Risolve con { annullata: true } se superata.
|
||||
*/
|
||||
calcola: async (sistema, modello, scelte) => {
|
||||
richiestaCorrente?.abort();
|
||||
const controller = new AbortController();
|
||||
richiestaCorrente = controller;
|
||||
|
||||
const firstSize = listini.misure.find(item => item.prezzo !== 0);
|
||||
|
||||
return firstSize;
|
||||
|
||||
},
|
||||
// Misure in mm: entro min/max del listino si usa la prima misura >= a quella
|
||||
// richiesta (le griglie non sono sempre a passo regolare, es. traslanti da 1450 mm).
|
||||
getGridPrice: (width, height) => {
|
||||
const larghezza = PriceCalculator.misuraListino('larghezza', width);
|
||||
const altezza = PriceCalculator.misuraListino('altezza', height);
|
||||
if (larghezza == null || altezza == null) return -1;
|
||||
|
||||
const misure = listini.misure.find(el => el.altezza == altezza && el.larghezza == larghezza);
|
||||
|
||||
if(misure == null) return -1;
|
||||
return misure.prezzo;
|
||||
},
|
||||
misuraListino: (campo, valore) => {
|
||||
const v = parseFloat(valore);
|
||||
if (isNaN(v)) return null;
|
||||
|
||||
const misure = [...new Set(listini.misure.map(el => el[campo]))].sort((a, b) => a - b);
|
||||
// Min/max del listino vincolanti: fuori range la misura non è disponibile.
|
||||
if (!misure.length || v < misure[0]) return null;
|
||||
return misure.find(m => m >= v) ?? null;
|
||||
},
|
||||
getConfigurationPrice: (data) => {
|
||||
|
||||
let prezzo_finale = data.prezzo_base;
|
||||
let percentuale_finiture = 0;
|
||||
let prezzo_accessori = 0;
|
||||
let percentuale_vetro = 0;
|
||||
let percentuale_accessori = 0;
|
||||
|
||||
const config = {};
|
||||
|
||||
if(data.finitura && data.override_supplemento_finitura !== true){
|
||||
let supplemento = (listini.finiture ?? []).find((el) => el.finitura === data.finitura);
|
||||
|
||||
config.tipo_finitura = supplemento;
|
||||
|
||||
percentuale_finiture = toPercentuale(supplemento?.percentuale);
|
||||
}
|
||||
|
||||
if(data.cerniere_scomparsa != null){
|
||||
let supplemento = listini.cerniere.find((el) => {
|
||||
if(el.valore == data.cerniere_scomparsa)
|
||||
return el;
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/sistemi/${encodeURIComponent(sistema)}/modelli/${encodeURIComponent(modello)}/prezzo`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
||||
body: JSON.stringify(scelte),
|
||||
signal: controller.signal,
|
||||
});
|
||||
const data = await res.json();
|
||||
|
||||
config.cerniere_scomparsa = supplemento;
|
||||
|
||||
prezzo_accessori += toPrezzo(supplemento?.prezzo);
|
||||
}
|
||||
|
||||
if(data.altezza_maniglia != null){
|
||||
|
||||
let supplemento = listini.altezza_maniglia.find((el) => {
|
||||
if(el.valore == data.altezza_maniglia)
|
||||
return el;
|
||||
});
|
||||
|
||||
config.altezza_maniglia = supplemento;
|
||||
|
||||
prezzo_accessori += toPrezzo(supplemento?.prezzo);
|
||||
}
|
||||
|
||||
if(data.azionamento != null){
|
||||
|
||||
let supplemento = listini.azionamento.find((el) => {
|
||||
if(el.valore == data.azionamento)
|
||||
return el;
|
||||
});
|
||||
|
||||
config.azionamento = supplemento;
|
||||
|
||||
prezzo_accessori += toPrezzo(supplemento?.prezzo);
|
||||
}
|
||||
|
||||
if(data.vetro != null){
|
||||
let vetro = listini.vetri.find((el) => {
|
||||
if(el.id == data.vetro)
|
||||
return el;
|
||||
});
|
||||
|
||||
config.vetro = vetro;
|
||||
percentuale_vetro = toPercentuale(vetro?.percentuale);
|
||||
}
|
||||
|
||||
|
||||
prezzo_finale = (prezzo_finale * (1 + percentuale_finiture+percentuale_vetro+data.percentuale_accessori)) + prezzo_accessori;
|
||||
// IVA disabilitata: i prezzi sono IVA esclusa
|
||||
// prezzo_finale += prezzo_finale * 22 / 100;
|
||||
|
||||
return {
|
||||
prezzo: prezzo_finale,
|
||||
configurazione: config
|
||||
return res.ok ? data : { errore: data.errors ?? { prezzo: [data.message] } };
|
||||
} catch (e) {
|
||||
if (e.name === 'AbortError') return { annullata: true };
|
||||
return { errore: { prezzo: ['Prezzo non disponibile'] } };
|
||||
} finally {
|
||||
if (richiestaCorrente === controller) richiestaCorrente = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
export default PriceCalculator;
|
||||
export default PriceCalculator;
|
||||
|
||||
+52
-16
@@ -13,6 +13,7 @@ import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
import 'bootstrap/dist/js/bootstrap.bundle.min.js';
|
||||
import Configurator from './configurator.js';
|
||||
import AuthGate from './auth_gate.js';
|
||||
import ConfigurazionePrecedente from './configurazione_precedente.js';
|
||||
import { API_BASE as PREVENTIVI_API_BASE } from './api_base.js';
|
||||
|
||||
|
||||
@@ -46,6 +47,17 @@ const configReady = (async () => {
|
||||
|
||||
let invioPreventivoInCorso = false;
|
||||
|
||||
// Conferma non bloccante (markup fisso #toast in index.html).
|
||||
let timerToast = null;
|
||||
const mostraToast = (messaggio) => {
|
||||
const toast = document.getElementById('toast');
|
||||
if (!toast) return;
|
||||
toast.querySelector('.toast-messaggio').textContent = messaggio;
|
||||
toast.classList.add('visibile');
|
||||
clearTimeout(timerToast);
|
||||
timerToast = setTimeout(() => toast.classList.remove('visibile'), 3000);
|
||||
};
|
||||
|
||||
// Mostra/aggiorna il link "Vai al preventivo" (markup fisso in index.html, sopra la cart-container).
|
||||
window.mostraLinkPreventivo = (token, count) => {
|
||||
const link = document.getElementById('vai-al-preventivo');
|
||||
@@ -88,6 +100,8 @@ ripristinaLinkPreventivo();
|
||||
|
||||
$(() => {
|
||||
|
||||
ConfigurazionePrecedente.intercettaCambioModello();
|
||||
|
||||
$('.cart-button').on('click', async () => {
|
||||
|
||||
if ($('.cart-button').hasClass('disabled') || invioPreventivoInCorso)
|
||||
@@ -97,18 +111,16 @@ $(() => {
|
||||
try {
|
||||
const params = ParamsParser.getQueryParams();
|
||||
const img = await window.generaImagine();
|
||||
const conf = window.configurazione || {};
|
||||
const inner = conf.configurazione || {};
|
||||
const inner = window.configurazione?.configurazione || {};
|
||||
|
||||
// Prezzo e descrizione delle scelte li ricalcola il backoffice: si inviano solo i codici
|
||||
// (letti ora dal menu) e le voci descrittive che non incidono sul prezzo.
|
||||
const body = {
|
||||
token: localStorage.getItem(CART_TOKEN_KEY) || undefined,
|
||||
sistema: params.sistema,
|
||||
type: params.type,
|
||||
nome: inner.nome,
|
||||
larghezza: parseInt(inner.width) || null,
|
||||
altezza: parseInt(inner.height) || null,
|
||||
qty: parseInt($('.quantity-input').val()) || 1,
|
||||
prezzo: conf.prezzo,
|
||||
scelte: Configurator.raccogliScelte(),
|
||||
payload: inner,
|
||||
img: img,
|
||||
};
|
||||
@@ -124,6 +136,8 @@ $(() => {
|
||||
const data = await res.json();
|
||||
localStorage.setItem(CART_TOKEN_KEY, data.token);
|
||||
window.mostraLinkPreventivo(data.token, data.carrello.items.length);
|
||||
ConfigurazionePrecedente.salva(params.sistema, params.type, body.scelte);
|
||||
mostraToast('Prodotto aggiunto al preventivo');
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
alert('Si è verificato un errore nell\'aggiunta al preventivo. Riprova.');
|
||||
@@ -136,7 +150,7 @@ $(() => {
|
||||
const start = async () => {
|
||||
const params = ParamsParser.getQueryParams();
|
||||
|
||||
// Sistemi e modelli (con icone) sono serviti dal backoffice, già filtrati sui soli attivi
|
||||
// Sistemi e modelli (con icone) sono serviti dal backoffice, già filtrati sui soli attivi con listino
|
||||
// e nell'ordine del menu: il primo sistema è quello di default.
|
||||
const res = await fetch(`${PREVENTIVI_API_BASE}/sistemi`, { headers: { Accept: 'application/json' } });
|
||||
if (!res.ok) throw new Error(`Catalogo non disponibile (HTTP ${res.status})`);
|
||||
@@ -168,23 +182,29 @@ const start = async () => {
|
||||
window.history.replaceState(null, '', url);
|
||||
}
|
||||
|
||||
const data = await (await fetch(`${modello_folder}/${modello.cartella}/form.json?rnd=${Math.random()}`)).json();
|
||||
const data_finiture = modello.moduli.includes('modello') ? (await (await fetch(`${modello_folder}/${modello.cartella}/modello.json?rnd=${Math.random()}`)).json()).filter(el => el.id != "") : [];
|
||||
const data_maniglie = modello.moduli.includes('maniglia') ? (await (await fetch(`${modello_folder}/${modello.cartella}/maniglia.json?rnd=${Math.random()}`)).json()).filter(el => el.id != "") : [];
|
||||
// Menu, finiture, maniglie, vetri, opzioni (con le immagini) e misure del listino dal backoffice.
|
||||
const resConfigurazione = await fetch(`${PREVENTIVI_API_BASE}/sistemi/${encodeURIComponent(params.sistema)}/modelli/${encodeURIComponent(modello.id)}/configurazione`, { headers: { Accept: 'application/json' } });
|
||||
if (!resConfigurazione.ok) throw new Error(`Configurazione non disponibile per il modello ${modello.id} (HTTP ${resConfigurazione.status})`);
|
||||
const configurazione = await resConfigurazione.json();
|
||||
|
||||
const data = configurazione.form;
|
||||
const data_finiture = configurazione.finiture;
|
||||
const data_maniglie = configurazione.maniglie;
|
||||
const data_vetri = configurazione.vetri;
|
||||
const listino = configurazione.misure;
|
||||
const data_avvolgibile = modello.moduli.includes('avvolgibile') ? (await (await fetch(`${modello_folder}/${modello.cartella}/avvolgibile.json?rnd=${Math.random()}`)).json()).filter(el => el.id != "") : [];
|
||||
const data_vetri = modello.moduli.includes('vetro') ? (await (await fetch(`${modello_folder}/${modello.cartella}/vetro.json?rnd=${Math.random()}`)).json()).filter(el => el.id != "") : [];
|
||||
const listino = modello.moduli.includes('listino')
|
||||
? await (await fetch(`${PREVENTIVI_API_BASE}/sistemi/${encodeURIComponent(params.sistema)}/modelli/${encodeURIComponent(modello.id)}/listino`, { headers: { Accept: 'application/json' } })).json()
|
||||
: [];
|
||||
if (modello.moduli.includes('listino') && !listino.length)
|
||||
throw new Error(`Nessun listino caricato per il modello ${modello.id}`);
|
||||
const altezza_maniglia = modello.moduli.includes('altezza_maniglia') ? await (await fetch(`${data_folder}/altezza_maniglia_${modello.categoria}.json?rnd=${Math.random()}`)).json() : [];
|
||||
|
||||
let priceCalculator = await MenuBuilder.build(data, 'left-container', modello.cartella_texture || modello.cartella, {
|
||||
let priceCalculator = await MenuBuilder.build(data, 'left-container', modello.id, {
|
||||
modelli: modelli,
|
||||
categoria: modello.categoria,
|
||||
sistema: params.sistema,
|
||||
data_finiture: data_finiture,
|
||||
// Finiture attive nel modello per lato (menu finiture interne / esterne).
|
||||
data_finiture_interne: configurazione.finiture_interne,
|
||||
data_finiture_esterne: configurazione.finiture_esterne,
|
||||
data_maniglie: data_maniglie,
|
||||
data_avvolgibile: data_avvolgibile,
|
||||
data_vetri: data_vetri,
|
||||
@@ -194,7 +214,8 @@ const start = async () => {
|
||||
|
||||
Configurator.init(priceCalculator, {
|
||||
modello: modello,
|
||||
nome_esteso: data.nome,
|
||||
sistema: params.sistema,
|
||||
form: data,
|
||||
data_finiture: data_finiture,
|
||||
data_maniglie: data_maniglie,
|
||||
data_avvolgibile: data_avvolgibile,
|
||||
@@ -249,6 +270,9 @@ const start = async () => {
|
||||
|
||||
$("#larghezza").first().trigger("change");
|
||||
|
||||
// Scelte del modello precedente, se l'utente ha chiesto di mantenerle.
|
||||
ConfigurazionePrecedente.applica();
|
||||
|
||||
//player.addMarker("Cylinder028", 'left', 'https://www.youtube.com/embed/l_zqI0v7JcU?si=Vq2k8nWHYk13esMQ');
|
||||
//player.addMarker("marker_1", 'left', 'https://www.youtube.com/embed/l_zqI0v7JcU?si=Vq2k8nWHYk13esMQ');
|
||||
//player.addMarker("marker_2", 'right', 'https://www.youtube.com/embed/LCi9jdF_4MQ?si=b8cyxSOI2sKEmmcm');
|
||||
@@ -368,6 +392,18 @@ const start = async () => {
|
||||
render();
|
||||
}
|
||||
|
||||
// Mostra / nasconde le mesh di una categoria (es. maniglia col motore): usate dalle opzioni
|
||||
// dei menu a tendina tramite i parametri func e categoria impostati nel backoffice.
|
||||
window.mostraCategoria = (categoria) => {
|
||||
player.showObject(categoria);
|
||||
render();
|
||||
}
|
||||
|
||||
window.nascondiCategoria = (categoria) => {
|
||||
player.hideObject(categoria);
|
||||
render();
|
||||
}
|
||||
|
||||
window.nascondiCerniere = (categoria) => {
|
||||
player.hideObject(categoria);
|
||||
render();
|
||||
|
||||
Reference in New Issue
Block a user