UNPKG

vanie

Version:

Vanie es una librería diseñada para el desarrollo de interfaces de usuario interactivas en el front-end. Con un enfoque en la creación de ventanas arrastrables, Vanie ofrece una experiencia de usuario familiar, inspirada en los sistemas operativos más emb

1,041 lines (935 loc) 73.2 kB
import CompiladorCssVanie from "./CompiladorCssVanie.js"; import { VanieAdmin , globalVanie} from "./GlobalVanie.js"; import { Desplazo, Dimension , Punto ,escala} from "nauty"; /** * Clase que crea ventanas arrastrables para su uso en Front-end [saber mas...](https://github.com/NekoShooter/Vanie) */ export default class Vanie { #dimension ={inicial:new Dimension(720,480),minima:new Dimension(200,200),fija:undefined}; #posicion = {apertura:new Punto,retorno:new Punto}; #cabecera = {str:undefined,justificado:undefined,nodo:undefined,nodos:[],ico:{str:undefined,nodo:undefined,nodos:[]},titulo:{str:undefined,span:undefined}}; #lienzo = {nodos:[],iframe:undefined,dimension:undefined,fija:false,str:undefined,nodo:undefined}; #classList = {};#id = {};#padre = globalVanie.padre; #opciones = {eliminar_alcerrar: true, btnAlaIzq:undefined, redimensionar: true} #funciones = {minimizar:[],maximizar:[],media:[],cerrar:[],abrir:[]}; #registro = {fnUp:undefined,fnMov:undefined}; #desplazo = new Desplazo; #animacionActiva = {minimizar:false,cerrar:false,abrir:false} #estado = {min:false,max:false,cerrar:true,media:false,mediaPos:undefined}; #llave; #marco; #cache; #css; #v; #r; #animar_apertura = true; #estilo_pre; #identificador; #map = {archivado:false,data:0,tmp:0};#transformacion = {mov:0,dim:0} /** * @constructor _`Opcional`_ encaso de usar **globalVanie** para asignar el estilo * Inicia el objeto `Vanie` * + **String** El nombre del estilo almacenado en el sistema global. * + **object** El objeto con las características de estilo para ser asignadas. * + **CompiladorCssVanie** El complilador de estilos * @param {string|{}|CompiladorCssVanie|undefined} estilo * + **String** El nombre de los estilos almacenados o predeterminados: * > [ `windows-claro` , `windows-oscuro` , `mac-claro` , `mac-oscuro` , `linux-claro` , `linux-oscuro` ] * + **object** El objeto con las características de estilo para ser asignadas. * + **CompiladorCssVanie** El complilador de estilos de algun objeto Vanie: *`instanciaVanie.compiladorCss`* */ constructor(estilo,identificador){ this.identificador = identificador; this.#posicion.apertura.bNuevo('center','center'); this.abrir = this.abrir.bind(this); this.minimizar = this.minimizar.bind(this); this.maximizar = this.maximizar.bind(this); this.cerrar = this.cerrar.bind(this); this.subir = this.subir.bind(this); this.animarApertura = this.animarApertura.bind(this); this.eliminarAlCerrar = this.eliminarAlCerrar.bind(this); this.eliminar = this.eliminar.bind(this); this.removerPadre = this.removerPadre.bind(this); this.limpiarCabecera = this.limpiarCabecera.bind(this); this.verificarPosicion = this.verificarPosicion.bind(this); this.eliminarDimensionFija = this.eliminarDimensionFija.bind(this); this.desconectarseDelGestor = this.desconectarseDelGestor.bind(this); this.eliminar(); this.#funciones = {minimizar:[],maximizar:[],media:[],cerrar:[],abrir:[]}; this.#marco = undefined; this.#css = globalVanie.agregarEstilo(estilo); if(!this.#css) this.#css = globalVanie.obtenerEstilo(estilo); this.#estilo_pre = this.#css && estilo; this.#llave = VanieAdmin.registrarNuevaVentana(this); VanieAdmin.activarRegistro(this.#llave,this);} /** * Elimina la ventana creada, pero no las características almacenadas. */ eliminar(){ if(this.estaConstruido && !this.#estado.cerrar && !this.#animar_apertura && this.esVisible && this.#llave) VanieAdmin.oculta(this); this.removerPadre(); this.#estado = {min:false,max:false,cerrar:true,media:false,mediaPos:undefined}; this.#v = undefined; this.#r = undefined; this.#cache={max:{dimension:this.#dimension.inicial, posicion: this.#posicion.apertura}, media:{dimension:this.#dimension.inicial, posicion: this.#posicion.apertura}, origen:{dimension:undefined,posicion:undefined}}} //#region padre /** * Se desconecta del elemento **HTMLElement** padre. */ removerPadre(){ if(!this.estaConstruido) return; const p = this.padre; if(p) p.removeChild(this.#v.ventana);} /** * Retorna la referencia del objeto **HTMLElement** padre o **undefined** en caso de no existir. * @returns {HTMLElement|undefined} */ get padre(){ if(!this.estaConstruido || !this.#v?.ventana.parentNode) return this.#padre; this.#padre = this.#v.ventana.parentNode; return this.#padre;} /** * Asigna o remplaza un objeto **HTMLElement** como padre. * @param {HTMLElement} padre Contenedor principal del objeto **Vanie**. */ set padre(padre){this.asignarPadre(padre);} /** * Asigna o remplaza un objeto **HTMLElement** como padre. * @param {HTMLElement} padre Elemento del DOM que se convertirá en el contenedor principal para el objeto **Vanie**. */ asignarPadre(padre){ if(!(padre instanceof HTMLElement)) return; try{ const p = this.padre; if(p === padre) return; if(this.estaConstruido){ if(p){p.removeChild(this.#v.ventana);} padre.appendChild(this.#v.ventana); this.posicion = this.#posicion.apertura;} this.#padre = padre; this.#padre.style.position = 'relative'; this.#validarMarco(); this.#cabeceraAplicarMod();} catch(error){console.error('no es posible asignar el elemento como padre'); return error;}} #validarMarco(){ if(this.#marco || !this.padre) return; if(!this.#marco ) this.#marco = this.padre.querySelector(`#${this.#css?.idMarco}`); if(!this.#marco ){ this.#marco = document.createElement('div'); this.#marco.setAttribute('id',this.#css?.idMarco); this.padre.insertBefore(this.#marco,this.padre.firstChild); this.#marco.addEventListener('transitionend',()=>{ this.#marco.classList.remove(`${globalVanie.idGlobal}--animacion`);});}} //#region cebecera /** * Asigna o modifica el contenido interno del `div` perteneciente a la cabecera del objeto **Vanie**. * @param {string|HTMLElement|HTMLElement[]} inner El contenido que tendrá el `div` asignado a la cabecera del objeto **Vanie**. * + `string` : Incorpora el contenido del string en el innerHTML del div de la cabecera. ⚠ Su grado de prioridad es máximo, por lo que cualquier modificación a objetos relacionados con la cabecera puede no aplicarse. * + `HTMLElement` : Incorpora el objeto HTMLElement como un nodo hijo a la cabecera. * + `Array HTMLElement` : Incorpora cada objeto HTMLElement del Array como un nodo hijo a la cabecera. */ set cabecera(inner){ const ok = this.#innerhtml(this.#v?.cabecera,inner,this.#cabecera,()=>{this.#cabecera.titulo.span = this.#cabecera.titulo.str = undefined;}); this.#cabeceraAplicarMod(ok);} /** * Retorna la referencia del objeto **HTMLElement** perteneciente a la cabecera de la ventana o **undefined** en caso de no existir. * @returns {HTMLElement|undefined} */ get cabecera(){return this.#v?.cabecera;} /** * Asignará un título a la cabecera del objeto **Vanie**. * @param {string} str El título que se establecerá en la cabecera del objeto **Vanie**, si se deja vacío `''` este se eliminará. */ set titulo(str){ if(typeof str != 'string' || this.#cabecera.str || !this.#css) return; const TITULO = str.trim(); this.#cabecera.titulo.str = TITULO; if(!this.#cabecera.titulo.span && TITULO != ''){ const span = document.createElement('span'); span.style.overflow = 'hidden'; span.style.whiteSpace = 'nowrap'; span.style.textOverflow = 'ellipsis'; span.classList.add(...this.#css.class('bloqueado','titulo')); span.innerText = TITULO; this.#cabecera.titulo.span = span; if(this.estaConstruido){ if(this.#cabecera.nodo || this.#cabecera.nodos.length) this.#v.cabecera.insertBefore(this.#cabecera.titulo.span,this.#v.cabecera.firstChild); else this.#v.cabecera.appendChild(this.#cabecera.titulo.span);}} else if(TITULO == '') { if(this.estaConstruido) this.#v.cabecera.removeChild(this.#cabecera.titulo.span); this.#cabecera.titulo.span = this.#cabecera.titulo.str =undefined;} else{ this.#cabecera.titulo.span.innerText = TITULO;} } /** * Modifica el justificado del `div` de la cabecera del objeto **Vanie**. * @param {string} str El justificado `center` | `end` | `start` */ set justificarCabecera(str){ if(typeof str != 'string') return; const justificado = str.trim(); this.#cabecera.justificado = justificado; this.#cabeceraAplicarMod();} /** * Retorna el `string` del justificado del `div` perteneciente a la cabecera del objeto **Vanie**. * @returns {string} */ get justificarCabecera(){return this.#cabecera.justificado??'';} /** * Retorna el `string` del título. * @returns {string} */ get titulo(){return this.#cabecera.titulo.str??'';} /** * Elimina el contenido del `div` asignado a la cabecera para dejarlo completamente limpio. */ limpiarCabecera(){ if(!this.estaConstruido) return; this.#cabecera.str = undefined; this.#cabeceraAplicarMod(false); while (this.#v.cabecera.firstChild) { this.#v.cabecera.removeChild(this.#v.cabecera.firstChild);}} #cabeceraAplicarMod(condicion = true){ if(!this.estaConstruido) return; this.#cabeceraAjustarEspacio(condicion); this.#cabeceraAplicarEstilo(condicion);} #cabeceraAjustarEspacio(condicion = true){ const padding = this.#v.divBotones.offsetWidth > this.#v.ico.offsetWidth ^ this.btnAlaDer?'paddingRight' : 'paddingLeft'; if(!condicion) { this.#cabecera.justificado = ''; this.#v.cabecera.style.justifyContent = ''; this.#v.cabecera.style[padding] = ''; return;} const justificado = this.#cabecera.justificado??this.#css.data.justificado; if(!justificado || justificado == '')return; if(this.#cabecera.justificado == 'center'){ const w = Math.abs(this.#v.divBotones.offsetWidth - this.#v.ico.offsetWidth); this.#v.cabecera.style[padding] = `${w}px`;} this.#v.cabecera.style.justifyContent = justificado;} #cabeceraAplicarEstilo(condicion = true){ if(!condicion){ this.#v.cabecera.style.display = ''; this.#v.cabecera.style.alignItems = ''; return;} this.#v.cabecera.style.display = 'flex'; this.#v.cabecera.style.alignItems = 'center';} // #region posicion #xStr(x){ switch (x){ case 'start': case 'left':return 0; case 'center': return (this.dimensionPadre.w - this.ancho)/2; case 'end': case 'right': return this.dimensionPadre.w - this.ancho; default: return x;}} #yStr(y){ switch (y){ case 'start': case 'top': return 0; case 'center': return (this.dimensionPadre.h - this.alto)/2; case 'end': case 'bottom': return Math.abs(this.dimensionPadre.h - this.alto + .5); default: return y;}} #posX(x){ if(typeof(x) == 'string'){ const cx = this.#xStr(x); if(cx == x) {this.#v.ventana.style.left= x;return;} x = cx;} this.#v.ventana.style.left=`${x}px`;} #posY(y){ if(typeof(y) == 'string'){ const cy = this.#yStr(y); if(cy == y) {this.#v.ventana.style.top= y;return;} y = cy;} this.#v.ventana.style.top=`${y}px`;} /** * * @returns {Punto} */ get posicion(){return new Punto(this.#v?.ventana);} /** * Modifica la posicion actual del objeto **Vanie**. * @param {Punto|{x:number|string y:number|string}} p Objeto `Punto` o `{x,y}` * @property {number|string} p.x - Posición en x: `number` | `'right'` | `'end'` | `'left'` | `'start'` | `'center'`. * @property {number|string} p.y - Posición en y: `number` | `'top'` | `'end'` | `'bottom'` | `'start'` | `'center'`. */ set posicion(p){if(!this.estaConstruido)return; try{this.#posX(p.x); this.#posY(p.y);}catch{return;}} /** * Retorna la posición en el eje **`x`** del objeto **Vanie**. * @returns {number} */ get x(){return this.#v?.ventana.offsetLeft??0;} /** * Modifica la posición en el eje **`x`** * @param {number} num Posición en x: `number` | `'right'` | `'end'` | `'left ` | `'start'` | `'center'`. */ set x(num){if(!this.estaConstruido) return; this.#posX(num);} /** * Retorna la posición en el eje **`y`** del objeto **Vanie**. * @returns {number} */ get y(){return this.#v?.ventana.offsetTop??0;} /** * Modifica la posición en el eje **`y`** * @param {number} num Posición en y: `number` | `'top'` | `'end'` | `'bottom'` | `'start'` | `'center'`. */ set y(num){if(!this.estaConstruido)return; this.#posY(num);} /** * Retorna un objeto `Punto` con la posición global del contenedor padre de la instancia **Vanie**. * @returns {Punto} */ get posicionPadre(){ return new Punto(this.padre,true);} /** * En caso de que el objeto **Vanie** salga de pantalla, rectifica su posición para que este se mantenga al alcance del usuario. */ verificarPosicion(){this.#validarPosicion(this);} #validarPosicion(objeto){ if(!this.estaAbierto || this.estaMaximizado) return; const d = this.dimensionPadre; if(objeto.posicion.y < 0) this.#posY(0); const altura = this.#alturaBarra(); const limiteIzq = (this.#v.divBotones.offsetWidth + (this.btnAlaIzq?-altura:altura))-this.ancho; const limiteDer = d.w - (this.btnAlaDer?altura:this.#v.divBotones.offsetWidth+altura); if(objeto.posicion.x <= limiteIzq) this.#posX(limiteIzq); if(objeto.posicion.x >= limiteDer) this.#posX(limiteDer); if(objeto.posicion.y > d.h - this.#v.barra.offsetHeight){ this.#posY(d.h - this.#v.barra.offsetHeight);}} /** * Modifica la posición de retorno para la acción `minimizar` del objeto **Vanie**. * @param {number} x Posición en x: `number` | `'right'` | `'end'` | `'left ` | `'start'` | `'center'`. * @param {number} y Posición en y: `number` | `'top'` | `'end'` | `'bottom'` | `'start'` | `'center'`. */ cambiarPuntoDeRetorno(x,y){ this.#posicion.retorno.bNuevo(typeof x == 'string'? this.#xStr(x):x,typeof y == 'string'? this.#yStr(y):y);} /** * Retorna la referencia del objeto `Punto` con la posición de retorno de la acción `minimizar` del objeto **Vanie**. * @returns {Punto} */ get pRetorno(){return this.#posicion.retorno;} /** * Remplaza la posición de retorno para la acción `minimizar` del objeto **Vanie**. * @param {Punto|{x:number|string y:number|string}} pos Objeto `Punto` o `{x,y}` * @property {number|string} pos.x - Posición en x: `number` | `'right'` | `'end'` | `'left ` | `'start'` | `'center'`. * @property {number|string} pos.y - Posición en y: `number` | `'top'` | `'end'` | `'bottom'` | `'start'` | `'center'`. */ set pRetorno(pos){if(pos) this.cambiarPuntoDeRetorno(pos.x,pos.y);} /** * Modifica la posición de apertura para la acción `abrir` del objeto **Vanie**. * @param {number} x Posición en x: `number` | `'right'` | `'end'` | `'left ` | `'start'` | `'center'`. * @param {number} y Posición en y: `number` | `'top'` | `'end'` | `'bottom'` | `'start'` | `'center'`. */ cambiarPuntoDeApertura(x,y){ this.#posicion.apertura.bNuevo(x,y); if(!this.estaConstruido) this.#cache.max.posicion = this.#posicion.apertura.copia; } /** * Retorna la referencia del objeto `Punto` con la posición de apertura de la acción `abrir` del objeto **Vanie**. * @returns {Punto} */ get pApertura(){return this.#posicion.apertura;} /** * Remplaza la posición de apertura para la acción `abrir` del objeto **Vanie**. * @param {Punto|{x:number|string y:number|string}} pos Objeto `Punto` o `{x,y}` * @property {number|string} pos.x - Posición en x: `number` | `'right'` | `'end'` | `'left ` | `'start'` | `'center'`. * @property {number|string} pos.y - Posición en y: `number` | `'top'` | `'end'` | `'bottom'` | `'start'` | `'center'`. */ set pApertura(pos){if(pos)this.cambiarPuntoDeApertura(pos.x,pos.y);} /** * Desplaza el objeto **Vanie** en el eje XY. * @param {number} dx Desplazo en el eje X. * @param {number} dy Desplazo en el eje Y. */ desplazar(dx,dy){ this.#desplazo.bNuevo(dx,dy); this.#v.ventana.style.transform = `translate(${dx}px,${dy}px)`;} /** * Retorna el objeto `Desplazo` usado para mover el elemento **Vanie** en los ejes XY. * @returns {Desplazo} */ get desplazo(){return this.#desplazo;} //#region dimension #reCalcularAncho(w){ this.#v.ventana.style.width=typeof (w) == 'number'?`${w}px`:w;} #reCalcularAltura(h){ this.#v.ventana.style.height=typeof (h) == 'number'?`${h}px`:h;} /** * Retorna un objeto `Dimension` que contiene la dimensión del objeto **Vanie**. * @returns {Dimension} */ get dimension(){ return new Dimension(this.#v?.ventana);} /** * Modifica la dimensión actual del objeto **Vanie**. * @param {Dimension|{w:number h:number}} d Objeto `Dimension` o `{w,h}` * @property {number|string} d.w - Ancho: `number` | `string` el porcentaje en relación con el ancho del contenedor padre. * @property {number|string} d.h - Alto: `number` | `string` el porcentaje en relación con el alto del contenedor padre. */ set dimension(d){if(!this.estaConstruido)return; try{this.#reCalcularAncho(d.w); this.#reCalcularAltura(d.h);}catch{return;}} /** * Retorna la altura del objeto **Vanie** * @returns {number} */ get alto(){return this.estaConstruido ? this.#v.ventana.offsetHeight:0;} /** * Modifica la altura actual del objeto **Vanie** * @param {number|string} h Alto: `number` | `string` el porcentaje en relación con el alto del contenedor padre. */ set alto(h){if(!this.estaConstruido) return; this.#reCalcularAltura(h);} /** * Retorna el ancho del objeto **Vanie** * @returns {number} */ get ancho(){return this.estaConstruido ? this.#v.ventana.offsetWidth:0;} /** * Modifica el ancho actual del objeto **Vanie** * @param {number|string} w Ancho: `number` | `string` el porcentaje en relación con el ancho del contenedor padre. */ set ancho(w){if(!this.estaConstruido) return; this.#reCalcularAncho(w);} /** * Retorna la dimensión del objeto contendor de la instancia **Vanie**. * @returns {Dimension} */ get dimensionPadre(){return new Dimension(this.padre);} /** * Modifica la dimensión del objeto **Vanie** con la que aparecerá al momento de ejecutar la acción `abrir`. * @param {number|string} w Ancho: `number` | `string` el porcentaje en relación con el ancho del contenedor padre. * @param {number|string} h Alto: `number` | `string` el porcentaje en relación con el alto del contenedor padre. */ cambiarDimensionInicial(w,h){ this.#dimension.inicial.bNuevo(w,h); if(!this.estaConstruido)this.#cache.max.dimencion = this.#dimension.inicial.copia;} get dApertura(){return this.#dimension.inicial;} /** * Modifica la dimensión del objeto **Vanie** con la que aparecerá al momento de ejecutar la acción `abrir`. * @param {Dimension|{w:number h:number}} dim * @property {number|string} dim.w Ancho: `number` | `string` el porcentaje en relación con el ancho del contenedor padre. * @property {number|string} dim.h Alto: `number` | `string` el porcentaje en relación con el alto del contenedor padre. */ set dApertura(dim){if(dim)this.cambiarDimensionInicial(dim.w,dim.h);} /** * Modifica la dimensión minima que tendra objeto **Vanie**. * @param {number} w Ancho * @param {number} h Alto */ cambiarDimensionMinima(w,h){ this.#dimension.minima.nuevo(w,h); if(this.estaConstruido){ this.#v.ventana.style.minWidth = `${w}px`; this.#v.ventana.style.minHeight = `${h}px`; if(this.ancho > this.#dimension.minima.w) this.ancho = this.#dimension.minima.w; if(this.alto > this.#dimension.minima.h) this.alto = this.#dimension.minima.h;}} /** * Retorna la referencia del objeto `Dimension` con la dimensión mínima de la instancia **Vanie**. * @returns {Dimension} */ get dMinima(){return this.#dimension.minima;} /** * Modifica la dimensión minima que tendra objeto **Vanie**. * @param {Dimension|{w:number h:number}} dim * @property {number} dim.w Ancho * @property {number} dim.h Alto */ set dMinima(dim){if(dim)this.cambiarDimensionMinima(dim.w,dim.h);} /** * Modifica la dimensión del lienzo del objeto **Vanie** sin contar con la altura de la cabecera. * > ⚠ esta función tiene un estatus de **MAXIMA** prioridad con respecto a otras funciones de cambio de dimensión. * @param {number} w Ancho * @param {number} h Alto * @param {boolean} [fijar=false] Por defecto es `false`, pero si se cambia a `true` fijará la dimensión haciéndola inmutable. */ cambiarDimensionDelLienzo(w,h,fijar = false){ const dimension = new Dimension(w,h); if(!dimension.esValido) return; this.#lienzo.dimension = dimension; if(this.estaConstruido){ const alto_barra = this.#alturaBarra(); if(fijar) this.cambiarDimensionFija(dimension.w,dimension.h + alto_barra) else this.dimension = {w:dimension.w,h:dimension.h + alto_barra};} this.#lienzo.fija = fijar;} /** * Retorna un objeto `Dimension` con la dimensión del lienzo. * @returns {Dimension} */ get dLienzo(){ if(this.estaConstruido) return new Dimension(this.#v.lienzo); return this.#lienzo.dimension;} /** * Modifica la dimensión del lienzo que tendra objeto **Vanie**. * @param {Dimension|{w:number h:number}} dim * @property {number} dim.w Ancho * @property {number} dim.h Alto */ set dLienzo(dim){if(dim)this.cambiarDimensionDelLienzo(dim.w,dim.h,this.#lienzo.fija);} /** * Puede usar o no la dimensión de lienzo como una dimensión inmutable del objeto **Vanie**. * > ⚠ esta función tiene un estatus de **MAXIMA** prioridad con respecto a otras funciones de cambio de dimensión. * @param {boolean} condicion Si es **`false`** eliminará la inmutabilidad de la dimensión, pero si es **`true`** fijará la dimensión haciéndola inmutable. */ fijarDimensionDelLienzo(condicion){ if(this.estaConstruido && condicion){ const nuevaDimension = new Dimension(this.#v.lienzo); this.cambiarDimensionDelLienzo(...nuevaDimension.data, true);} else if(this.estaConstruido && !condicion){ this.#lienzo.dimension = undefined; if(this.#lienzo.fija) this.eliminarDimensionFija(); this.#lienzo.fija = false;} else this.#lienzo.fija = !!condicion;} /** * Modifica la dimensión del objeto **Vanie** haciendola inmutable. * > ⚙ esta función tiene un estatus de prioridad alta con respecto a otras funciones de cambio de dimensión * @param {number} w Ancho: `number`. * @param {number} h Alto: `number`. */ cambiarDimensionFija(w,h){ const dimension = new Dimension(w,h) if(this.#lienzo.fija || !dimension.esValido) return; this.#dimension.fija = dimension; if(this.estaConstruido) { this.#reglas('remove'); this.dimension = this.#dimension.fija;}} /** * Retorna un objeto `Dimension` con la dimensión fija de la instancia **Vanie**. * @returns {Dimension} */ get dFija(){return this.dimension = this.#dimension.fija;} /** * Remplaza la dimensión del objeto **Vanie** haciendola inmutable. * > ⚙ esta función tiene un estatus de prioridad alta con respecto a otras funciones de cambio de dimensión * @param {Dimension|{w:number h:number}} dim * @property {number} dim.w Ancho * @property {number} dim.h Alto */ set dFija(dim){if(dim)this.cambiarDimensionFija(dim.w,dim.h);} /** * Elimina la inmutabilidad del objeto **Vanie** */ eliminarDimensionFija(){ this.#dimension.fija = undefined; if(this.estaConstruido) this.#reglas('add');} #recompilarDimension(css_anterior){ if(this.#dimension.fija && !this.#lienzo.fija) return; const altura = this.#alturaBarra(); if(this.#lienzo.dimension){ const [w,h] = this.#lienzo.dimension.data; if(this.#lienzo.fija){ if(!this.#dimension.fija) this.#dimension.fija = new Dimension; this.#dimension.fija.bNuevo(w,h + altura); this.dimension = this.#dimension.fija ? this.#dimension.fija :this.#dimension.inicial;} else this.#dimension.inicial.bNuevo(w,h + altura);} if(css_anterior && this.estaConstruido && this.#v.lienzo && !(this.estaDividido||this.estaMaximizado)){ const nuevaDimension = new Dimension(this.#v.lienzo); nuevaDimension.h += altura; this.dimension = nuevaDimension;} else if(css_anterior && this.estaConstruido){ const altura_anterior = css_anterior.CONFIGURACION.data.alturaBarra; if(this.estaMaximizado){ const maxNueva = this.#cache.max.dimension; maxNueva.h = (maxNueva.h - altura_anterior) + altura; this.#cache.max.dimension = maxNueva;} if(this.estaDividido){ const mitadNueva = this.#cache.media.dimension; mitadNueva.h = (mitadNueva.h - altura_anterior) + altura; this.#cache.media.dimension = mitadNueva;}}} #alturaBarra(){ let altura = this.#css.CONFIGURACION.data.alturaBarra; if(typeof altura != 'number') altura = this.#v?.barra.offsetHeight; return altura;} //#region colision #ejecutarFunciones(llave,condicion){this.#funciones[llave]?.forEach(funcion=>{funcion(condicion)});} #eventoColision(x,y,xm,ym,limite){ this.#map.data = VanieAdmin.intColision(xm>=limite.der,x<=limite.izq,y<=limite.sup,ym>=limite.inf); const exclusion = limite.ref | this.#map.tmp; const data = limite.ref | this.#map.data; if( this.#map.data != this.#map.tmp){ this.#map.archivado = true; VanieAdmin.registrarColision(this.#llave,this.#map.data);} else if(!this.#map.data && this.#map.archivado){ this.#map.archivado = false; VanieAdmin.eliminarRegistroColision(this.#llave);} if(data != exclusion){ VanieAdmin.validarColision(!!(data&1),!!(data&2),!!(data&4),!!(data&8));} this.#map.tmp = this.#map.data;} #verificarColision(x,y,xm,ym){ const limite = VanieAdmin.limites(this.dimensionPadre,this.#llave); if(!limite) return; this.#eventoColision(x,y,xm,ym,limite);} #desaparecerColision(condicion = false){ if(!this.#llave || !VanieAdmin.objLimites) return; if(condicion){ if(!this.#map.archivado) return; const colision = VanieAdmin.colisiones(this.#llave); if((colision | this.#map.data) != colision){ VanieAdmin.validarColision(!!(colision&1),!!(colision&2),!!(colision&4),!!(colision&8));} VanieAdmin.eliminarRegistroColision(this.#llave); this.#map.archivado = false; this.#map.tmp = this.#map.data = undefined;} else{ this.#verificarColision(this.x,this.y,this.ancho + this.x,this.alto+this.y);}} //#region minimizar /** * Ejecuta la acción de minimizado si se encuentra visible y en caso de que este esté minimizado lo des-minimiza. */ minimizar(){ if(!this.estaAbierto) return; this.#v.divBotones.classList.remove(this.#css.class('bloqueado')); this.#animacionActiva.minimizar = true; this.#estado.min = !this.#estado.min; if(this.#estado.min){ const [w,h] = this.dimension.data; const [x,y] = this.posicion.data; if(this.#llave) { VanieAdmin.oculta(this); this.#v.ventana.style.zIndex = VanieAdmin.ventanasVisibles +1;} const matrix = escala(.1,.1).desplazar(this.pRetorno.x - x - w/2,this.pRetorno.y-y-h/2); setTimeout(() => { this.#v.ventana.classList.add(this.#css.class('animacion')); this.#v.ventana.style.transform = matrix.str; },80);} else { if(this.#llave){ VanieAdmin.visible(this); VanieAdmin.subirPosicion(this.#llave);} this.#v.ventana.classList.add(this.#css.class('animacion')); this.#v.ventana.classList.remove(this.#css.class('desaparecer')); this.#v.ventana.style.transform = 'matrix(1,0,0,1,0,0)';}} //#region maximizar /** * Si el objeto **Vanie** está visible en pantalla, lo maximiza al 100%. Si este ya se encuentra maximizado, lo devuelve a su tamaño original. */ maximizar(){ if(!this.esVisible) return; this.#expandir('max'); if(!this.estaMaximizado && this.#estado.mediaPos && this.#cache.origen.posicion){ this.#ejecutarFunciones('media',{lado: this.#estado.mediaPos, estado: this.#estado.media}); this.#posX({der:'50%',izq:0}[this.#estado.mediaPos]); this.#posY(0);} else if(this.#estado.mediaPos && this.#cache.origen.posicion){ this.#estado.media = false; this.#ejecutarFunciones('media',{lado: this.#estado.mediaPos, estado: this.#estado.media});}} #reglas(llave){ if(!this.#opciones.redimensionar) return; const accion ={'add':'remove','remove':'add'} for(const reglas in this.#r) this.#r[reglas].div.classList[accion[llave]](...this.#css.class('bloqueado','none'));} #expandir(llave,e = undefined,data=undefined){ if(this.#dimension.fija || this.#estado.cerrar) return; if(llave == 'media' && this.#estado.mediaPos == 'sup'){ this.#estado.mediaPos = undefined; llave = 'max';} if(data === undefined) this.#v.ventana.classList.add(this.#css.class('animacion')); this.#estado[llave] = !this.#estado[llave]; const accion = llave == 'max'? 'full':'media'; this.#ejecutarFunciones(llave != 'media'?`${llave}imizar`:llave ,llave != 'media' ? this.#estado[llave]:{lado: this.#estado.mediaPos,estado: this.#estado.media}); if(this.#estado[llave]){ this.#cache[llave].dimension = this.dimension; this.#cache[llave].posicion = this.posicion; this.posicion = {x:0,y:0}; const [w,h] = this.dimensionPadre.data; VanieAdmin.subirPosicion(this); if(llave == 'media' && this.#estado.mediaPos == 'der') { this.#verificarColision(w/2,0,w,h); this.#posX('50%');} else if(llave == 'media' && this.#estado.mediaPos == 'izq'){ this.#verificarColision(0,0,w/2,h);} this.#redondearEsquinas('remove'); if(llave == 'max'){ if(this.#estado.media) this.#v.ventana.classList.remove(this.#css.class('media')); this.#reglas('remove'); this.#verificarColision(0,0,w,h);} this.#v.ventana.classList.add(this.#css.class(accion));} else{ const d = this.#cache.origen.dimension??this.#cache[llave].dimension; const p = this.#cache.origen.posicion??this.#cache[llave].posicion; this.dimension = d; this.posicion = p; this.#validarPosicion(this.#cache.origen.posicion ? this.#cache.origen : this.#cache[llave]); if(llave == 'max'){ if(this.#estado.media) this.#v.ventana.classList.add(this.#css.class('media')); this.#reglas('add')} else{ this.#estado.mediaPos = undefined;} this.#redondearEsquinas(['add','remove'][+(this.#estado.media | this.#estado.max)]); this.#v.ventana.classList.remove(this.#css.class(accion)); //#region reposicionador if(e && data?.sinAnimacion){ const dimension = this.#cache.origen.dimension ?? this.#cache[llave].dimension const anchoP = data.dim_padre.w; const media = dimension.w/2; let x = e.clientX - media; if((e.clientX + media) > anchoP){x = anchoP - dimension.w;} else if(x < 0) x = 0; this.#posX(x); this.#posY(0); data.origen = this.posicion;} else{ this.#verificarColision(p.x,p.y,d.w + p.x,d.h+p.y); } if(!this.#estado.mediaPos && !this.#estado.max){ this.#cache.origen.dimension = this.#cache.origen.posicion = undefined;}}} //#region cerrar /** * Al accionar, `cerrar` ejecuta una de las siguientes dos acciones: * + `Defecto`: Elimina completamente su contenido y se desconecta del contenedor padre. * + `eliminarAlCerrar(false)`: Solo oculta el contenido. */ cerrar(){ if(!this.estaAbierto) return; if(this.esVisible && this.#llave) VanieAdmin.oculta(this); if(!this.esVisible){ this.#estado.cerrar = true; this.#cerrarVentana(); return;} this.#estado.cerrar = true; this.#animacionActiva.cerrar = true; this.#v.ventana.classList.add(...this.#css.class('animacion','transparente','bloqueado')); this.#v.ventana.style.transform = escala(.95,.95).str;} #cerrarVentana(){ this.#desaparecerColision(true); if(this.#opciones.eliminar_alcerrar){this.eliminar();} else{ if(this.estaMinimizado){this.#estado.min = false;} this.#v.ventana.classList.add(this.#css.class('none')); this.#v.ventana.classList.remove(...this.#css.class('transparente','bloqueado'));} this.#ejecutarFunciones('cerrar',this.#estado.cerrar);} // #region media /** * Coloca el objeto **Vanie** en la posición asignada con un tamaño del 50% de la pantalla, Solo si se encuentra visible. * @param {string} posicion Parámetros aceptados: `'der'` = derecha, `'izq'`= izquierda. */ media(posicion){ const pos = {der:'50%',izq:0}; let arranque = false; if(typeof posicion != 'string' || !this.esVisible || pos[posicion] == undefined || this.#dimension.fija) return; if(this.#cache.origen.dimension == undefined){ arranque = true; const asignar = (estado)=>{ this.#cache.origen.dimension = estado ? this.#cache[estado].dimension.copia:this.dimension; this.#cache.origen.posicion = estado ? this.#cache[estado].posicion.copia:this.posicion;} if(this.estaMaximizado){ if(this.#estado.mediaPos){ asignar('media');} else asignar('max');} else if(this.estaDividido){asignar('media');} else asignar();} const lado = this.#estado.mediaPos; const esDiferente = this.#estado.mediaPos != posicion; this.#estado.mediaPos = posicion; if(esDiferente && this.#estado.media) { if(this.estaMaximizado){this.#expandir('max');} this.#v.ventana.classList.add(this.#css.class('animacion')); this.#posX(pos[this.#estado.mediaPos]); this.#posY(0);} else { if(this.estaMaximizado && this.#estado.media){ this.#expandir('max'); if(arranque) this.#estado.media = false; else this.#estado.media = lado == posicion; } this.#expandir('media');} this.#ejecutarFunciones('media',{lado: this.#estado.mediaPos, estado: this.#estado.media});} // #region abrir /** * La acción `abrir` ejecuta las siguientes acciones: * + Si la instancia **Vanie** no se ha construido, esta la construye y la muestra en pantalla. * + Si se ejecutó la función `cerrar` la construirá y la mostrará nuevamente. * + Si esta `minimizada` la des-minimizara. * + Si ya se encuentra visible en pantalla, esta subirá la posición de la instancia de **Vanie** con respecto a los otros objetos **Vanie** que se encuentren en pantalla. */ abrir(){ if(this.#estado.cerrar && !this.#opciones.eliminar_alcerrar && this.estaConstruido){ this.#ejecutarFunciones('abrir'); this.#v.ventana.classList.remove(this.#css.class('none')); this.#desaparecerColision(false);} this.#estado.cerrar = false; if(this.#estado.min){ this.minimizar(); return;} if(!this.estaConstruido){ this.#reConstruir(); this.#accionadores(); this.#animacionActiva.abrir = this.#animar_apertura; if(this.#dimension.fija) this.#reglas('remove'); if(this.#llave){ VanieAdmin.visible(this); VanieAdmin.subirPosicion(this.#llave);} if(this.#padre && this.#animar_apertura){ setTimeout(()=>{ this.#v.ventana.classList.remove(...this.#css.class('transparente','bloqueado')); this.#v.ventana.style.transform = 'scale(1,1)'; },100);} this.#transitionend(); this.#desaparecerColision(false); this.#ejecutarFunciones('abrir'); this.#validarPosicion(this);} else this.subir();} // #region transitionend #transitionend(){ this.#v.ventana.addEventListener('transitionend',()=>{ if(!this.estaConstruido) return; this.#v.ventana.classList.remove(this.#css.class('animacion')); if(this.#animacionActiva.minimizar){ this.#ejecutarFunciones('minimizar', this.#estado.min); this.#animacionActiva.minimizar = false; this.#v.ventana.classList.remove(this.#css.class('transparente')); if(this.#estado.min){ this.#v.ventana.style.zIndex = 0; this.#v.ventana.classList.add(this.#css.class('desaparecer'));} else this.#v.ventana.style.transform = ''; this.#desaparecerColision(this.#estado.min);} else if(this.#animacionActiva.abrir || this.#animacionActiva.cerrar){ this.#v.ventana.style.transform = ''; if(this.#animacionActiva.cerrar){ this.#animacionActiva.cerrar=false; this.#cerrarVentana();} else this.#animacionActiva.abrir=false;}});} //#region reconstruir #reConstruir(){ if(!this.#css) throw('no se ha asignado ningun estilo'); this.#v = {}; this.#estilizar(); for(const div in this.#classList){this.#v[div].classList.add(...this.#classList[div]);} for(const div in this.#id){this.#v[div].setAttribute('id',this.#id[div]);} this.#v.ventana.style.zIndex = '0'; if(this.#animar_apertura){ this.#v.ventana.style.transform = 'scale(.95,.95)'; this.#v.ventana.classList.add(...this.#css.class('animacion','transparente'));} if(this.#dimension.minima.esValido){ this.#v.ventana.style.minWidth = `${this.#dimension.minima.w}px`; this.#v.ventana.style.minHeight = `${this.#dimension.minima.h}px`;} const reglas = ['sup','inf','der','izq','esqsd','esqsi','esqid','esqii']; const data = [0x05,0x04,0x08,0x0a,0x0d,0x0f,0x0c,0x0e]; this.#r = {} for(let i = 0; i < reglas.length; i++){ this.#r[reglas[i]] = {div:document.createElement('div'),val:data[i]}; this.#v.ventana.appendChild(this.#r[reglas[i]].div);} this.#v.ventana.appendChild(this.#v.barra); this.#v.ventana.appendChild(this.#v.lienzo); this.#v.ventana.classList.add(this.#css.idGlobal,this.#css.class('sombra')); this.#recompilarDimension(); this.dimension = this.#dimension.fija ? this.#dimension.fija :this.#dimension.inicial; this.#asignarContenido(this.#v.lienzo,this.#lienzo); const padre = this.#padre; this.#padre = undefined; this.asignarPadre(padre);} #reacomodar(){ if (!this.#v.barra.childNodes.length)this.#v.barra.appendChild(this.#v.cabecera); else{ this.#v.barra.removeChild(this.#v.ico); this.#v.barra.removeChild(this.#v.divBotones);} if(this.btnAlaIzq){ this.#v.barra.insertBefore(this.#v.divBotones, this.#v.cabecera); this.#v.barra.appendChild(this.#v.ico);} else{ this.#v.barra.insertBefore(this.#v.ico, this.#v.cabecera); this.#v.barra.appendChild(this.#v.divBotones);}} // #region estilos /** * retorna el nombre del estilo que se encuentra usado la ventana. * @returns {string} */ get estilo(){return this.#css?.CONFIGURACION.data.nombre??'';} /** * Cambia el estilo del objeto **Vanie** y lo fija. * + **String** El nombre del estilo almacenado en el sistema global. * + **object** El objeto con las características de estilo para ser asignadas. * + **CompiladorCssVanie** El complilador de estilos * @param {string|{}|CompiladorCssVanie} nuevoEstilo * + **String** El nombre de los estilos almacenados o predeterminados: * > [ `windows-claro` , `windows-oscuro` , `mac-claro` , `mac-oscuro` , `linux-claro` , `linux-oscuro` ] * + **object** El objeto con las características de estilo para ser asignadas. * + **CompiladorCssVanie** El complilador de estilos de algun objeto Vanie: *`instanciaVanie.compiladorCss`* */ cambiarEstiloBase(nuevoEstilo){ this.#estilo_pre = false; if(!this.#css){ this.#css = globalVanie.agregarEstilo(nuevoEstilo); if(!this.#css) this.#css = globalVanie.obtenerEstilo(nuevoEstilo);} else{ this.cambiarEstilo(nuevoEstilo);} this.#estilo_pre = !!this.#css;} /** * Cambia el estilo del objeto **Vanie** * + **String** El nombre del estilo almacenado en el sistema global. * + **object** El objeto con las características de estilo para ser asignadas. * + **CompiladorCssVanie** El complilador de estilos * @param {string|{}|CompiladorCssVanie} nuevoEstilo * + **String** El nombre de los estilos almacenados o predeterminados: * > [ `windows-claro` , `windows-oscuro` , `mac-claro` , `mac-oscuro` , `linux-claro` , `linux-oscuro` ] * + **object** El objeto con las características de estilo para ser asignadas. * + **CompiladorCssVanie** El complilador de estilos de algun objeto Vanie: *`instanciaVanie.compiladorCss`* */ cambiarEstilo(nuevoEstilo){ if(this.#estilo_pre) return; const viejo_css = this.#css; if(nuevoEstilo instanceof CompiladorCssVanie && nuevoEstilo.esValido) this.#css = nuevoEstilo; else if(typeof nuevoEstilo == 'string' && nuevoEstilo != this.#css?.CONFIGURACION.data.nombre){ globalVanie.agregarEstilo(nuevoEstilo); this.#css = globalVanie.obtenerEstilo(nuevoEstilo); } else if(typeof nuevoEstilo == 'object'){ globalVanie.agregarEstilo(nuevoEstilo); this.#css = globalVanie.obtenerEstilo(nuevoEstilo); } else return; if(!this.#css) { this.#css = viejo_css; throw('error al introducir el nuevo estilo');} if(this.estaConstruido) { this.#estilizar(viejo_css); this.#validarPosicion(this);}} #redondearEsquinas(accion){ if(this.#css.data.radioSup) this.#v.ventana.classList[accion](this.#css.class('radioSup')); if(this.#css.data.radio) this.#v.ventana.classList[accion](this.#css.class('radio'));} #estilizar(viejo_css = undefined){ if(!(this.#css instanceof CompiladorCssVanie)) return; const elementos = this.#css.LISTA_DIV; for(let i = 0; i < elementos.length; i++){ if(!viejo_css) {this.#v[elementos[i]] = document.createElement('div');} else if(i + 1 < elementos.length && viejo_css.div?.[elementos[i]]?.class) { this.#v[elementos[i]].classList.remove(viejo_css.class(elementos[i]));} if(i + 1 < elementos.length && this.#css.div?.[elementos[i]]?.class) { this.#v[elementos[i]].classList.add(this.#css.class(elementos[i]));}} if(viejo_css){ this.#recompilarDimension(viejo_css); while(this.#v.divBotones.firstChild) this.#v.divBotones.removeChild(this.#v.divBotones.firstChild); this.#v.divBotones.classList.remove(viejo_css.classBtnVisibles(3),viejo_css.class('controles')); if(viejo_css.CONFIGURACION.data.radioSup && this.#v.ventana.classList.contains(viejo_css.class('radioSup'))) this.#v.ventana.classList.remove(viejo_css.class('radioSup')); if(viejo_css.CONFIGURACION.data.radio && this.#v.ventana.classList.contains(viejo_css.class('radio'))) this.#v.ventana.classList.remove(viejo_css.class('radio'));} if(!(this.estaMaximizado || this.estaDividido))this.#redondearEsquinas('add'); this.#v.divBotones.classList.add(this.#css.classBtnVisibles(3),this.#css.class('controles')); this.#v.barra.style.height = `${this.#css.CONFIGURACION.data.alturaBarra}px`; this.#css.CONFIGURACION.botones.data.distribucion.forEach((btn)=>{ this.#v.divBotones.appendChild(this.#v[btn]); if(viejo_css) this.#v[btn].classList.remove(...viejo_css.class(btn,'botones')); this.#v[btn].classList.add(...this.#css.class(btn,'botones')); if(viejo_css && !viejo_css.div[btn]['contenido']) this.#v[btn].innerHTML = ''; if(!this.#css.div[btn]['contenido'] || typeof(this.#css.div[btn]['contenido']) != 'string') return; const contenido = this.#css.div[btn]['contenido'].trim(); if(/^-/.test(contenido)){ const cli = contenido.split(' '); if((cli[0] == '-p' || cli[0] == '--p') && cli.length <= 4){ let color = '#000'; let w = 24; let h = 24; const version2 = cli[0] == '--p'; for(let i = 1; i < cli.length; i++){ if(i == 1 && /^#/.test(cli[i])) color = cli[1]; if(i == 2 && parseInt(cli[i])) w = h = parseInt(cli[i]); if(i == 3 && parseInt(cli[i])) h = parseInt(cli[i]);} this.#v[btn].innerHTML = this.#css.ICO(btn,w,h,color,version2); return;}} this.#v[btn].innerHTML = this.#css.div[btn]['contenido']}); this.#reacomodar(); const modificarCabecera = this.#cabecera.str || this.#cabecera.titulo.span || this.#cabecera.nodo || this.#cabecera.nodos.length; const nodos = this.#v.cabecera.childNodes.length; if(nodos || modificarCabecera){ this.#v.cabecera.style.padding = ''; if(this.#cabecera.str){this.#v.cabecera.innerHTML = this.#cabecera.str;} else { if(this.#cabecera.titulo.span)this.#v.cabecera.appendChild(this.#cabecera.titulo.span); this.#asignarContenido(this.#v.cabecera,this.#cabecera);}} this.#asignarContenido(this.#v.ico,this.#cabecera.ico); this.#cabeceraAplicarMod(modificarCabecera);} // #region accionadores #accionadores(){ if(!this.estaConstruido) return; const pul = {}; const limite = {} const f = (e)=>{ pul['sinAnimacion'] = true; pul["padre"] = this.posicionPadre;pul['valido']={x:true, y:true}; pul['x']=e.clientX;pul["origen"]=this.posicion; pul['y']=e.clientY;pul["dimension"]=this.dimension; pul['dim_padre'] = this.dimensionPadre; pul['limite'] = VanieAdmin.limites(this.dimensionPadre,this.#llave); VanieAdmin.subirPosicion(this.#llave); this.bloquearIframe(true);} const desplazo = new Desplazo; const pLocal = new Punto; const pGlobal = new Punto; let orden = 0; this.#registro.fnMov = (e)=>{ if(!orden) return; let y = 0x01 & orden ? pul.y - e.clientY : e.clientY - pul.y; let x = 0x02 & orden ? pul.x - e.clientX : e.clientX - pul.x;