versione 0.8

This commit is contained in:
2026-07-13 11:58:30 +02:00
commit 761db06329
1723 changed files with 159016 additions and 0 deletions
+563
View File
@@ -0,0 +1,563 @@
import { gsap } from 'gsap';
var APP = {
Player: function (canvas_container) {
var renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
var loader = new THREE.ObjectLoader();
var camera, scene;
var events = {};
const markers = [];
var controls = null;
var dom = document.getElementById(canvas_container);
//var dom = document.createElement( 'div' );
dom.appendChild(renderer.domElement);
this.dom = dom;
this.canvas = renderer.domElement;
this.width = dom.innerWidth;
this.height = dom.innerHeight;
this.load = function (json) {
var project = json.project;
if (project.shadows !== undefined) renderer.shadowMap.enabled = project.shadows;
if (project.shadowType !== undefined) renderer.shadowMap.type = project.shadowType;
if (project.toneMapping !== undefined) renderer.toneMapping = project.toneMapping;
if (project.toneMappingExposure !== undefined) renderer.toneMappingExposure = project.toneMappingExposure;
this.setScene(loader.parse(json.scene));
this.setCamera(loader.parse(json.camera));
controls = new OrbitControls(camera, renderer.domElement);
controls.minPolarAngle = THREE.MathUtils.degToRad(90);
controls.maxPolarAngle = THREE.MathUtils.degToRad(90);
controls.enableDamping = true;
controls.dampingFactor = 0.1;
controls.minDistance = 1.2;
controls.maxDistance = 20;
controls.addEventListener('change', function () {
renderer.render(scene, camera);
});
events = {
init: [],
start: [],
stop: [],
keydown: [],
keyup: [],
pointerdown: [],
pointerup: [],
pointermove: [],
update: []
};
var scriptWrapParams = 'player,renderer,scene,camera';
var scriptWrapResultObj = {};
for (var eventKey in events) {
scriptWrapParams += ',' + eventKey;
scriptWrapResultObj[eventKey] = eventKey;
}
var scriptWrapResult = JSON.stringify(scriptWrapResultObj).replace(/\"/g, '');
for (var uuid in json.scripts) {
var object = scene.getObjectByProperty('uuid', uuid, true);
if (object === undefined) {
console.warn('APP.Player: Script without object.', uuid);
continue;
}
var scripts = json.scripts[uuid];
for (var i = 0; i < scripts.length; i++) {
var script = scripts[i];
var functions = (new Function(scriptWrapParams, script.source + '\nreturn ' + scriptWrapResult + ';').bind(object))(this, renderer, scene, camera);
for (var name in functions) {
if (functions[name] === undefined) continue;
if (events[name] === undefined) {
console.warn('APP.Player: Event type not supported (', name, ')');
continue;
}
events[name].push(functions[name].bind(object));
}
}
}
dispatch(events.init, arguments);
};
this.addToScene = function (object) {
scene.add(object);
}
this.swapModel = function (model_name) {
let found = false;
scene.traverse((mod) => {
if (mod.userData.name == model_name) {
mod.visible = true;
found = true;
}
else {
if (mod.name == 'infisso')
mod.visible = false;
}
});
renderer.render(scene, camera);
return found;
}
this.hideModel = function (model_name) {
let modello = scene.getObjectByName(model_name);
modello.visible = false;
}
this.viewFrontSide = function () {
camera.position.set(0, 0, 0.7);
const box = new THREE.Box3().setFromObject(scene);
const size = box.getSize(new THREE.Vector3());
const center = box.getCenter(new THREE.Vector3());
const maxDim = Math.max(size.x, size.y, size.z);
const fov = camera.fov * (Math.PI / 180);
const cameraZ = Math.abs(maxDim / (2 * Math.tan(fov / 2))) / 0.4;
const direction = new THREE.Vector3().subVectors(camera.position, center).normalize();
camera.position.copy(center).add(direction.multiplyScalar(cameraZ));
camera.lookAt(center);
camera.updateProjectionMatrix();
}
this.flipView = function () {
if (!scene || !camera) return;
const box = new THREE.Box3().setFromObject(scene);
const center = box.getCenter(new THREE.Vector3());
const size = box.getSize(new THREE.Vector3());
const maxDim = Math.max(size.x, size.y, size.z);
const fov = camera.fov * (Math.PI / 180);
const cameraZ = Math.abs(maxDim / (2 * Math.tan(fov / 2))) / 0.4;
const radius = cameraZ;
const duration = 1;
const startAngle = scene.rotation.y
const endAngle = startAngle + Math.PI; // Angolo finale (180 gradi in radianti)
gsap.to(camera.position, {
x: 0,
y: 0,
z: cameraZ,
duration: duration,
ease: "power1.inOut",
onUpdate: () => {
//camera.lookAt(center); // Mantieni la camera puntata verso il centro
},
onComplete: () => {
//controls.update(); // Aggiorna i controlli al termine dell'animazione
}
});
gsap.to(scene.rotation, {
y: scene.rotation.y + Math.PI,
duration: duration,
ease: "power1.inOut",
onUpdate: () => {
renderer.render(scene, camera); // Render the scene during the animation
},
onComplete: () => {
//controls.update(); // Update controls after the animation
}
});
};
this.moveCameraToFront = function (objectName) {
if (!scene || !camera) return;
// Trova l'oggetto nella scena
const object = scene.getObjectByName(objectName);
if (!object) {
console.warn(`Oggetto con nome "${objectName}" non trovato.`);
return;
}
// Ottieni la posizione dell'oggetto
const targetPosition = object.getWorldPosition(new THREE.Vector3());
// Calcola la nuova posizione della telecamera (davanti all'oggetto)
const offset = new THREE.Vector3(0, 0, -2); // Offset per posizionare la telecamera davanti
const newCameraPosition = targetPosition.clone().add(offset);
// Anima la posizione della telecamera con GSAP
gsap.to(camera.position, {
x: newCameraPosition.x,
y: newCameraPosition.y,
z: newCameraPosition.z,
duration: 1.5, // Durata dell'animazione in secondi
ease: "power1.inOut",
onUpdate: () => {
camera.lookAt(targetPosition); // Mantieni la telecamera puntata verso l'oggetto
},
onComplete: () => {
controls.update(); // Aggiorna i controlli al termine dell'animazione
}
});
};
this.setCamera = function (value) {
camera = value;
camera.aspect = this.width / this.height;
camera.updateProjectionMatrix();
};
this.setScene = function (value) {
scene = value;
};
this.rotateBackground = function (radians) {
scene.backgroundRotation.y = radians;
scene.environmentRotation.y = radians;
renderer.render(scene, camera);
};
this.setPixelRatio = function (pixelRatio) {
renderer.setPixelRatio(pixelRatio);
};
this.setSize = function (width, height) {
this.width = width;
this.height = height;
if (camera) {
camera.aspect = this.width / this.height;
camera.updateProjectionMatrix();
}
renderer.setSize(width, height);
};
function dispatch(array, event) {
for (var i = 0, l = array.length; i < l; i++) {
array[i](event);
}
}
var time, startTime, prevTime;
function animate() {
time = performance.now();
try {
dispatch(events.update, { time: time - startTime, delta: time - prevTime });
} catch (e) {
console.error((e.message || e), (e.stack || ''));
}
if (controls.update()) {
renderer.render(scene, camera);
updateMarkers();
prevTime = time;
}
}
this.play = function () {
startTime = prevTime = performance.now();
document.addEventListener('keydown', onKeyDown);
document.addEventListener('keyup', onKeyUp);
document.addEventListener('pointerdown', onPointerDown);
document.addEventListener('pointerup', onPointerUp);
document.addEventListener('pointermove', onPointerMove);
dispatch(events.start, arguments);
renderer.setAnimationLoop(animate);
};
this.stop = function () {
document.removeEventListener('keydown', onKeyDown);
document.removeEventListener('keyup', onKeyUp);
document.removeEventListener('pointerdown', onPointerDown);
document.removeEventListener('pointerup', onPointerUp);
document.removeEventListener('pointermove', onPointerMove);
dispatch(events.stop, arguments);
renderer.setAnimationLoop(null);
};
this.render = function (time) {
dispatch(events.update, { time: time * 1000, delta: 0 /* TODO */ });
renderer.render(scene, camera);
};
this.dispose = function () {
renderer.dispose();
camera = undefined;
scene = undefined;
};
this.showObject = (name) => {
scene.traverse((object) => {
if (object.isMesh) {
if (object.userData.categoria == name) {
object.visible = true;
}
}
});
}
this.hideObject = (name) => {
scene.traverse((object) => {
if (object.isMesh) {
if (object.userData.categoria == name) {
object.visible = false;
}
}
});
}
this.addMarker = (name, popup_position, video_url) => {
const object = scene.getObjectByName(name);
const position = object.getWorldPosition(new THREE.Vector3());
const markerContainer = document.getElementById('markerContainer');
const marker = document.createElement('div');
marker.className = 'marker';
markerContainer.appendChild(marker);
const popup = document.createElement('div');
popup.className = 'popup';
popup.style.display = 'none';
popup.innerHTML = `
<iframe width="100%" height="100%" src="${video_url}" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>`;
popup.classList.add(popup_position);
popup.dataset.position = popup_position;
marker.appendChild(popup);
object.visible = false;
marker.onclick = () => {
if (popup.style.display == 'none') {
popup.style.display = 'block';
} else {
popup.style.display = 'none';
}
};
markers.push({ element: marker, position: position });
}
this.changeXscale = (name, scale) => {
let infisso = scene.getObjectByName(name);
scene.traverse((object) => {
if (object.name == name) {
object.scale.x = scale;
}
});
//infisso.scale.x = scale;
}
this.changeTexture = async (data) => {
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;
// 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.
if (data.isStale && data.isStale()) return;
scene.traverse((object) => {
if (object.isMesh) {
if (data.categories.includes(object.userData.categoria)) {
object.material.map = null;
object.material.bumpMap = null;
if (texture != null) {
texture.colorSpace = THREE.SRGBColorSpace;
object.material.map = texture;
}
if (bump != null) {
bump.colorSpace = THREE.NoColorSpace;
object.material.bumpMap = bump;
}
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);
object.material.needsUpdate = true;
}
}
});
},
this.changeColor = async (data) => {
if (data.color_valore_corpo_base != "") {
scene.traverse((object) => {
if (object.isMesh) {
if (data.categories.includes(object.userData.categoria)) {
object.material.color = new THREE.Color(data.color_valore_corpo_base);
object.material.needsUpdate = true;
}
}
});
}
}
const updateMarkers = () => {
markers.forEach(marker => {
const vector = new THREE.Vector3(marker.position.x, marker.position.y, marker.position.z);
vector.project(camera);
const popup = marker.element.querySelector('.popup');
if (camera.rotation.y < 0) {
if (popup.dataset.position == 'right') {
popup.classList.add('left');
popup.classList.remove('right');
} else {
popup.classList.add('right');
popup.classList.remove('left');
}
} else {
if (popup.dataset.position == 'right') {
popup.classList.add('right');
popup.classList.remove('left');
} else {
popup.classList.add('left');
popup.classList.remove('right');
}
}
const x = (vector.x * 0.5 + 0.5) * dom.clientWidth;
const y = (vector.y * -0.5 + 0.5) * dom.clientHeight;
marker.element.style.left = `${x}px`;
marker.element.style.top = `${y}px`;
});
}
function onKeyDown(event) {
dispatch(events.keydown, event);
}
function onKeyUp(event) {
dispatch(events.keyup, event);
}
function onPointerDown(event) {
dispatch(events.pointerdown, event);
}
function onPointerUp(event) {
dispatch(events.pointerup, event);
}
function onPointerMove(event) {
dispatch(events.pointermove, event);
}
}
};
export { APP };
+187
View File
@@ -0,0 +1,187 @@
let shared_data = null;
const Configurator = {
init: (priceCalculator, config_data) => {
Configurator.priceCalculator = priceCalculator;
shared_data = config_data;
},
enable: () => {
$(("#larghezza, #altezza, select, input[name=vetro], input[type=radio]")).on('input', (e) => {
Configurator.checkTextures()
let res = Configurator.generatePrice();
if(res == null) return;
res = Configurator.generateConfigData(res);
$(".prezzo").html(`€ ${res.prezzo.toFixed(2)}<span>IVA Inclusa</span>`);
window.configurazione = res;
if($(e.target).data('model')){
caricaModello($(e.target).data('model'));
}
});
},
generatePrice: () => {
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;
}
});
//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();
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();
let price = Configurator.priceCalculator.getGridPrice(config_data.width, config_data.height);
if(price < 0){
$(".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');
}
config_data.prezzo_base = price;
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;
},
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;
});
//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;
}
let val_maniglia = $(`input[name=maniglia]:checked`).val();
let finitura_maniglia = shared_data.data_maniglie.find(m => m.id == val_maniglia);
let altezza = parseFloat(config.configurazione.height);
let mod_altezza = altezza % 10;
if(mod_altezza > 0) altezza = (altezza - mod_altezza) + 10;
let altezza_maniglia = shared_data.altezza_maniglia.find((m) => m.altezza == altezza);
if(altezza_maniglia != null){
config.configurazione.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];
}
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');
}
},
priceCalculator: null
}
export default Configurator;
+443
View File
@@ -0,0 +1,443 @@
import Configurator from './configurator.js';
import PriceCalculator from './price_calculator.js';
let container = null;
let textureRequestVersion = {};
let data_finiture = null;
let data_maniglie = null;
let data_avvolgibile = null;
let data_vetri = null;
let listino = null;
const MenuBuilder = {
build: async (params, parent_element_id, model, data) => {
data_finiture = data.data_finiture;
data_maniglie = data.data_maniglie;
data_avvolgibile = data.data_avvolgibile;
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);
let template_file = params.template != null ? params.template : 'menu.html';
let template = await fetch(`./assets/_${template_file}?rnd=${Math.random()}`);
template = await template.text();
container = document.getElementById(parent_element_id);
container.innerHTML = template;
let isMobile = window.innerWidth <= 1108;
console.log("is mobile", isMobile);
if (isMobile && window.innerWidth < window.innerHeight) {
console.log('Mobile portrait mode setup');
const menuHeader = $('.menu-categorie').html();
const opzioniMenu = $('.opzioni-mobile-menu').html();
$('#menu-mobile').append(menuHeader);
$('#menu-mobile').append(opzioniMenu);
$('.opzioni-mobile-menu').remove();
$('.menu-categorie').remove();
//console.log($("#menu-mobile").height());
$("#canvas-container").height(window.innerHeight - ($('#menu-mobile').height() + 90));
const menuScroll = $(".menu-scroll").html();
$(".mobile-popup .popup-content").html(menuScroll);
$(".mobile-popup").addClass("d-none");
$(".opzioni-menu").removeClass("d-none");
$(".menu-scroll").remove();
$(".mobile-popup .popup-content").css('max-height', window.innerHeight * 0.6);
$(".mobile-popup .popup-content").css('overflow-y', 'auto');
const canvas_height = $('#canvas-container').height();
$('.button-180').css('bottom', canvas_height / 2);
$(".btn-opzioni").on('click', function () {
const opzione = $(this).data('opzione');
$('.mob-menu-item').addClass('d-none');
$(`.mobile-popup-backdrop, .${opzione}-menu`).removeClass('d-none');
$(".mobile-popup").removeClass("d-none");
// porta lo scrolling del popup mobile all'inizio quando si apre
const popupContent = document.querySelector('.mobile-popup .popup-content');
if (popupContent) {
popupContent.scrollTop = 0;
// assicurati che il primo elemento sia visibile / focusabile
const firstChild = popupContent.querySelector('*');
if (firstChild && typeof firstChild.focus === 'function') firstChild.focus();
}
});
$(".btn-popup").removeClass("d-none");
$(".btn-popup, .mobile-popup-backdrop, .mobile-popup .btn-container").on('click', function () {
$(".mobile-popup-backdrop, .mobile-popup").addClass("d-none");
});
//console.log('Mobile portrait mode detected');
} else if (isMobile && window.innerWidth >= window.innerHeight) {
$("#left-container").addClass('overflow-auto');
//console.log('Mobile landscape mode detected');
}
let listino_min = listino[0];
let listino_max = listino[listino.length - 1];
const maxLarghezza = Math.max(...listino.map(item => item.larghezza));
const maxAltezza = Math.max(...listino.map(item => item.altezza));
const minLarghezza = Math.min(...listino.map(item => item.larghezza));
const minAltezza = Math.min(...listino.map(item => item.altezza));
$('.titolo').html(`${params.nome}`);
$('.larghezza-max').html(`${minLarghezza}-${maxLarghezza}cm`);
$('.altezza-max').html(`${minAltezza}-${maxAltezza}cm`);
window.sistema = data.sistema;
buildListaModelli(data.modelli, data.modello_id, data.categoria, data.sistema);
if (!isMobile) {
let hMisure = $(".menu-categorie").outerHeight();
$('.menu-scroll').height(window.innerHeight - hMisure);
} else {
if(window.innerWidth < window.innerHeight)
container = document.querySelector('.mobile-popup');
}
for (const key in params.radios) {
let options = params.radios[key];
let res = buildRadios(options, key, model);
container.querySelector(`.${key}`).innerHTML = res.html;
console.log(key);
if (options.length > 0)
$(`.cnt-${key}`).removeClass('d-none');
if (res.has_sub_options) {
let radios = container.querySelectorAll(`.${key} input[type=radio]`);
radios.forEach((radio) => {
radio.addEventListener('change', () => {
toggleSubOption(key, { target: radio });
});
});
}
}
if (isMobile) {
$(".sistema-menu, .finiture-menu, .accessori-menu,.vetro-menu, .misure-menu").addClass('d-none');
}
$('.apertura input').on('change', () => {
let val = $('.apertura input:checked').val();
let p = params.radios.apertura.find(r => r.label == val);
cambiaApertura('infisso', p.scale_x);
});
/* scorro i tipi di finitura nella form e se è uniforme nascondo i tasti non necessari */
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(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');
$('.btn-opzioni.finiture-interne p').text('Finiture');
$('.btn-opzioni.finiture-interne img').attr('src','./assets/img/opzioni/finiture.png');
$('.finiture-interne-menu .menu-header').text('Finiture');
}
})
$('.info-popup').on('click', function (e) {
e.preventDefault();
if (!$('#popup').hasClass('d-none')) {
$('#popup').addClass('d-none');
$('#popup img').remove();
}
const imageUrl = $(this).data('image-url');
const imgElement = document.createElement('img');
imgElement.src = imageUrl;
imgElement.alt = 'Popup Image';
imgElement.classList.add('img-fluid', 'popup-image');
document.querySelector('#popup').appendChild(imgElement);
$("#popup").removeClass('d-none');
});
$('#popup .close').on('click', function () {
$('#popup').addClass('d-none');
$('#popup img').remove();
});
for (const key in params.select) {
let options = params.select[key];
buildSelect(options, key);
}
/* opzioni disabilitate per richiesta del cliente */
/*
if(params.select.cerniere_a_scomparsa != null)
buildSelect(params.select.cerniere_a_scomparsa, 'cerniere_a_scomparsa');
if(params.select.altezza_maniglia != null)
buildSelect(params.select.altezza_maniglia, 'altezza_maniglia');
*/
let size = PriceCalculator.getMinSize();
$('#larghezza').val(size.larghezza + '.00');
$('#altezza').val(size.altezza + '.00');
return PriceCalculator;
}
};
const buildListaModelli = (modelli, modello, categoria, sistema) => {
let lista_modelli = modelli.filter(m => m.enabled !== false);
let html = '';
lista_modelli.forEach(m => {
let selected = m.id == modello ? 'selected' : '';
html += `<div class="d-inline btn-categoria">
<a href="./?type=${m.id}&sistema=${sistema}">
<img class="${selected}" src="./assets/data/icone/${m.id}.jpg" alt="${m.id}" />
</a>
<p>${m.nome}</p>
</div>`;
});
$('.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}`);
select.innerHTML = ``;
options.forEach(option => {
select.innerHTML += `<option value="${option.valore}">${option.label}</option>`;
});
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);
}
if (el.flip != null)
checkFlip(el.flip);
});
$(`.cnt-${parent}`).removeClass('d-none');
}
const buildTextureRadio = async (model, parent, icon_folder, texture_folder, categories, data, tipo = '') => {
let html = '';
let defaultIndex = data.findIndex(option => option.predefinito === true);
if (defaultIndex < 0) defaultIndex = 0;
data.forEach((option, i) => {
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>`;
}
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}">
<div class="option-content">
<img src="./assets/data/immagini/${window.sistema}/${model}/${icon_folder}/${option.anteprima_img}" alt="${option.descrizione}">
<span class="checkmark"></span>
</div>
</label>
<div class="option-label">${option.descrizione} ${popup_link}</div>
</div>`;
let radio = document.createElement('div');
radio.innerHTML = html;
radio = radio.firstChild;
radio.addEventListener('change', async (e) => {
textureRequestVersion[parent] = (textureRequestVersion[parent] || 0) + 1;
const requestVersion = textureRequestVersion[parent];
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");
await cambiaTexture(option);
if (isStale()) return;
if (option.categories.includes('interno')) {
option.categories = ['corpo_base'];
cambiaColoreCB(option);
}
});
//if(!container.querySelector(`.menu-options.${parent}`)) console.log(parent);
container.querySelector(`.menu-options.${parent}`).appendChild(radio);
});
$(`.cnt-${parent}`).removeClass('d-none');
//container.querySelector(`.${parent}`).innerHTML = html;
};
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 = '';
let has_options_class = '';
let container_id = '';
if (option.opzioni != null) {
has_sub_options = true;
has_options = true;
for (const key_opt in option.opzioni) {
let options = option.opzioni[key_opt];
console.log(key_opt)
let res = buildRadios(options, key_opt, model);
container.querySelector(`.${key_opt}`).innerHTML = res.html;
sub_option_container = container.querySelector(`.${key_opt}`).parentElement.parentElement;
sub_option_container.classList.add('d-none');
}
}
if (option.model != null) {
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)"`;
container_id = `data-toggle-id="${sub_option_container.id}"`;
}
let checked = i == 0 ? 'checked' : '';
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}">
<div class="option-content">
<img src="./assets/data/immagini/${window.sistema}/${model}/${option.icon}" alt="${option.label}">
<span class="checkmark"></span>
</div>
</label>
<div class="option-label">${option.label}</div>
</div>`;
});
return {
html: html,
has_sub_options: has_sub_options
};
};
window.toggleSubOption = (id, e) => {
let parent_class = id + '-sub-option';
const sub_option_container = container.querySelector(`.${parent_class}`);
if (e.target.classList.contains('has-options')) {
container.querySelector(`#${e.target.dataset.toggleId}`).classList.remove('d-none');
} else {
sub_option_container.classList.add('d-none');
}
}
export default MenuBuilder;
+14
View File
@@ -0,0 +1,14 @@
const ParamsParser = {
getQueryParams: () => {
const params = new URLSearchParams(window.location.search);
const queryParams = {};
params.forEach((value, key) => {
queryParams[key] = value;
});
return queryParams;
}
};
export default ParamsParser;
+122
View File
@@ -0,0 +1,122 @@
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;
};
const PriceCalculator = {
init: async (data) => {
listini = data;
},
getMinSize: () => {
const firstSize = listini.misure.find(item => item.prezzo !== 0);
return firstSize;
},
getGridPrice: (width, height) => {
let altezza = parseFloat(height);
let larghezza = parseFloat(width);
let mod_altezza = altezza % 10;
let mod_larghezza = larghezza % 10;
if(mod_altezza > 0) altezza = (altezza - mod_altezza) + 10;
if(mod_larghezza > 0) larghezza = (larghezza - mod_larghezza) + 10;
let misure = listini.misure.find((el) => {
if(el.altezza == altezza && el.larghezza == larghezza)
return el;
});
if(misure == null) return -1;
return misure.prezzo;
},
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;
});
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;
prezzo_finale += prezzo_finale * 22 / 100; // IVA 22%
return {
prezzo: prezzo_finale,
configurazione: config
}
}
}
export default PriceCalculator;
+349
View File
@@ -0,0 +1,349 @@
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import $ from 'jquery';
import { APP } from './app.js';
import ParamsParser from './params_parser.js';
import MenuBuilder from './menu_builder.js';
import '@fortawesome/fontawesome-free/css/all.min.css';
import './price_calculator.js';
import 'bootstrap/dist/css/bootstrap.min.css';
import 'bootstrap/dist/js/bootstrap.bundle.min.js';
import Configurator from './configurator.js';
window.$ = $;
window.THREE = THREE; // Used by APP Scripts.
window.OrbitControls = OrbitControls; // Used by APP Scripts.
window.GLTFLoader = GLTFLoader; // Used by APP Scripts.
$(() => {
$('.cart-button').on('click', async () => {
if($('.cart-button').hasClass('disabled'))
return;
const img = await window.generaImagine();
const data = {
payload: window.configurazione,
img: img,
qty: parseInt($('.quantity-input').val()),
fn: 'aggiungi_prodotto'
};
window.parent.postMessage(data, "https://www.infissionline.it");
window.parent.postMessage(data, "https://infissionline.test");
});
});
const start = async () => {
const params = ParamsParser.getQueryParams();
let data_folder = `./assets/data/sistemi/${params.sistema}`;
let modello_folder = `./assets/data/modelli/${params.sistema}`;
const modelli = await (await fetch(`${data_folder}/modelli.json?rnd=` + Math.random())).json();
const modello = modelli.find(m => m.id === params.type);
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 != "") : [];
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(`${modello_folder}/${modello.cartella}/listino.json?rnd=${Math.random()}`)).json() : [];
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, {
modelli: modelli,
categoria: modello.categoria,
sistema: params.sistema,
data_finiture: data_finiture,
data_maniglie: data_maniglie,
data_avvolgibile: data_avvolgibile,
data_vetri: data_vetri,
listino: listino,
modello_id: modello.id
});
Configurator.init(priceCalculator, {
modello: modello,
nome_esteso: data.nome,
data_finiture: data_finiture,
data_maniglie: data_maniglie,
data_avvolgibile: data_avvolgibile,
data_vetri: data_vetri,
altezza_maniglia: altezza_maniglia,
listino: listino
});
Configurator.enable();
$(`.menu-header-sistema .${params.sistema}`).addClass('selected');
$(`.v-divider.${params.sistema}`).addClass('d-none');
var player = new APP.Player('canvas-container');
var playerOff = new APP.Player('canvas-offscreen');
var loader = new THREE.FileLoader();
loader.load('./assets/app.json', function (text) {
player.load(JSON.parse(text));
playerOff.load(JSON.parse(text));
player.rotateBackground(Math.PI);
playerOff.rotateBackground(Math.PI);
playerOff.setSize(playerOff.dom.clientWidth, playerOff.dom.clientHeight);
var gltfLoader = new GLTFLoader();
gltfLoader.load(`./assets/models/${params.sistema}/${modello.modello}`, function (gltf) {
document.getElementById('loadingBarContainer').style.display = 'none';
//gltf.scene.rotation.y = THREE.MathUtils.degToRad(80);
gltf.scene.name = 'infisso';
window.current_model = modello.modello;
gltf.scene.userData.name = modello.modello;
//$("input[type=radio]").first().trigger("click");
gltf.scene.position.y = 0;
gltf.scene.position.z = 0.1;
//gltf.scene.rotation.set(0, Math.PI, 0);
player.addToScene(gltf.scene);
player.viewFrontSide();
playerOff.addToScene(gltf.scene.clone());
playerOff.viewFrontSide();
$('#canvas-offscreen').addClass('d-none');
player.dom.style.position = 'relative';
if (params.type != 'cassonetto') {
$("input[name=maniglia]").first().trigger("click");
$("input[name=finitura_interna]").eq(1).trigger("click");
$("input[name=finitura_interna]").eq(0).trigger("click");
}
$("#larghezza").first().trigger("change");
//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');
},
function (xhr) {
var percentComplete = (xhr.loaded / xhr.total) * 100;
document.getElementById('loadingBarProgress').style.width = percentComplete + '%';
document.getElementById('loadingPercentage').innerText = Math.round(percentComplete) + '%';
}, function (error) {
console.error('An error happened while loading the GLB file:', error);
});
player.setSize(player.dom.clientWidth, player.dom.clientHeight);
player.play();
window.addEventListener('resize', function () {
//console.log(player.dom.clientWidth, player.dom.clientHeight);
player.setSize(player.dom.clientWidth, player.dom.clientHeight);
player.render();
});
});
$('.quantity-btn').on('click', (e) => {
const input = document.querySelector('.quantity-input');
let value = parseInt(input.value);
if (e.target.classList.contains('increment')) {
input.value = value + 1;
} else if (e.target.classList.contains('decrement') && value > 1) {
input.value = value - 1;
}
});
window.generaImagine = async () => {
playerOff.render();
$('#canvas-offscreen').removeClass('d-none');
const canvas = $('#canvas-offscreen canvas')[0];
const dataUrl = await canvas.toDataURL('image/png');
$('#canvas-offscreen').addClass('d-none');
return dataUrl;
const a = document.createElement('a');
a.href = dataUrl;
a.download = 'immagine.png';
a.click();
}
window.render = () => {
player.render();
}
let currentView = 'front';
window.cambiaTexture = (data) => {
return new Promise(async (resolve, reject) => {
try {
await player.changeTexture(data)
//console.log(data)
//console.log('window.rotate', window.rotate);
if (data && data.categories.includes('esterno') && currentView !== 'back'){
if(window.rotate){
player.flipView();
currentView = 'back';
}
}
if (data && (data.categories.includes('interno') || data.categories.includes('maniglia')) && currentView !== 'front'){
if(window.rotate){
player.flipView();
currentView = 'front';
}
}
render();
resolve();
} catch (error) {
reject(error);
}
});
}
window.flip180 = () => {
player.flipView();
currentView = currentView === 'front' ? 'back' : 'front';
render();
}
window.checkFlip = (direction) => {
if(direction != currentView)
flip180();
}
window.cambiaColoreCB = (data) => {
player.changeColor(data);
render();
}
window.cambiaApertura = (name, scale) => {
player.changeXscale(name, scale);
render();
}
window.mostraCerniere = (categoria) => {
player.showObject(categoria);
render();
}
window.nascondiCerniere = (categoria) => {
player.hideObject(categoria);
render();
}
window.rotate = true;
window.caricaModello = (modello) => {
let textureButtons = $(".texture-button:checked");
window.rotate = false;
const radios = {};
$('.texture-button[name]').each(function() {
radios[$(this).attr('name')] ??= this;
});
document.querySelectorAll(".texture-button[name=maniglia]").forEach(el => el.checked = false)
//console.log(textureButtons)
if(player.swapModel(modello)){
for(let el in radios){
$(radios[el]).trigger("click");
}
$(textureButtons).each((i, el) => {
$(el).trigger("click");
});
setTimeout(() => {
window.rotate = true;
}, 1000);
//
return;
}
document.getElementById('loadingBarContainerAlt').style.display = 'flex';
document.getElementById('loadingBarProgressAlt').style.width = '0%';
document.getElementById('loadingPercentageAlt').innerText = '0%';
var gltfLoader = new GLTFLoader();
gltfLoader.load(`./assets/models/${modello}`, function (gltf) {
document.getElementById('loadingBarContainerAlt').style.display = 'none';
$('#canvas-offscreen').removeClass('d-none');
//gltf.scene.rotation.y = THREE.MathUtils.degToRad(80);
gltf.scene.name = 'infisso';
gltf.scene.userData.name = modello;
setTimeout(() => {
for(let el in radios){
$(radios[el]).trigger("click");
}
});
gltf.scene.position.y = 0;
gltf.scene.position.z = 0.1;
//gltf.scene.rotation.set(0, Math.PI, 0);
player.hideModel('infisso');
playerOff.hideModel('infisso');
player.addToScene(gltf.scene);
player.viewFrontSide();
playerOff.addToScene(gltf.scene.clone());
//playerOff.viewFrontSide();
$('#canvas-offscreen').addClass('d-none');
player.dom.style.position = 'relative';
if (params.type != 'cassonetto') {
$("input[name=maniglia]").first().trigger("click");
}
$("#larghezza").first().trigger("change");
$(textureButtons).each((i, el) => {
$(el).trigger("click");
});
setTimeout(() => {
window.rotate = true;
}, 1000);
//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');
},
function (xhr) {
var percentComplete = (xhr.loaded / xhr.total) * 100;
document.getElementById('loadingBarProgressAlt').style.width = percentComplete + '%';
document.getElementById('loadingPercentageAlt').innerText = Math.round(percentComplete) + '%';
}, function (error) {
console.error('An error happened while loading the GLB file:', error);
});
}
}
start();