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
404 lines (377 loc) • 19.9 kB
JavaScript
import Vanie from "./Vanie.js";
import CompiladorCssVanie from "./CompiladorCssVanie.js";
//#region AdministradorVanie
class AdministradorVanie{
nodos = new Map;
trueColision = new Map;
#ventanas_visibles = 0;
#registros = 0;
#mousemove;
#mouseup;
ventanaArrastrada;
#limites = undefined;
#eventos ={registro:[],vista:[],colision:[],pulsar:[],arrastre:[]};
constructor(){
document.addEventListener('mousemove',e=>this.#mousemove?.(e));
document.addEventListener('mouseup',e=>this.#mouseup?.(e));}
set mousemove(fn){if(typeof fn == 'function') this.#mousemove = fn;}
set mouseup(fn){if(typeof fn == 'function') this.#mouseup = fn;}
get ventanasVisibles(){ return this.#ventanas_visibles;}
get estilosPredeterminados(){return CompiladorCssVanie.predeterminados;}
#iter(e,args){
const fn = this.#eventos[e];
for(let i = 0; i < fn.length; ++i) fn[i](args);}
oculta(ventana){--this.#ventanas_visibles;this.#iter('vista',{vanie:ventana,visibles:this.#ventanas_visibles,seOculto:true,seMostro:false});}
visible(ventana){++this.#ventanas_visibles;this.#iter('vista',{vanie:ventana,visibles:this.#ventanas_visibles,seOculto:false,seMostro:true});}
validarColision(der=false,izq=false,sup=false,inf=false){
this.#iter('colision',{der:der,izq:izq,sup:sup,inf:inf});}
arrastre(condicion,ventana,p,pg,d){
if(!condicion){
this.ventanaArrastrada = undefined;
return;}
this.ventanaArrastrada = ventana;
this.#iter('arrastre',{vanie:ventana,local:p,global:pg,desplazo:d});}
estaEnElLimite(){return !!this.trueColision.size;}
registrarColision(llave,obj){this.trueColision.set(llave,obj); }
eliminarRegistroColision(llave){this.trueColision.delete(llave);}
eliminarLimites(){this.#limites = undefined;}
subirPosicion(llave){
const vanieNodo = this.nodos.get(llave);
if(!vanieNodo || !vanieNodo.estaAbierto) return;
const zindex = vanieNodo.ventana.style.zIndex;
if(zindex >= this.#ventanas_visibles) return;
for(const nodo of this.nodos.values()){
nodo.bloquearIframe(true);
if(nodo.ventana?.style.zIndex > zindex){
nodo.ventana.style.zIndex = `${nodo.ventana.style.zIndex - 1}`;}}
vanieNodo.bloquearIframe(false);
vanieNodo.ventana.style.zIndex = `${this.#ventanas_visibles}`;}
registrarNuevaVentana(vanie){
if(!(vanie instanceof Vanie)) return undefined;
const llave = 'Vanie'+ ++this.#registros;
this.nodos.set(llave,vanie);
this.subirPosicion(llave);
return llave;}
pulsar(llave,vanie){
if(!llave) return;
this.#iter('pulsar',vanie);
}
activarRegistro(llave,vanie){
if(!llave) return;
this.#iter('registro',vanie);}
removerVentana(llave){
const vanieNodo = this.nodos.get(llave);
if(!vanieNodo) return;
if(vanieNodo.esVisible) --this.#ventanas_visibles;
this.nodos.delete(llave);}
asignarLimites(der,izq,sup,inf,){
this.#limites = {der:der,izq:izq,sup:sup,inf:inf}}
limites(dimension,llave){
if(!this.#limites ||!llave) return undefined;
const f = (v,i)=>{
if(typeof v == 'string')
switch(v){
case 'bottom': return dimension.h;
case 'top':return 0;
case 'right': return 0;
case 'left':return dimension.w;
case 'start': return 0;
case 'end': return dimension.data[i];
default:
let res = 0;
let str = v.split('%');
if(str.length == 2) res = parseFloat(str[1]);
const num = parseFloat(str[0]);
if(isNaN(num)||isNaN(res)) return undefined;
return dimension.multiplica(num/100).data[i] + res;}
else if(typeof v != 'number') return undefined;
return v}
const der = f(this.#limites.der,0);
const izq = f(this.#limites.izq,0);
const sup = f(this.#limites.sup,1);
const inf = f(this.#limites.inf,1);
if((der==undefined&&izq==undefined&&sup==undefined&&inf==undefined)||(sup??'')>=inf||(izq??'')>=der)return undefined;
return{der:der,izq:izq,sup:sup,inf:inf,ref:this.colisiones(llave)}}
get objLimites(){return this.#limites;}
intColision(der,izq,sup,inf){ return der|izq<<1|sup<<2|inf<<3;}
colisiones(llave){
let colision = 0;
for(const [clave,valor] of this.trueColision){if(clave != llave) colision |= valor;}
return colision;}
eventos(evento,fn){
const listaDeEventos = this.#eventos[evento]
if(listaDeEventos && !listaDeEventos.includes(fn)) listaDeEventos.push(fn);}
eliminarEventos(evento, funcion){
if(typeof(evento != 'string' || this.#eventos[evento] == undefined) || !(typeof funcion == 'function')) return;
const i = this.#eventos.indexOf(funcion);
if(i !== -1) this.#eventos.splice(indiceAEliminar, 1);}}
const VanieAdmin = new AdministradorVanie;
//#region GestorVanie
class GestorVanie{
#Epersonalizados = [];
#Epredefinidos = [];
#padre;
#sistemaBase;
#idglobal = 'VANIE_GLOBAL';
/**
* @readonly
* Retorna el numero de ventanas visibles en pantalla
* @returns {number}
*/
get ventanasVisibles(){ return VanieAdmin.ventanasVisibles;}
/**
* Retorna el elemento `HTMLElement` contenedor o `undefined` si no existe.
* @returns {HTMLElement|undefined}
*/
get padre(){return this.#padre;}
/**
* Retorna el nombre de la id global.
* @returns {string}
*/
get idGlobal(){return this.#idglobal;}
/**
* Retorna los nombre de los estilos predeterminados: [ `'windows-claro'` , `'windows-oscuro'` , `'linux-claro'` , `'linux-oscuro'` , `'mac-claro'` , `'mac-oscuro'` ]
* @returns {string[]}
*/
get estilosPredeterminados(){return CompiladorCssVanie.predeterminados;}
/**
* Conecta el elemento del DOM para ser usado de forma global por todos los objetos **Vanie**.
* @param {HTMLElement} padre Elemento del DOM que sera el contenedor principal.
* @returns {this}
*/
conectarseA(padre){
if(padre instanceof HTMLElement) this.#padre = padre;
return this;}
/**
* Cambia y reescribe la clase global que controla parte de los estilos y comportamientos de los objetos **Vanie**.
* @param {string} nuevoNombre Nuevo nombre de la clase global.
* > ⚠ Solo debería cambiar el nombre de la clase global si interfiere con su diseño y al principio de su código, ya que si está en ejecución podría causar problemas de compatibilidad.
* @returns {this}
*/
renombrarIdGlobal(nuevoNombre){
if(typeof nuevoNombre == 'string' && nuevoNombre != this.#idglobal){
if(this.#Epersonalizados.length) this.#Epersonalizados.forEach(css=>{css.modificarGlobal(nuevoNombre)});
if(this.#Epredefinidos.length) this.#Epredefinidos.forEach(css=>{css.modificarGlobal(nuevoNombre)});
if(this.#sistemaBase) this.#sistemaBase.modificarGlobal(nuevoNombre);
this.#idglobal = nuevoNombre;}
return this;}
/**
* Obtiene el estilo almacenado.
* + **String** El nombre del estilo almacenado en el sistema global.
* @param {string|undefined} sistema Nombre del estilo.
* + **String** El nombre del estilo almacenado en el sistema global.
* + **undefined** Retorna el estilo principal que se encuentra almacenado.
* @returns {CompiladorCssVanie}
*/
obtenerEstilo(sistema = undefined){
if(!sistema) return this.#sistemaBase;
const personalizado = this.#__buscarEstiloPersonalizado(sistema);
if(personalizado) return personalizado;
return this.#__buscarEstiloPredeterminado(sistema);}
/**
* Establece un estilo principal para ser usado de forma global.
* + **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|{}}} sistema
* + **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`*
* @returns {this}
*/
establecerBase(estilo){
this.agregarEstilo(estilo);
let cf = this.obtenerEstilo(estilo);
if(cf && !this.esElEstiloBase(cf)){
this.#sistemaBase = cf;
VanieAdmin.nodos.forEach(ventana=>{ventana.cambiarEstilo(cf);});}
return this;}
/**
* Retorna el nombre del estilo principal o `undefined` si no está establecido.
* @returns {string|undefined}
*/
get estiloBase(){return this.#sistemaBase?.data.nombre;}
/**
* Retorna `true` si existe un estilo principal o `false` si no existe.
* @returns {boolean}
*/
get hayEstiloBase(){return !!this.#sistemaBase;}
/**
* Retorna `true` si existen estilos almacenados para ser usados de forma global o `false` si no es así.
* @returns {boolean}
*/
get hayEstilos(){return this.#Epredefinidos.length && this.#Epersonalizados.length && this.#sistemaBase;}
/**
* Retorna `true` si existe alguna ventana siendo arrastrada por la pantalla o `false` si no es así.
* @returns {boolean}
*/
get hayArrastre(){return !!VanieAdmin.ventanaArrastrada;}
/**
* Retorna el objeto `Vanie` de la ventana que esta siendo arrastrada por la pantalla o `undefined` si no es así.
* @returns {Vanie|undefined}
*/
get ventanaArrastrada(){return VanieAdmin.ventanaArrastrada;}
/**
* Retorna el numero de instancias Vanie registradas.
* @returns {number}
*/
get registros(){return VanieAdmin.nodos.size;}
/**
* Establece los límites que determinarán las colisiones de los objetos **Vanie** en pantalla.
* @param {{der:undefined|number|string izq:undefined|number|string sup:undefined|number|string inf:undefined|number|string}} o Objeto con las siguientes características:
* @example
* {
* der : undefined | number | string = 'right' 'end' '0%...100%' '100%-1' '100%+1',
* izq : undefined | number | string = 'left' 'start' '0%...100%' '100%-1' '100%+1',
* sup : undefined | number | string = 'top' 'start' '0%...100%' '100%-1' '100%+1',
* inf : undefined | number | string = 'bottom' 'end' '0%...100%' '100%-1' '100%+1',
* }
*/
set limites(o){
if(o == undefined || o == 0 || o == ''|| (o.der == undefined&&o.izq == undefined&&o.sup == undefined&&o.inf== undefined)){
VanieAdmin.eliminarLimites();
return;}
try{
this.#establecerLimites(o.der,o.izq,o.sup,o.inf);}catch(err){
console.error('--OBJETO INVALIDO--');
console.info(`Caracteristicas que debe contener un objeto valido:
{
der : undefined | number | string = 'right' | 'end' | '0%' '100%' '100%-1' '100%+1',
izq : undefined | number | string = 'left' | 'start' | '0%' '100%' '100%-1' '100%+1',
sup : undefined | number | string = 'top' | 'start' | '0%' '100%' '100%-1' '100%+1',
inf : undefined | number | string = 'bottom' | 'end' | '0%' '100%' '100%-1' '100%+1',)
} `);
return err;}}
#establecerLimites(der,izq,sup,inf){
const s = typeof sup == 'string'?sup.trim():sup;
const i = typeof inf == 'string'?inf.trim():inf;
const d = typeof der == 'string'?der.trim():der;
const iz = typeof izq == 'string'?izq.trim():izq;
VanieAdmin.asignarLimites(d,iz,s,i);}
/**
* Si se ha hecho una asignación correcta, retornará los límites que determinarán las colisiones de los objetos **Vanie** en pantalla, de lo contrario devolverá `undefined`.
* @returns {{der:undefined|number|string izq:undefined|number|string sup:undefined|number|string inf:undefined|number|string} | undefined}
*/
get limites(){return VanieAdmin.objLimites;}
/**
* Toma un CompiladorCssVanie para comprobar si es el estilo principal.
* @param {CompiladorCssVanie} estilo
* @returns {boolean}
*/
esElEstiloBase(estilo){
if(!(estilo instanceof CompiladorCssVanie) || !this.#sistemaBase)return false;
return estilo.data.nombre == this.#sistemaBase.data.nombre;}
/**
* Agrega un estilo a las listas globales para ser usado cuando se use su nombre.
* + **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} 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`*
*/
agregarEstilo(estilo){
if(!estilo || (typeof estilo != 'string' && typeof estilo != 'object')) return undefined;
let cf = undefined;
let base = !this.#Epredefinidos.length && !this.#Epersonalizados.length;
if(typeof(estilo) == 'string'){
if(!CompiladorCssVanie.predeterminados.includes(estilo)) return undefined;
if(!this.#__buscarEstiloPredeterminado(estilo)){
cf = new CompiladorCssVanie(estilo,this.#idglobal)
this.#Epredefinidos.push(cf);}}
else{
try{
if(!this.#__buscarEstiloPersonalizado(estilo.data.nombre)){
cf = new CompiladorCssVanie(estilo,this.#idglobal);
if(!cf.esValido) return undefined;
this.#Epersonalizados.push(cf);}
}catch(error){
console.error('se a producido un error al asignar el estilo'); return error;}}
if(base && cf) this.#sistemaBase = cf;
return cf;}
/**
* Ejecuta una función proporcionada una vez por cada ventana/llave en el sistema global, en el orden en el que se registro.
* @param {function (ventana llave Map)} funcion
*/
ventanasForEach(funcion){VanieAdmin.nodos.forEach(funcion);}
/**
* Otorga el nombre o la lista de nombres de la clase global a partir de un alias para su uso en los elementos del DOM.
* @param {...string} alias El alias de la clase global que ejecutara las siguientes órdenes en CSS:
* + `titulo` : CSS personalizable, si no ha cambiado la id global podrá usarla como: **'VANIE_GLOBAL--titulo'** caso contrario, asigne el nombre que uso como id global `${idglobal}--titulo`.
* + `animacion` : transition : **all .3s ease**;
* + `none` : display : **none**;
* + `transparente` : opacity : **0**;
* + `desaparecer` : visibility : **hidden**;
* + `bloqueado` : user-select : **none**; -webkit-user-select : **none**; -moz-user-select :**none**; pointer-events :**none**;
* + `full` : width : **100%** !important; height : **100%** !important;
* + `media` : width : **50%** !important; height : **100%** !important;
* + `radio` : border-radius : **8px**;
* + `radioSup` : border-radius : **8px 8px 0 0**;
* @returns {string|string[]}
*/
globalClass(...alias){return this.#sistemaBase?.globalClass(...alias);}
/**
* Detecta los eventos relacionados al objeto **Vanie**.
* @param {string} evento el Nombre del evento:
* + `'registro'` : Detecta cada vez que se crea una nueva instancia del objeto **Vanie**.
* + `'vista'` : Detecta todos los eventos que ocultan o muestran las ventanas en pantalla
* + `'colision'` : Detecta la colisión de las ventanas con los límites impuestos en la variable _globalVenie.limites_ y emite una señal en el caso de que se haya cruzado y/o abandonado el límite.
* + `'pulsar'` : Detecta cuando una ventana ha sido pulsada.
* + `'arrastre'` : Detecta cuando una ventana esta siendo arrastrada.
* @param {function(Vanie|{vanie:Vanie visibles:number seMostro:boolean seOculto:boolean}|{der:boolean izq:boolean sup:boolean inf:boolean}):void} funcion La función a ejecutarse puede aceptar un argumento.
* @example
* globalVanie.addEventListener('registro', vanie=>{
* console.log(`la ventana ${vanie.identificador} se ha registrado`) });
*
* globalVanie.addEventListener('vista', objeto=>{
* objeto.vanie; // Vanie: ventana que lanzo el evento.
* objeto.visibles; // number: número de ventanas visibles en pantalla.
* objeto.seOculto; // boolean: true si la ventana se ocultó, false si no fue así.
* objeto.seMostro; // boolean: true si la ventana se mostró en pantalla, false si no fue así.
* });
*
* globalVanie.addEventListener('colision', colision=>{
* colision.der; // boolean: true si excedió el límite derecho, false si salió del límite.
* colision.izq; // boolean: true si excedió el límite izquierdo, false si salió del límite.
* colision.sup; // boolean: true si excedió el límite superior, false si salió del límite.
* colision.inf; // boolean: true si excedió el límite inferior, false si salió del límite.
* });
*
* globalVanie.addEventListener('pulsar', ventana=>{
* console.log('ventana' + ventana.identificador + 'ha sido seleccionada');
* });
*
* globalVanie.addEventListener('arrastre',objeto=>{
* objeto.vanie; // Vanie: ventana que lanzo el evento.
* objeto.local; // Punto: coordenada local del mouse.
* objeto.global; // Punto: coordenada global del mouse.
* objeto.desplazo; // Desplazo: desplazamiento de la ventana.});
*/
addEventListener(evento,funcion){if(typeof evento == 'string' && typeof funcion == 'function') VanieAdmin.eventos(evento,funcion);}
/**
* Elimina las funciones enlazadas a los eventos.
* @param {string} evento el Nombre del evento:
* + `'registro'` : Detecta cada vez que se crea una nueva instancia del objeto **Vanie**.
* + `'vista'` : Detecta todos los eventos que ocultan o muestran las ventanas en pantalla
* + `'colision'` : Detecta la colisión de las ventanas con los límites impuestos en la variable _globalVenie.limites_ y emite una señal en el caso de que se haya cruzado y/o abandonado el límite.
* @param {function():void} funcion La función a Eliminar.
*/
removeEventListener(evento, funcion){VanieAdmin.eliminarEventos(evento,funcion);}
#__buscarEstiloPredeterminado(nombre){
if(this.#Epredefinidos.length)
for(let i = 0; i < this.#Epredefinidos.length; i++)
if(this.#Epredefinidos[i]?.data?.nombre == nombre) return this.#Epredefinidos[i];
return undefined;}
#__buscarEstiloPersonalizado(nombre){
if(this.#Epersonalizados.length)
for(let i = 0; i < this.#Epersonalizados.length; i++)
if(this.#Epersonalizados[i]?.data?.nombre == nombre) return this.#Epersonalizados[i];
return undefined;}}
/**
* La instancia global que controlara todos los objetos **Vanie**
*/
const globalVanie = new GestorVanie;
export {VanieAdmin,globalVanie}