Page Handler Update
This commit is contained in:
		
							parent
							
								
									7a05d26162
								
							
						
					
					
						commit
						1238a1ff7c
					
				|  | @ -0,0 +1,212 @@ | |||
| import { nextFrame } from "../animation/nextFrame"; | ||||
| import { OnAnimationFrame } from "../animation/OnAnimationFrame"; | ||||
| import { DateMath } from "../date/DateMath"; | ||||
| import { ActivityAnalyser } from "../dom/ActivityAnalyser"; | ||||
| import { Insight } from "../dom/Insight"; | ||||
| import { LandscapeScale } from "../dom/LandscapeScale"; | ||||
| import { PageHandler, PageHandlerMode } from "../dom/PageHandler"; | ||||
| import { UserAgentDeviceTypes } from "../dom/UserAgentDeviceType"; | ||||
| import { TemplatesManager, TemplatesManagerMode } from "../templates/TemplatesManager"; | ||||
| import { AppPathConverter } from "./AppPathConverter"; | ||||
| 
 | ||||
| export class AppInitializerData | ||||
| { | ||||
|   localAdress:string; | ||||
|   webAdress:string; | ||||
|   templatesList:string[]; | ||||
|   addTemplateStyles:boolean = false; | ||||
|   pageRoot:string; | ||||
|   pagesPath?:string; | ||||
|   disableForceHTTPS?:boolean; | ||||
|   htmlMode?:boolean; | ||||
|   allowWWWSubdomain?:boolean; | ||||
|    | ||||
| } | ||||
| 
 | ||||
| export class ElectronAppInitializerData | ||||
| { | ||||
|   appLocation:string; | ||||
|   templatesList:string[]; | ||||
|   pageRoot:string; | ||||
|   disableForceHTTPS?:boolean; | ||||
|   htmlMode?:boolean; | ||||
|   allowWWWSubdomain:boolean; | ||||
| } | ||||
| 
 | ||||
| export class App | ||||
| { | ||||
|   readonly activityAnalyser = new ActivityAnalyser(); | ||||
|   readonly onAnimationFrame = new OnAnimationFrame(); | ||||
|   readonly templatesManager = new TemplatesManager(); | ||||
|   readonly insight = new Insight(); | ||||
|   readonly landscapeScale = new LandscapeScale(); | ||||
|   readonly pathConverter = new AppPathConverter( this ); | ||||
| 
 | ||||
|   private _pageHandler:PageHandler; | ||||
|   private _initializerData:AppInitializerData; | ||||
|   private _electronInitializerData:ElectronAppInitializerData; | ||||
| 
 | ||||
|   protected _loaded = false; | ||||
|   get loaded() | ||||
|   { | ||||
|     return this._loaded; | ||||
|   } | ||||
| 
 | ||||
|   protected _loadedTime:Date = null; | ||||
| 
 | ||||
|   get timeElapsedSinceLoaded() | ||||
|   { | ||||
|     return DateMath.getDifferenceMs( new Date(), this._loadedTime ) / 1000; | ||||
|   }  | ||||
| 
 | ||||
|   setLoaded() | ||||
|   { | ||||
|     this._loaded = true; | ||||
|     this._loadedTime = new Date(); | ||||
|   } | ||||
| 
 | ||||
|   get initializerData(){ return this._initializerData; } | ||||
| 
 | ||||
|   get pageHandler(){ return this._pageHandler; } | ||||
| 
 | ||||
|    | ||||
|   get webAdress() | ||||
|   { | ||||
|     return this._initializerData.webAdress; | ||||
|   } | ||||
| 
 | ||||
|   get isHTMLMode() | ||||
|   { | ||||
|     return this._initializerData.htmlMode === true; | ||||
|   } | ||||
| 
 | ||||
|   grabHash() | ||||
|   { | ||||
|     let hash = window.location.hash; | ||||
|     return hash; | ||||
|   } | ||||
| 
 | ||||
|   get isLocal() | ||||
|   { | ||||
|     return false; | ||||
|   }    | ||||
| 
 | ||||
|   async initializeApp( data:AppInitializerData|ElectronAppInitializerData ):Promise<void> | ||||
|   {     | ||||
|     if ( document.readyState === "loading" ) | ||||
|     { | ||||
|       document.addEventListener(  | ||||
|         "DOMContentLoaded",  | ||||
|         ()=> | ||||
|         {  | ||||
|           this.setLoaded(); | ||||
|         }  | ||||
|       );  | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|       this.setLoaded(); | ||||
|     } | ||||
| 
 | ||||
|     setTimeout( ()=>{ this.setLoaded() }, 3000 ) | ||||
| 
 | ||||
|     if ( ! data.allowWWWSubdomain  ) | ||||
|     {  | ||||
|       let url = window.location + ""; | ||||
| 
 | ||||
|       if ( url.startsWith( "https://www." ) ) | ||||
|       { | ||||
|         let realURL = url.replace( /^https:\/\/www\./, "https://" ); | ||||
| 
 | ||||
|         window.location.assign( realURL ); | ||||
| 
 | ||||
|         return Promise.resolve(); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     UserAgentDeviceTypes.setOnBody(); | ||||
| 
 | ||||
|     if ( ( data as  ElectronAppInitializerData ).appLocation ) | ||||
|     { | ||||
|       console.log( "ELECTRON APP" ); | ||||
|       this._electronInitializerData = data as ElectronAppInitializerData; | ||||
| 
 | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|       this._initializerData = data as AppInitializerData; | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     if ( data.templatesList ) | ||||
|     { | ||||
|       let templatesList = data.templatesList; | ||||
|       let templateStylesMode = this.initializerData.addTemplateStyles ?  | ||||
|                                  TemplatesManagerMode.ADD_STYLES_TO_HEAD :  | ||||
|                                  TemplatesManagerMode.IGNORE_STYLES; | ||||
|                                   | ||||
|       this.templatesManager.setMode( templateStylesMode ); | ||||
|       templatesList.forEach( c => this.templatesManager.addTemplateHTML( c ) );           | ||||
|     } | ||||
|      | ||||
|     if ( this._initializerData ) | ||||
|     { | ||||
|       //console.log( "PAGE HANDLER FOR WEB" );
 | ||||
|       let appData = this._initializerData; | ||||
|       this._pageHandler = new PageHandler( appData.localAdress, appData.webAdress, data.disableForceHTTPS ); | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|       //console.log( "PAGE HANDLER FOR APP" );
 | ||||
|       let electronData = this._electronInitializerData; | ||||
|       this._pageHandler = new PageHandler( electronData.appLocation, null, electronData.disableForceHTTPS, PageHandlerMode.ELECTRON ); | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     this.pageHandler.setPageRootTag( data.pageRoot ); | ||||
| 
 | ||||
|     if ( this._initializerData.pagesPath ) | ||||
|     { | ||||
|       this.pageHandler.setPagesPath( this._initializerData.pagesPath ); | ||||
|     } | ||||
| 
 | ||||
|      | ||||
|     this.onAnimationFrame.run();     | ||||
| 
 | ||||
|     this.onAnimationFrame.addListener( | ||||
|       ()=> | ||||
|       { | ||||
|         this.landscapeScale.update(); | ||||
|         this.insight.update(); | ||||
|          | ||||
|       }  | ||||
|     )  | ||||
| 
 | ||||
|     this.activityAnalyser.start(); | ||||
| 
 | ||||
|     await this.pageHandler.initialize(); | ||||
| 
 | ||||
|     this.initializePage();     | ||||
| 
 | ||||
|   } | ||||
| 
 | ||||
|   async initializePage() | ||||
|   { | ||||
|     while ( ! this.loaded ) | ||||
|     { | ||||
|       await nextFrame(); | ||||
|     } | ||||
| 
 | ||||
|     //console.log( "App.initializePage" );
 | ||||
|     this.insight.grabElements(); | ||||
|     this.templatesManager.processChildren( document.body ); | ||||
|     this.pageHandler.replaceLinks(); | ||||
| 
 | ||||
|     return Promise.resolve(); | ||||
|   } | ||||
| 
 | ||||
|   static updateAll() | ||||
|   { | ||||
|     Insight.updateAll(); | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,34 @@ | |||
| 
 | ||||
| import { RootPathResolver } from "../dom/RootPathResolver"; | ||||
| import { App } from "./App"; | ||||
| 
 | ||||
| export class AppPathConverter | ||||
| { | ||||
|   #app:App; | ||||
|    | ||||
|   // root:  relative to root => ::/en/store 
 | ||||
|   // absolute: internet/localhost => https://rokojori.com/en/store || localhost/en/store
 | ||||
|   // relative: to current page => ../en/store
 | ||||
| 
 | ||||
|   constructor( app:App ) | ||||
|   { | ||||
|     this.#app = app; | ||||
|   } | ||||
| 
 | ||||
|   rootToRelative( rootPath:string ) | ||||
|   { | ||||
|     // ::/en/store/
 | ||||
|     // -> ../../en/store
 | ||||
| 
 | ||||
|     let page = this.#app.pageHandler.currentPage; | ||||
|     let rootToken = RootPathResolver.rootToken; | ||||
|     let pathToRoot = this.#app.pageHandler.rootPathResolver.getRootPath( page ); | ||||
| 
 | ||||
|     console.log( "PathToRoot", page, pathToRoot ); | ||||
|      | ||||
|     let value = rootPath; | ||||
|     value = value.replace( rootToken, pathToRoot ); | ||||
| 
 | ||||
|     return value; | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,29 @@ | |||
| export class DateHelper | ||||
| { | ||||
|   static now() | ||||
|   { | ||||
|     let date = new Date(); | ||||
| 
 | ||||
|     return date; | ||||
|   } | ||||
| 
 | ||||
|   static today() | ||||
|   { | ||||
|     let date = new Date(); | ||||
|     date.setHours( 0, 0, 0 ); | ||||
| 
 | ||||
|     return date; | ||||
|   } | ||||
| 
 | ||||
|   static createYMD( year:number, month:number = 1, day:number = 1, hours:number = 0, minutes:number = 0, seconds:number = 0  ) | ||||
|   { | ||||
|     let date = new Date(); | ||||
|      | ||||
|     date.setFullYear( year ); | ||||
|     date.setMonth( month - 1 ); | ||||
|     date.setDate( day ); | ||||
|     date.setHours( hours, minutes, seconds ); | ||||
| 
 | ||||
|     return date; | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,154 @@ | |||
| import { DateHelper } from "./DateHelper"; | ||||
| 
 | ||||
| export class DateMath | ||||
| { | ||||
|    | ||||
|   static sort( dates:Date[] ) | ||||
|   { | ||||
|     dates.sort( DateMath.sortDate ); | ||||
|   } | ||||
| 
 | ||||
|   static sortDate( a:Date, b:Date ) | ||||
|   { | ||||
|     return DateMath.isBefore( a, b ) ? -1 : 1; | ||||
|   } | ||||
| 
 | ||||
|   static isNowOlderThanMS( d:Date, ms:number ) | ||||
|   { | ||||
|     let difference = this.getDifferenceMs( DateHelper.now(), d ); | ||||
|     return difference > ms;  | ||||
|   } | ||||
| 
 | ||||
|   static isBeforeNow( d:Date ) | ||||
|   { | ||||
|     return d.getTime() < DateHelper.now().getTime(); | ||||
|   }   | ||||
| 
 | ||||
|   static isInThePast( d:Date ) | ||||
|   { | ||||
|     return DateMath.isBeforeNow( d ); | ||||
|   } | ||||
| 
 | ||||
|   static isExpired( d:Date ) | ||||
|   { | ||||
|     return DateMath.isInThePast( d ); | ||||
|   } | ||||
| 
 | ||||
|   static isExpiredMS( dateMS:number, durationMS:number ) | ||||
|   { | ||||
|     let end = dateMS + durationMS; | ||||
| 
 | ||||
|     return end <= DateHelper.now().getTime(); | ||||
|   } | ||||
|   | ||||
|   static isBefore( a:Date, b:Date ) | ||||
|   { | ||||
|     return a.getTime() < b.getTime(); | ||||
|   } | ||||
| 
 | ||||
|   static isBeforeOrEquals( a:Date, b:Date ) | ||||
|   { | ||||
|     return a.getTime() <= b.getTime(); | ||||
|   } | ||||
| 
 | ||||
|   static isAfter( a:Date, b:Date ) | ||||
|   { | ||||
|     return a.getTime() > b.getTime(); | ||||
|   } | ||||
| 
 | ||||
|   static isAfterNow( d:Date ) | ||||
|   { | ||||
|     return d.getTime() > DateHelper.now().getTime(); | ||||
|   } | ||||
| 
 | ||||
|   static isInTheFuture( d:Date ) | ||||
|   { | ||||
|     return DateMath.isAfterNow( d ); | ||||
|   } | ||||
| 
 | ||||
|   static getDifferenceMs( a:Date, b:Date ) | ||||
|   { | ||||
|     return a.getTime() - b.getTime(); | ||||
|   } | ||||
| 
 | ||||
|   static isAfterOrEquals( a:Date, b:Date ) | ||||
|   { | ||||
|     return a.getTime() >= b.getTime(); | ||||
|   } | ||||
| 
 | ||||
|   static addMilliseconds( a:Date, durationMS:number ) | ||||
|   { | ||||
|     let dateMS = a.getTime(); | ||||
|     let milliseconds = dateMS + durationMS;   | ||||
| 
 | ||||
|     return new Date( milliseconds ); | ||||
|   } | ||||
| 
 | ||||
|   static fromNowAddDays( days:number ) | ||||
|   { | ||||
|     return DateMath.addDays( new Date(), days ); | ||||
|   } | ||||
| 
 | ||||
|   static fromNowAddHours( hours:number ) | ||||
|   { | ||||
|     return DateMath.addHours( new Date(), hours ); | ||||
|   } | ||||
| 
 | ||||
|   static fromNowAddMinutes( minutes:number ) | ||||
|   { | ||||
|     return DateMath.addMinutes( new Date(), minutes ); | ||||
|   } | ||||
| 
 | ||||
|   static addSeconds( a:Date, duration:number ) | ||||
|   { | ||||
|     return this.addMilliseconds( a, duration * 1000 ); | ||||
|   } | ||||
| 
 | ||||
|   static addMinutes( a:Date, durationMinutes:number ) | ||||
|   { | ||||
|     return this.addSeconds( a, durationMinutes * 60 ); | ||||
|   } | ||||
| 
 | ||||
|   static addHours( a:Date, durationHours:number ) | ||||
|   { | ||||
|     return this.addMinutes( a, durationHours * 60 ); | ||||
|   } | ||||
| 
 | ||||
|   static addDays( a:Date, durationDays:number ) | ||||
|   { | ||||
|     return this.addHours( a, durationDays * 24 ); | ||||
|   } | ||||
| 
 | ||||
|   static nextMonth( date:Date ) | ||||
|   { | ||||
|     date = new Date( date ); | ||||
| 
 | ||||
|     if ( date.getMonth() == 11 ) | ||||
|     { | ||||
|       date.setFullYear( date.getFullYear() + 1, 0 ); | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|       date.setMonth( date.getMonth() + 1 ); | ||||
|     } | ||||
| 
 | ||||
|     return date;     | ||||
|   } | ||||
| 
 | ||||
|   static isOnSameDay( a:Date, b:Date ) | ||||
|   { | ||||
|     if ( a.getFullYear() !== b.getFullYear() ) | ||||
|     { | ||||
|       return false; | ||||
|     } | ||||
| 
 | ||||
|     if ( a.getMonth() !== b.getMonth() ) | ||||
|     { | ||||
|       return false; | ||||
|     } | ||||
| 
 | ||||
|     return a.getDate() === b.getDate(); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,72 @@ | |||
| import { EventSlot } from "../events/EventSlot"; | ||||
| import { OnBlur, OnFocus, OnMouseDown, OnMouseLeave, OnMouseMove, OnMouseUp, OnTouchCancel, OnTouchEnd, OnTouchMove, OnTouchStart } from "../dom/EventListeners"; | ||||
| 
 | ||||
| export class ActivityAnalyser | ||||
| { | ||||
|   readonly onActive = new EventSlot(); | ||||
|   readonly onPassive = new EventSlot(); | ||||
|   private _timeOutDurationMS = 3000; | ||||
|   private _active = false; | ||||
|   get active(){ return this._active; } | ||||
|    | ||||
|   private _timeOutCallback:any = null; | ||||
| 
 | ||||
|   start() | ||||
|   { | ||||
|     let setActive = ()=> | ||||
|     { | ||||
|       this._cancelPassiveState(); | ||||
| 
 | ||||
|       if ( ! this._active ) | ||||
|       { | ||||
|         this._active = true; | ||||
|         this.onActive.dispatch( null ); | ||||
|       } | ||||
|        | ||||
|       this._reschedulePassiveState(); | ||||
|     } | ||||
| 
 | ||||
|     let activeEvents =  | ||||
|     [  | ||||
|       OnFocus, OnBlur, | ||||
|       OnMouseMove, OnMouseDown, OnMouseUp, OnMouseLeave,  | ||||
|       OnTouchStart, OnTouchMove, OnTouchCancel, OnTouchEnd | ||||
|     ]; | ||||
| 
 | ||||
|      | ||||
|     activeEvents.forEach( | ||||
|       ( onEvent )=> | ||||
|       { | ||||
|         onEvent.add( document.body, setActive ); | ||||
|       } | ||||
|     ) | ||||
|   }  | ||||
| 
 | ||||
|   private _cancelPassiveState() | ||||
|   { | ||||
|     if ( this._timeOutCallback ) | ||||
|     { | ||||
|       clearTimeout( this._timeOutCallback ); | ||||
|       this._timeOutCallback = null; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private _reschedulePassiveState() | ||||
|   { | ||||
|     this._cancelPassiveState(); | ||||
| 
 | ||||
|     this._timeOutCallback = setTimeout( ()=>{ this._setPassive(); }, this._timeOutDurationMS ); | ||||
|   } | ||||
| 
 | ||||
|   private _setPassive() | ||||
|   { | ||||
|     if ( ! this._active ) | ||||
|     { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     this._active = false; | ||||
|     this.onPassive.dispatch( null ); | ||||
|   } | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,161 @@ | |||
| import { DOMEditor } from "./DOMEditor"; | ||||
| import { ElementAttribute } from "./ElementAttribute"; | ||||
| 
 | ||||
| export enum AttributeValueMatchMode | ||||
| { | ||||
|   EXACTLY, | ||||
|   STARTS_WITH | ||||
| } | ||||
| 
 | ||||
| export class AttributeValue | ||||
| { | ||||
|   private _value:string; | ||||
|   private _attribute:ElementAttribute; | ||||
|   private _mode:AttributeValueMatchMode = AttributeValueMatchMode.EXACTLY; | ||||
|    | ||||
| 
 | ||||
|   static get type_checkbox(){ return new AttributeValue( ElementAttribute.type, "checkbox" ); } | ||||
|   static get type_email(){ return new AttributeValue( ElementAttribute.type, "email" ); } | ||||
|   static get type_text(){ return new AttributeValue( ElementAttribute.type, "text" ); } | ||||
|   static get type_file(){ return new AttributeValue( ElementAttribute.type, "file" ); } | ||||
|   static get type_number(){ return new AttributeValue( ElementAttribute.type, "number" ); } | ||||
|   static get type_time(){ return new AttributeValue( ElementAttribute.type, "time" ); } | ||||
|   static get type_date(){ return new AttributeValue( ElementAttribute.type, "date" ); } | ||||
|   static get type_password(){ return new AttributeValue( ElementAttribute.type, "password" ); } | ||||
| 
 | ||||
|   static get target_blank(){ return new AttributeValue( ElementAttribute.target, "_blank" ); } | ||||
|    | ||||
|   static get preloadAuto() { return  new AttributeValue( ElementAttribute.preload, "auto" ); } | ||||
|   static get preloadNone() { return  new AttributeValue( ElementAttribute.preload, "none" ); } | ||||
|   static get preloadMetaData() { return  new AttributeValue( ElementAttribute.preload, "metadata" ); } | ||||
| 
 | ||||
|   static parseAttributeValue( source:string ) | ||||
|   { | ||||
|     let seperator = source.indexOf( "=" ); | ||||
| 
 | ||||
|     let attributeName = source.substring( 0, seperator ).trim(); | ||||
| 
 | ||||
|     let value = source.substring( seperator + 1 ).trim(); | ||||
| 
 | ||||
|     if ( value.startsWith( "\"" ) || value.startsWith( "'") ) | ||||
|     { | ||||
|       value = value.substring( 1, value.length - 1 ); | ||||
|     }  | ||||
|      | ||||
|      | ||||
|     let attribute = new ElementAttribute( attributeName, false ); | ||||
| 
 | ||||
|     return new AttributeValue( attribute, value ); | ||||
|   } | ||||
| 
 | ||||
|   constructor( attribute:ElementAttribute, value:string, mode:AttributeValueMatchMode = AttributeValueMatchMode.EXACTLY ) | ||||
|   { | ||||
|     this._value = value; | ||||
|     this._attribute = attribute; | ||||
|     this._mode = mode || AttributeValueMatchMode.EXACTLY; | ||||
|   } | ||||
| 
 | ||||
|   get attribute() | ||||
|   { | ||||
|     return this._attribute; | ||||
|   } | ||||
| 
 | ||||
|   get value() | ||||
|   { | ||||
|     return this._value; | ||||
|   } | ||||
| 
 | ||||
|   get selector():string | ||||
|   { | ||||
|     switch ( this._mode ) | ||||
|     { | ||||
|       case AttributeValueMatchMode.EXACTLY: | ||||
|       {  | ||||
|         return this._attribute.selectorEquals( this._value ); | ||||
|       } | ||||
|        | ||||
|       case AttributeValueMatchMode.STARTS_WITH:  | ||||
|       {  | ||||
|         return this._attribute.selectorStartsWith( this._value );  | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     console.log( "No selector" ); | ||||
| 
 | ||||
|     return null; | ||||
|   } | ||||
| 
 | ||||
|   find( elements:Element[] ) | ||||
|   { | ||||
|     for ( let i = 0; i < elements.length; i++ ) | ||||
|     { | ||||
|       if ( this.in( elements[ i ] ) ) | ||||
|       { | ||||
|         return elements[ i ]; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     return null; | ||||
|   } | ||||
| 
 | ||||
|   query<T extends Element>( element:Element ) | ||||
|   { | ||||
|     return element.querySelector( this.selector ) as T; | ||||
|   } | ||||
| 
 | ||||
|   queryDoc<T extends Element>() | ||||
|   { | ||||
|     return document.querySelector( this.selector ) as T; | ||||
|   } | ||||
| 
 | ||||
|   queryAll( element:Element ) | ||||
|   { | ||||
|     return DOMEditor.nodeListToArray( element.querySelectorAll( this.selector ) ); | ||||
|   } | ||||
| 
 | ||||
|   queryAllInDoc() | ||||
|   { | ||||
|     return DOMEditor.nodeListToArray( document.querySelectorAll( this.selector ) ); | ||||
|   } | ||||
| 
 | ||||
|   set( element:Element ) | ||||
|   { | ||||
|     this._attribute.to( element, this._value ); | ||||
|   } | ||||
| 
 | ||||
|   removeFrom( element:Element ) | ||||
|   { | ||||
|     this._attribute.removeFrom( element ); | ||||
|   } | ||||
| 
 | ||||
|   toggle( element:Element ) | ||||
|   { | ||||
|     if ( this.in( element ) ) | ||||
|     { | ||||
|       this.removeFrom( element ); | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|       this.set( element ); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   isSelected( selectElement:HTMLSelectElement ) | ||||
|   { | ||||
|     return selectElement.value == this.value; | ||||
|   } | ||||
| 
 | ||||
|   in( element:Element ) | ||||
|   { | ||||
|     let attValue = this._attribute.from( element ); | ||||
| 
 | ||||
|     switch ( this._mode ) | ||||
|     { | ||||
|       case AttributeValueMatchMode.EXACTLY: return attValue === this._value; | ||||
|       case AttributeValueMatchMode.STARTS_WITH: return attValue.startsWith( this._value ); | ||||
|     } | ||||
| 
 | ||||
|     return false; | ||||
| 
 | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,12 @@ | |||
| export class Cursor | ||||
| { | ||||
|   static lock( type:string = null ) | ||||
|   { | ||||
|      | ||||
|   } | ||||
| 
 | ||||
|   static free() | ||||
|   { | ||||
| 
 | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,138 @@ | |||
| import { Box2 } from "../geometry/Box2"; | ||||
| import { Vector2 } from "../geometry/Vector2"; | ||||
| 
 | ||||
| export class DOMHitTest | ||||
| { | ||||
|   static get scrollOffset():Vector2 | ||||
|   {  | ||||
|     return new Vector2( window.scrollX, window.scrollY ); | ||||
|   } | ||||
|     | ||||
|   static getPageRect( e:Element ):ClientRect | ||||
|   { | ||||
|     let pageBox = this.getPageBox( e ); | ||||
| 
 | ||||
|     return Box2.toDomRect( pageBox ); | ||||
|   } | ||||
| 
 | ||||
|   static other() | ||||
|   { | ||||
|     return true; | ||||
|   } | ||||
| 
 | ||||
|   static isPointerOver( me:MouseEvent|TouchEvent, e:Element ) | ||||
|   { | ||||
|     if ( ! me || ! e ) | ||||
|     { | ||||
|       return false; | ||||
|     } | ||||
| 
 | ||||
|     let relative = DOMHitTest.getNormalizedPointerPosition( me, e ); | ||||
|      | ||||
|     if ( relative.x < 0 || relative.x > 1) | ||||
|     { | ||||
|       return false; | ||||
|     } | ||||
| 
 | ||||
|     if ( relative.y < 0 || relative.y > 1 ) | ||||
|     { | ||||
|       return false; | ||||
|     } | ||||
|      | ||||
|     return true; | ||||
|   }  | ||||
| 
 | ||||
|   static getPageBox( e:Element ):Box2 | ||||
|   { | ||||
|     let pageBox = Box2.fromClientRect( e.getBoundingClientRect() ); | ||||
| 
 | ||||
|     pageBox.translate( this.scrollOffset ); | ||||
|   | ||||
|     return pageBox; | ||||
|   } | ||||
| 
 | ||||
|   private static _lastPageX:number = null; | ||||
|   private static _lastPageY:number = null; | ||||
| 
 | ||||
|   static getPointerPosition( e:MouseEvent|TouchEvent ):Vector2 | ||||
|   { | ||||
|     let isMouseEvent = /mouse|click/.test( e.type );  | ||||
|     console.log( e.type ); | ||||
|     let mouseEvent = isMouseEvent ? e as MouseEvent : null; | ||||
|     let touchEvent = isMouseEvent ? null : ( e as TouchEvent ); | ||||
|     let pageX = isMouseEvent ? mouseEvent.pageX : touchEvent.touches.length === 0 ? null : touchEvent.touches[ 0 ].pageX; | ||||
|     let pageY = isMouseEvent ? mouseEvent.pageY : touchEvent.touches.length === 0 ? null : touchEvent.touches[ 0 ].pageY; | ||||
| 
 | ||||
|     // if ( isMouseEvent && CustomMouse.active )
 | ||||
|     // {
 | ||||
|     //   pageX = CustomMouse.x;
 | ||||
|     //   pageY = CustomMouse.y;
 | ||||
|     // }
 | ||||
| 
 | ||||
|     if ( pageX === null ) | ||||
|     { | ||||
|       pageX = DOMHitTest._lastPageX === undefined ? window.innerWidth / 2 : DOMHitTest._lastPageX; | ||||
|     } | ||||
| 
 | ||||
|     if ( pageY === null ) | ||||
|     { | ||||
|       pageY = DOMHitTest._lastPageY === undefined ? window.innerHeight / 2 : DOMHitTest._lastPageY; | ||||
|     } | ||||
| 
 | ||||
|     DOMHitTest._lastPageX = pageX; | ||||
|     DOMHitTest._lastPageY = pageY; | ||||
|      | ||||
|     return new Vector2( pageX, pageY ); | ||||
|   } | ||||
| 
 | ||||
|   static getRelativePointerPosition( e:MouseEvent|TouchEvent, element:Element ) | ||||
|   { | ||||
|     let position = this.getPointerPosition( e ); | ||||
|     let pageRect = this.getPageBox( element );  | ||||
| 
 | ||||
|     return position.sub( pageRect.min ); | ||||
|   } | ||||
| 
 | ||||
|   static getRelativeWindowPosition( e:MouseEvent|TouchEvent ) | ||||
|   { | ||||
|     let position = this.getPointerPosition( e ); | ||||
|     let windowBox = new Box2( new Vector2( 0, 0 ), new Vector2( window.innerWidth, window.innerHeight ) ); | ||||
| 
 | ||||
|     position.sub( windowBox.min ); | ||||
|     let size = windowBox.size; | ||||
|     position.x /= size.x; | ||||
|     position.y /= size.y; | ||||
|      | ||||
|     return position; | ||||
| 
 | ||||
|   } | ||||
| 
 | ||||
|   static getNormalizedPointerPosition( e:MouseEvent|TouchEvent, element:Element ) | ||||
|   { | ||||
|     let position = this.getPointerPosition( e ); | ||||
|     let pageRect = this.getPageBox( element ); | ||||
| 
 | ||||
|     position.sub( pageRect.min ); | ||||
|     let size = pageRect.size; | ||||
|     position.x /= size.x; | ||||
|     position.y /= size.y; | ||||
| 
 | ||||
|     return position; | ||||
| 
 | ||||
|   } | ||||
| 
 | ||||
|   static getNormalizedPosition( position:Vector2, element:Element ) | ||||
|   { | ||||
|     let pageRect = this.getPageBox( element ); | ||||
| 
 | ||||
|     position.sub( pageRect.min ); | ||||
|     let size = pageRect.size; | ||||
|     position.x /= size.x; | ||||
|     position.y /= size.y; | ||||
| 
 | ||||
|     return position; | ||||
| 
 | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,23 @@ | |||
| export enum DOMOrientationType | ||||
| { | ||||
|   Portrait, | ||||
|   Landscape | ||||
| } | ||||
| 
 | ||||
| export class DOMOrientation | ||||
| { | ||||
|   static get type() | ||||
|   { | ||||
|     return this.isLandscape ? DOMOrientationType.Landscape : DOMOrientationType.Portrait; | ||||
|   } | ||||
| 
 | ||||
|   static get isLandscape() | ||||
|   { | ||||
|     return window.innerWidth > window.innerHeight; | ||||
|   } | ||||
| 
 | ||||
|   static get isPortrait() | ||||
|   { | ||||
|     return window.innerWidth < window.innerHeight; | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,932 @@ | |||
| import { Func } from "../tools/TypeUtilities"; | ||||
| import { Cursor } from "./Cursor"; | ||||
| import { DOMHitTest } from "./DOMHitTest"; | ||||
| 
 | ||||
| export type click = "click"; | ||||
| 
 | ||||
| export type contextmenu = "contextmenu"; | ||||
| 
 | ||||
| export type touchstart  = "touchstart"; | ||||
| export type touchmove   = "touchmove"; | ||||
| export type touchcancel = "touchcancel"; | ||||
| export type touchend    = "touchend"; | ||||
| 
 | ||||
| export type mousedown = "mousedown"; | ||||
| export type mousemove = "mousemove"; | ||||
| export type mouseup   = "mouseup"; | ||||
| export type mouseleave = "mouseleave"; | ||||
| export type dblclick   = "dblclick"; | ||||
| export type mouseover  = "mouseover"; | ||||
| export type mouseenter = "mouseenter"; | ||||
| export type wheel = "wheel"; | ||||
| 
 | ||||
| export type keydown = "keydown"; | ||||
| export type keyup = "keyup"; | ||||
| 
 | ||||
| export type blur  = "blur"; | ||||
| export type focus = "focus"; | ||||
| 
 | ||||
| export type change = "change"; | ||||
| export type input  = "input"; | ||||
| export type submit = "submit"; | ||||
| 
 | ||||
| export type resize = "resize"; | ||||
| 
 | ||||
| export type pause = "pause"; | ||||
| export type play  = "play"; | ||||
| export type ended = "ended";  | ||||
| export type timeupdate = "timeupdate"; | ||||
| 
 | ||||
| export type pointerlockchange = "pointerlockchange"; | ||||
| export type pointerlockerror = "pointerlockerror"; | ||||
| 
 | ||||
| export type load = "load"; | ||||
| 
 | ||||
| export type selectstart = "selectstart";  | ||||
| 
 | ||||
| // export type (\w+).+ 
 | ||||
| // $1 |
 | ||||
| export type EventListenerType =    | ||||
|   click | | ||||
| 
 | ||||
|   contextmenu |  | ||||
| 
 | ||||
|   touchstart | | ||||
|   touchmove | | ||||
|   touchcancel | | ||||
|   touchend | | ||||
| 
 | ||||
|   mousedown | | ||||
|   mousemove | | ||||
|   mouseup | | ||||
|   mouseleave | | ||||
|   dblclick | | ||||
|   mouseover |  | ||||
|   mouseenter | | ||||
|   wheel | | ||||
| 
 | ||||
|   keydown | | ||||
|   keyup | | ||||
| 
 | ||||
|   blur |  | ||||
|   focus | | ||||
|   change | | ||||
|   input | | ||||
|   submit | | ||||
| 
 | ||||
|   resize | | ||||
| 
 | ||||
|   pause | | ||||
|   play | | ||||
|   ended | | ||||
|   timeupdate | | ||||
| 
 | ||||
|   pointerlockchange | | ||||
|   pointerlockerror | | ||||
| 
 | ||||
|   load | | ||||
| 
 | ||||
|   selectstart  | ||||
|    | ||||
|   ; | ||||
| 
 | ||||
| // export type (\w+)\s*=\s*"(.+)"\s*; 
 | ||||
| // static readonly $1:$1 = "$2";
 | ||||
| export class Events | ||||
| { | ||||
|   static readonly MouseButtonLeft   = 0; | ||||
|   static readonly MouseButtonMiddle = 1; | ||||
|   static readonly MouseButtonRight  = 2; | ||||
| 
 | ||||
| 
 | ||||
|   static readonly click:click = "click"; | ||||
| 
 | ||||
|   static readonly contextmenu:contextmenu = "contextmenu"; | ||||
| 
 | ||||
|   static readonly touchstart:touchstart   = "touchstart"; | ||||
|   static readonly touchmove:touchmove     = "touchmove"; | ||||
|   static readonly touchcancel:touchcancel = "touchcancel"; | ||||
|   static readonly touchend:touchend       = "touchend"; | ||||
|    | ||||
|   static readonly mousedown:mousedown = "mousedown"; | ||||
|   static readonly mousemove:mousemove = "mousemove"; | ||||
|   static readonly mouseup:mouseup     = "mouseup"; | ||||
|   static readonly mouseleave:mouseleave     = "mouseleave"; | ||||
|   static readonly dblclick:dblclick = "dblclick"; | ||||
|   static readonly mouseover:mouseover     = "mouseover"; | ||||
|   static readonly mouseenter:mouseenter     = "mouseenter"; | ||||
| 
 | ||||
|   static readonly wheel:wheel = "wheel"; | ||||
| 
 | ||||
|   static readonly keydown:keydown = "keydown"; | ||||
|   static readonly keyup:keyup = "keyup"; | ||||
| 
 | ||||
|   static readonly blur:blur     = "blur"; | ||||
|   static readonly focus:focus   = "focus"; | ||||
|   static readonly change:change = "change"; | ||||
|   static readonly input:input   = "input"; | ||||
|   static readonly submit:submit   = "submit"; | ||||
| 
 | ||||
|   static readonly resize:resize   = "resize"; | ||||
| 
 | ||||
|   static readonly pause:pause   = "pause"; | ||||
|   static readonly play:play   = "play"; | ||||
|   static readonly ended:ended   = "ended"; | ||||
|   static readonly timeupdate:timeupdate   = "timeupdate"; | ||||
| 
 | ||||
|   static readonly pointerlockchange:pointerlockchange = "pointerlockchange"; | ||||
|   static readonly pointerlockerror:pointerlockerror = "pointerlockerror"; | ||||
| 
 | ||||
|   static readonly load = "load"; | ||||
| 
 | ||||
|   static readonly selectstart:selectstart = "selectstart"; | ||||
| 
 | ||||
|    | ||||
| 
 | ||||
|   static copyMouseEvent( e:MouseEvent ) | ||||
|   { | ||||
|     return new MouseEvent( e.type, e ); | ||||
|   } | ||||
| 
 | ||||
|   static copyTouchEvent( e:TouchEvent ) | ||||
|   { | ||||
|     let te = new TouchEvent( e.type, e as any ); | ||||
|     return te; | ||||
|   } | ||||
| 
 | ||||
|   static copyMouseOrTouchEvent( e:TouchEvent|MouseEvent ) | ||||
|   { | ||||
|     if ( this.isMouseEvent( e ) ) | ||||
|     { | ||||
|       return this.copyMouseEvent( e as MouseEvent ); | ||||
|     } | ||||
| 
 | ||||
|     return this.copyTouchEvent( e as TouchEvent ); | ||||
|   } | ||||
|    | ||||
|   static is( e:Event, ...types:EventListenerType[] ) | ||||
|   { | ||||
|     for ( let t of types ) | ||||
|     { | ||||
|       if ( e.type === t ) | ||||
|       { | ||||
|         return true; | ||||
|       } | ||||
|     }  | ||||
| 
 | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   static isType( e:string, ...types:EventListenerType[] ) | ||||
|   { | ||||
|     for ( let t of types ) | ||||
|     { | ||||
|       if ( e === t ) | ||||
|       { | ||||
|         return true; | ||||
|       } | ||||
|     }  | ||||
| 
 | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   static isEventOneOf( e:string|Event, ...types:EventListenerType[] ) | ||||
|   { | ||||
|     if ( typeof e === "string" ) | ||||
|     { | ||||
|       for ( let t of types ) | ||||
|       { | ||||
|         if ( e === t ) | ||||
|         { | ||||
|           return true; | ||||
|         } | ||||
|       }  | ||||
| 
 | ||||
|       return false; | ||||
|     } | ||||
| 
 | ||||
|     for ( let t of types ) | ||||
|     { | ||||
|       if ( e.type === t ) | ||||
|       { | ||||
|         return true; | ||||
|       } | ||||
|     }  | ||||
| 
 | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   static isMouseEvent( e:MouseEvent|TouchEvent ) | ||||
|   { | ||||
|     return /^mouse/.test( e.type ) || ( e.type === Events.dblclick ); | ||||
|   } | ||||
| 
 | ||||
|   static îsTouchEvent( e:MouseEvent|TouchEvent ) | ||||
|   { | ||||
|     return /^touch/.test( e.type )  | ||||
|   }  | ||||
| 
 | ||||
|   static toMouseEvent( e:MouseEvent|TouchEvent ) | ||||
|   { | ||||
|     if ( ! this.isMouseEvent( e ) ) | ||||
|     { | ||||
|       console.log( "Not mouse event:", e.type ); | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     return e as MouseEvent; | ||||
|   } | ||||
| 
 | ||||
|   static toTouchEvent( e:MouseEvent|TouchEvent ) | ||||
|   { | ||||
|     if ( this.îsTouchEvent( e ) ) | ||||
|     { | ||||
|       return null; | ||||
|     } | ||||
|      | ||||
|     return e as TouchEvent; | ||||
|   } | ||||
| 
 | ||||
|   static isStart( e:MouseEvent|TouchEvent|string ) | ||||
|   { | ||||
|     return Events.isEventOneOf( e, Events.mousedown, Events.touchstart ); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   static isMove( e:MouseEvent|TouchEvent|string ) | ||||
|   { | ||||
|     return Events.isEventOneOf( e, Events.mousemove, Events.touchmove ); | ||||
|   } | ||||
| 
 | ||||
|   static isEnd( e:MouseEvent|TouchEvent|string ) | ||||
|   { | ||||
|     return Events.isEventOneOf( e, Events.mouseup, Events.touchend, Events.touchcancel ); | ||||
|   } | ||||
| 
 | ||||
|   static silent( e:Event ) | ||||
|   { | ||||
|     e.stopImmediatePropagation(); | ||||
|     e.stopPropagation(); | ||||
|     e.preventDefault(); | ||||
|   } | ||||
| 
 | ||||
|   static once<T>( target:EventTarget, type:EventListenerType, callback:(t:T)=>void ) | ||||
|   { | ||||
|     let onceCallback = ( e:T )=> | ||||
|     { | ||||
|       try | ||||
|       { | ||||
|         callback( e ); | ||||
|       } | ||||
|       catch( ex ) | ||||
|       { | ||||
|         console.error( ex ); | ||||
|       } | ||||
| 
 | ||||
|       target.removeEventListener( type, onceCallback as any ); | ||||
|     } | ||||
| 
 | ||||
|     target.addEventListener( type, onceCallback as any  ); | ||||
|   } | ||||
| 
 | ||||
|   static disableSelection( e:HTMLElement ):Func<void> | ||||
|   { | ||||
|     let preventer = ( ev:any ) => { ev.preventDefault() }; | ||||
| 
 | ||||
|     OnMouseDown.add( e, preventer ); | ||||
|     OnSelectStart.add( e, preventer ); | ||||
| 
 | ||||
|     let remover = ()=> | ||||
|     { | ||||
|       OnMouseDown.remove( e, preventer ); | ||||
|       OnSelectStart.remove( e, preventer ); | ||||
|     } | ||||
| 
 | ||||
|     return remover; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class OnPointerLockChange | ||||
| { | ||||
|   static add( element:HTMLElement|Document, callback:( e:Event )=>void, options:any = undefined ) | ||||
|   { | ||||
|     document.addEventListener( Events.pointerlockchange, callback, options ); | ||||
|   } | ||||
| 
 | ||||
|   static remove( element:HTMLElement|Document, callback:( e:Event )=>void ) | ||||
|   { | ||||
|     document.removeEventListener( Events.pointerlockchange, callback ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class OnWheel | ||||
| { | ||||
|   static add( element:HTMLElement, callback:( e:WheelEvent )=>void, options:any = undefined ) | ||||
|   { | ||||
|     element.addEventListener( Events.wheel, callback, options ); | ||||
|   } | ||||
| 
 | ||||
|   static remove( element:HTMLElement, callback:( e:WheelEvent )=>void ) | ||||
|   { | ||||
|     element.removeEventListener( Events.wheel, callback ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export type  MouseEventCallback = ( e:MouseEvent )=>void; | ||||
| 
 | ||||
| export class WrappedCallback | ||||
| { | ||||
|   element:HTMLElement; | ||||
|   wrappedCallback:MouseEventCallback; | ||||
| } | ||||
| 
 | ||||
| export class OnClick | ||||
| { | ||||
|   static add( element:HTMLElement, callback:MouseEventCallback, options:any = undefined ) | ||||
|   { | ||||
|     element.addEventListener( Events.click, callback, options ); | ||||
|   } | ||||
| 
 | ||||
|   static remove( element:HTMLElement, callback:MouseEventCallback ) | ||||
|   { | ||||
|     element.removeEventListener( Events.click, callback ); | ||||
|   }  | ||||
| } | ||||
| 
 | ||||
| export class OnMouseDown | ||||
| { | ||||
|   static add( element:HTMLElement, callback:( e:MouseEvent )=>void, options:any = undefined ) | ||||
|   { | ||||
|     element.addEventListener( Events.mousedown, callback, options ); | ||||
|   } | ||||
| 
 | ||||
|   static remove( element:HTMLElement, callback:( e:MouseEvent )=>void ) | ||||
|   { | ||||
|     element.removeEventListener( Events.mousedown, callback ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| export class OnContextMenu | ||||
| { | ||||
|   static add( element:HTMLElement, callback:( e:MouseEvent )=>void, options:any = undefined ) | ||||
|   { | ||||
|     element.addEventListener( Events.contextmenu, callback, options ); | ||||
|   } | ||||
| 
 | ||||
|   static remove( element:HTMLElement, callback:( e:MouseEvent )=>void ) | ||||
|   { | ||||
|     element.removeEventListener( Events.contextmenu, callback ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| export class OnMouseDrag | ||||
| { | ||||
|   static noHold = false; | ||||
| 
 | ||||
|   static add( element:HTMLElement, callback:( e:MouseEvent )=>void|boolean, options:any = undefined ) | ||||
|   { | ||||
|     let noHold = OnMouseDrag.noHold; | ||||
|     let isHoldInteraction = null; | ||||
|     let downStart = 0; | ||||
|     let holdInteractionTreshold = 500; | ||||
|   | ||||
|     let checkAutomaticSwitch = (e:MouseEvent) =>  | ||||
|     { | ||||
|       let time = new Date().getTime(); | ||||
|       let elapsed = time - downStart; | ||||
| 
 | ||||
|       if ( elapsed > holdInteractionTreshold ) | ||||
|       { | ||||
|         removeEvents( e ); | ||||
|       } | ||||
|       else | ||||
|       { | ||||
|         Cursor.lock(); | ||||
|         //UIComponent.lockCursor( UICursors.releaseDragging + "" );
 | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     let removeEvents = ( e:MouseEvent )=> | ||||
|     { | ||||
|       if ( noHold ) | ||||
|       { | ||||
|         let mouseUpEvent = new MouseEvent( Events.mouseup, e ); | ||||
|         callback( mouseUpEvent ); | ||||
|       } | ||||
|       else | ||||
|       { | ||||
|         callback( e ); | ||||
|       } | ||||
|        | ||||
| 
 | ||||
|       OnMouseMove.remove( document.body, callback ); | ||||
| 
 | ||||
|       if ( noHold ) | ||||
|       { | ||||
|         OnMouseUp.remove( document.body, checkAutomaticSwitch ); | ||||
|         OnMouseDown.remove( document.body, removeEvents ); | ||||
|         //UIComponent.freeCursor();
 | ||||
| 
 | ||||
|         Cursor.free(); | ||||
|       } | ||||
|       else | ||||
|       { | ||||
|         OnMouseUp.remove( document.body, removeEvents ); | ||||
|       }       | ||||
|        | ||||
|     } | ||||
| 
 | ||||
|      | ||||
|      | ||||
|     let addEvents = ()=> | ||||
|     {     | ||||
|       OnMouseMove.add( document.body, callback ); | ||||
| 
 | ||||
|       if ( noHold ) | ||||
|       { | ||||
|         OnMouseUp.add( document.body, checkAutomaticSwitch ); | ||||
|         OnMouseDown.add( document.body, removeEvents ); | ||||
|       } | ||||
|       else | ||||
|       { | ||||
|         OnMouseUp.add( document.body, removeEvents ); | ||||
|       } | ||||
|        | ||||
|     }  | ||||
| 
 | ||||
|     let onDown =  ( e:MouseEvent )=>  | ||||
|     {  | ||||
|       let result = callback( e ); | ||||
| 
 | ||||
|       if ( result === false ) | ||||
|       { | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       downStart = new Date().getTime(); | ||||
|       isHoldInteraction = true; | ||||
|        | ||||
|       addEvents(); | ||||
|     } | ||||
| 
 | ||||
|     OnMouseDown.add( element, onDown ); | ||||
| 
 | ||||
| 
 | ||||
|     return onDown; | ||||
|      | ||||
|   } | ||||
| 
 | ||||
|   static remove( element:HTMLElement, callback:( e:MouseEvent )=>void ) | ||||
|   { | ||||
|     OnMouseDown.remove( element, callback ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class OnMouseOver | ||||
| { | ||||
|   static add( element:HTMLElement, callback:( e:MouseEvent )=>void, options:any = undefined ) | ||||
|   { | ||||
|     element.addEventListener( Events.mouseover, callback, options ); | ||||
|   } | ||||
| 
 | ||||
|   static remove( element:HTMLElement, callback:( e:MouseEvent )=>void ) | ||||
|   { | ||||
|     element.removeEventListener( Events.mouseover, callback ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class OnMouseEnter | ||||
| { | ||||
|   static add( element:HTMLElement, callback:( e:MouseEvent )=>void, options:any = undefined ) | ||||
|   { | ||||
|     element.addEventListener( Events.mouseenter, callback, options ); | ||||
|   } | ||||
| 
 | ||||
|   static remove( element:HTMLElement, callback:( e:MouseEvent )=>void ) | ||||
|   { | ||||
|     element.removeEventListener( Events.mouseenter, callback ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class OnDoubleClick | ||||
| { | ||||
|   static add( element:HTMLElement, callback:( e:MouseEvent )=>void, options:any = undefined ) | ||||
|   { | ||||
|     element.addEventListener( Events.dblclick, callback, options ); | ||||
|   } | ||||
| 
 | ||||
|   static remove( element:HTMLElement, callback:( e:MouseEvent )=>void ) | ||||
|   { | ||||
|     element.removeEventListener( Events.dblclick, callback ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class OnMouseMove | ||||
| { | ||||
|   static add( element:HTMLElement, callback:( e:MouseEvent )=>void, options:any = undefined ) | ||||
|   { | ||||
|     element.addEventListener( Events.mousemove, callback, options ); | ||||
|   } | ||||
| 
 | ||||
|   static remove( element:HTMLElement, callback:( e:MouseEvent )=>void ) | ||||
|   { | ||||
|     element.removeEventListener( Events.mousemove, callback ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class OnMouseUp | ||||
| { | ||||
|   static add( element:HTMLElement, callback:( e:MouseEvent )=>void, options:any = undefined ) | ||||
|   { | ||||
|     element.addEventListener( Events.mouseup, callback, options ); | ||||
|   } | ||||
| 
 | ||||
|   static remove( element:HTMLElement, callback:( e:MouseEvent )=>void ) | ||||
|   { | ||||
|     element.removeEventListener( Events.mouseup, callback ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class OnMouseLeave | ||||
| { | ||||
|   static add( element:HTMLElement, callback:( e:MouseEvent )=>void, options:any = undefined ) | ||||
|   { | ||||
|     element.addEventListener( Events.mouseleave, callback, options ); | ||||
|   } | ||||
| 
 | ||||
|   static remove( element:HTMLElement, callback:( e:MouseEvent )=>void ) | ||||
|   { | ||||
|     element.removeEventListener( Events.mouseleave, callback ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class OnKeyDown | ||||
| { | ||||
|   static add( element:HTMLElement, callback:( e:KeyboardEvent )=>void, options:any = undefined ) | ||||
|   { | ||||
|     element.addEventListener( Events.keydown, callback, options ); | ||||
|   } | ||||
| 
 | ||||
|   static remove( element:HTMLElement, callback:( e:KeyboardEvent )=>void ) | ||||
|   { | ||||
|     element.removeEventListener( Events.keydown, callback ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class OnKeyUp | ||||
| { | ||||
|   static add( element:HTMLElement, callback:( e:KeyboardEvent )=>void, options:any = undefined ) | ||||
|   { | ||||
|     element.addEventListener( Events.keyup, callback, options ); | ||||
|   } | ||||
| 
 | ||||
|   static remove( element:HTMLElement, callback:( e:KeyboardEvent )=>void ) | ||||
|   { | ||||
|     element.removeEventListener( Events.keyup, callback ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class OnTouchStart | ||||
| { | ||||
|   static add( element:HTMLElement, callback:( e:TouchEvent )=>void, options:any = undefined ) | ||||
|   { | ||||
|     element.addEventListener( Events.touchstart, callback, options ); | ||||
|   } | ||||
| 
 | ||||
|   static remove( element:HTMLElement, callback:( e:TouchEvent )=>void ) | ||||
|   { | ||||
|     element.removeEventListener( Events.touchstart, callback ); | ||||
|   } | ||||
| } | ||||
| export class OnTouchEnd | ||||
| { | ||||
|   static add( element:HTMLElement, callback:( e:TouchEvent )=>void, options:any = undefined ) | ||||
|   { | ||||
|     element.addEventListener( Events.touchend, callback, options ); | ||||
|   } | ||||
| 
 | ||||
|   static remove( element:HTMLElement, callback:( e:TouchEvent )=>void ) | ||||
|   { | ||||
|     element.removeEventListener( Events.touchend, callback ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class OnTouchCancel | ||||
| { | ||||
|   static add( element:HTMLElement, callback:( e:TouchEvent )=>void, options:any = undefined ) | ||||
|   { | ||||
|     element.addEventListener( Events.touchcancel, callback, options ); | ||||
|   } | ||||
| 
 | ||||
|   static remove( element:HTMLElement, callback:( e:TouchEvent )=>void ) | ||||
|   { | ||||
|     element.removeEventListener( Events.touchcancel, callback ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class OnTouchMove | ||||
| { | ||||
|   static add( element:HTMLElement, callback:( e:TouchEvent )=>void, options:any = undefined ) | ||||
|   { | ||||
|     element.addEventListener( Events.touchmove, callback, options ); | ||||
|   } | ||||
| 
 | ||||
|   static remove( element:HTMLElement, callback:( e:TouchEvent )=>void ) | ||||
|   { | ||||
|     element.removeEventListener( Events.touchmove, callback ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class OnTouchDrag | ||||
| { | ||||
|   static add( element:HTMLElement, callback:( e:TouchEvent )=>void, options:any = undefined ) | ||||
|   { | ||||
|     OnTouchStart.add( element, callback, options ); | ||||
|     OnTouchMove.add( element, callback, options ); | ||||
|     OnTouchEnd.add( element, callback, options ); | ||||
|   } | ||||
| 
 | ||||
|   static remove( element:HTMLElement, callback:( e:TouchEvent )=>void ) | ||||
|   { | ||||
|     OnTouchStart.remove( element, callback ); | ||||
|     OnTouchMove.remove( element, callback ); | ||||
|     OnTouchEnd.remove( element, callback ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class OnDrag | ||||
| { | ||||
|   static add( element:HTMLElement, callback:( e:TouchEvent|MouseEvent )=>void, options:any = undefined ) | ||||
|   { | ||||
|     OnTouchDrag.add( element, callback ); | ||||
|     let mouseCallback = OnMouseDrag.add( element, callback ); | ||||
| 
 | ||||
|     return { touch:callback, mouse:mouseCallback }; | ||||
|   } | ||||
| 
 | ||||
|   static remove( element:HTMLElement, callbackData:{ touch:()=>void, mouse:()=>void } ) | ||||
|   { | ||||
|     OnTouchDrag.add( element, callbackData.touch ); | ||||
|     OnMouseDrag.add( element, callbackData.mouse ); | ||||
|   } | ||||
|      | ||||
| } | ||||
| 
 | ||||
| export type HORIZONTAL = "HORIZONTAL"; | ||||
| export type VERTICAL   = "VERTICAL"; | ||||
| export type BOTH       = "BOTH"; | ||||
| 
 | ||||
| export type SwipeType = HORIZONTAL | VERTICAL | BOTH; | ||||
| 
 | ||||
| export class OnSwipe | ||||
| { | ||||
|   static readonly HORIZONTAL:HORIZONTAL = "HORIZONTAL"; | ||||
|   static readonly VERTICAL:VERTICAL     = "VERTICAL"; | ||||
|   static readonly BOTH:BOTH             = "BOTH"; | ||||
| 
 | ||||
|   static add( type:SwipeType, treshold:number, element:HTMLElement, callback:( e:TouchEvent )=>void, options:any = undefined ) | ||||
|   { | ||||
|     let onStart =  | ||||
|     ( startEvent:TouchEvent )=> | ||||
|     { | ||||
|       let checkingMovement = true; | ||||
|       let active = false;  | ||||
|       let startPosition = DOMHitTest.getPointerPosition( startEvent ); | ||||
| 
 | ||||
|       let onDrag = ( dragEvent:TouchEvent )=> | ||||
|       { | ||||
|         if ( checkingMovement ) | ||||
|         {  | ||||
|           let position = DOMHitTest.getPointerPosition( dragEvent ); | ||||
|           let difference = position.sub( startPosition ); | ||||
|           let moved = difference.length > treshold; | ||||
|            | ||||
|           if ( moved ) | ||||
|           { | ||||
|             if ( OnSwipe.HORIZONTAL === type ) | ||||
|             { | ||||
|               active = Math.abs( difference.x ) >  Math.abs( difference.y ); | ||||
|             } | ||||
|             else if ( OnSwipe.VERTICAL === type ) | ||||
|             { | ||||
|               active =  Math.abs( difference.y ) >  Math.abs( difference.x ); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|               active = true; | ||||
|             } | ||||
| 
 | ||||
|             checkingMovement = false; | ||||
|             callback( startEvent ); | ||||
|           } | ||||
|         } | ||||
| 
 | ||||
|         if ( ! active ) | ||||
|         { | ||||
|           return; | ||||
|         } | ||||
| 
 | ||||
|         callback( dragEvent ); | ||||
| 
 | ||||
|         dragEvent.preventDefault(); | ||||
|         dragEvent.stopImmediatePropagation(); | ||||
|         dragEvent.stopPropagation(); | ||||
|       } | ||||
| 
 | ||||
|        | ||||
| 
 | ||||
|       let removeListeners = ( endEvent :TouchEvent ) => | ||||
|       { | ||||
|         if ( active ) | ||||
|         { | ||||
|           callback( endEvent ); | ||||
| 
 | ||||
|           endEvent.preventDefault(); | ||||
|           endEvent.stopImmediatePropagation(); | ||||
|           endEvent.stopPropagation(); | ||||
|    | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|         OnTouchMove.remove( element, onDrag ); | ||||
|         OnTouchCancel.remove( element, removeListeners ); | ||||
|         OnTouchEnd.remove( element, removeListeners ); | ||||
|       }  | ||||
| 
 | ||||
|       OnTouchMove.add( element, onDrag ); | ||||
|       OnTouchCancel.add( element, removeListeners ); | ||||
|       OnTouchEnd.add( element, removeListeners ); | ||||
|     }; | ||||
| 
 | ||||
|     OnTouchStart.add( element, onStart ); | ||||
| 
 | ||||
|     return onStart; | ||||
|   } | ||||
| 
 | ||||
|   static remove( element:HTMLElement, callback:( e:TouchEvent )=>void ) | ||||
|   { | ||||
|     OnTouchStart.remove( element, callback ); | ||||
|   } | ||||
|      | ||||
| } | ||||
| 
 | ||||
| export class OnBlur | ||||
| { | ||||
|   static add( element:HTMLElement, callback:( e:FocusEvent )=>void, options:any = undefined ) | ||||
|   { | ||||
|     element.addEventListener( Events.blur, callback, options ); | ||||
|   } | ||||
| 
 | ||||
|   static remove( element:HTMLElement, callback:( e:FocusEvent )=>void ) | ||||
|   { | ||||
|     element.removeEventListener( Events.blur, callback ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class OnFocus | ||||
| { | ||||
|   static add( element:HTMLElement, callback:( e:FocusEvent )=>void, options:any = undefined ) | ||||
|   { | ||||
|     element.addEventListener( Events.focus, callback, options ); | ||||
|   } | ||||
| 
 | ||||
|   static remove( element:HTMLElement, callback:( e:FocusEvent )=>void ) | ||||
|   { | ||||
|     element.removeEventListener( Events.focus, callback ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class OnChange | ||||
| { | ||||
|   static add( element:HTMLElement|HTMLInputElement, callback:( e:Event )=>void, options:any = undefined ) | ||||
|   { | ||||
|     element.addEventListener( Events.change, callback, options ); | ||||
|   } | ||||
| 
 | ||||
|   static remove( element:HTMLElement|HTMLInputElement, callback:( e:Event )=>void ) | ||||
|   { | ||||
|     element.removeEventListener( Events.change, callback ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class OnInput | ||||
| { | ||||
|   static add( element:HTMLElement, callback:( e:InputEvent )=>void, options:any = undefined ) | ||||
|   { | ||||
|     ( element as any ).addEventListener( Events.input, callback, options ); | ||||
|   } | ||||
| 
 | ||||
|   static remove( element:HTMLElement, callback:( e:InputEvent )=>void ) | ||||
|   { | ||||
|     ( element as any ).removeEventListener( Events.input, callback ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class OnSubmit | ||||
| { | ||||
|   static add( element:HTMLFormElement, callback:( e:Event )=>void, options:any = undefined ) | ||||
|   { | ||||
|     element.addEventListener( Events.submit, callback, options ); | ||||
|   } | ||||
| 
 | ||||
|   static remove( element:HTMLFormElement, callback:( e:Event )=>void ) | ||||
|   { | ||||
|     element.removeEventListener( Events.submit, callback ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class OnResize | ||||
| { | ||||
|   static add( window:Window, callback:( e:Event )=>void, options:any = undefined ) | ||||
|   { | ||||
|     window.addEventListener( Events.resize, callback, options ); | ||||
|   } | ||||
| 
 | ||||
|   static remove( window:Window, callback:( e:Event )=>void ) | ||||
|   { | ||||
|     window.removeEventListener( Events.resize, callback ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class OnPlay | ||||
| { | ||||
|   static add( audioElement:HTMLAudioElement, callback:( e:Event )=>void, options:any = undefined ) | ||||
|   { | ||||
|     audioElement.addEventListener( Events.play, callback, options ); | ||||
|   } | ||||
| 
 | ||||
|   static remove( audioElement:HTMLAudioElement, callback:( e:Event )=>void ) | ||||
|   { | ||||
|     audioElement.removeEventListener( Events.play, callback ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class OnPause | ||||
| { | ||||
|   static add( audioElement:HTMLAudioElement, callback:( e:Event )=>void, options:any = undefined ) | ||||
|   { | ||||
|     audioElement.addEventListener( Events.pause, callback, options ); | ||||
|   } | ||||
| 
 | ||||
|   static remove( audioElement:HTMLAudioElement, callback:( e:Event )=>void ) | ||||
|   { | ||||
|     audioElement.removeEventListener( Events.pause, callback ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class OnEnded | ||||
| { | ||||
|   static add( audioElement:HTMLAudioElement, callback:( e:Event )=>void, options:any = undefined ) | ||||
|   { | ||||
|     audioElement.addEventListener( Events.ended, callback, options ); | ||||
|   } | ||||
| 
 | ||||
|   static remove( audioElement:HTMLAudioElement, callback:( e:Event )=>void ) | ||||
|   { | ||||
|     audioElement.removeEventListener( Events.ended, callback ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class OnTimeUpdate | ||||
| { | ||||
|   static add( audioElement:HTMLAudioElement, callback:( e:Event )=>void, options:any = undefined ) | ||||
|   { | ||||
|     audioElement.addEventListener( Events.timeupdate, callback, options ); | ||||
|   } | ||||
| 
 | ||||
|   static remove( audioElement:HTMLAudioElement, callback:( e:Event )=>void ) | ||||
|   { | ||||
|     audioElement.removeEventListener( Events.timeupdate, callback ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class OnLoad | ||||
| { | ||||
|   static add( element:HTMLScriptElement|FileReader, callback:( e:Event )=>void, options:any = undefined ) | ||||
|   { | ||||
|     element.addEventListener( Events.load, callback, options ); | ||||
|   } | ||||
| 
 | ||||
|   static remove( element:HTMLScriptElement|FileReader, callback:( e:Event )=>void ) | ||||
|   { | ||||
|     element.removeEventListener( Events.load, callback ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class OnSelectStart | ||||
| { | ||||
|   static add( element:HTMLElement, callback:( e:Event )=>void, options:any = undefined ) | ||||
|   { | ||||
|     element.addEventListener( Events.selectstart, callback, options ); | ||||
|   } | ||||
| 
 | ||||
|   static remove( element:HTMLElement, callback:( e:Event )=>void ) | ||||
|   { | ||||
|     element.removeEventListener( Events.selectstart, callback ); | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,372 @@ | |||
| 
 | ||||
| 
 | ||||
| import { ClassFlag } from "./ClassFlag"; | ||||
| import { HTMLNodeTreeWalker } from '../graphs/HTMLNodeTreeWalker'; | ||||
| import { Box2 } from "../geometry/Box2"; | ||||
| import { Vector2 } from "../geometry/Vector2"; | ||||
| import { ElementAttribute } from "./ElementAttribute"; | ||||
| import { MapList } from "../tools/MapList"; | ||||
| 
 | ||||
| export type above_sight = "above-sight"; | ||||
| export type in_sight    = "in-sight"; | ||||
| export type below_sight = "below-sight"; | ||||
| 
 | ||||
| export type InsighStateType = above_sight | in_sight | below_sight; | ||||
| 
 | ||||
| export class InsighState | ||||
| { | ||||
|   static readonly above_sight:above_sight = "above-sight"; | ||||
|   static readonly in_sight:in_sight       = "in-sight"; | ||||
|   static readonly below_sight:below_sight = "below-sight"; | ||||
| } | ||||
| 
 | ||||
| export class InsightData | ||||
| { | ||||
|   insight:Insight; | ||||
|   isInsight = false; | ||||
|   wasInsight = false; | ||||
|   viewBox:Box2 = null; | ||||
|   elementBox:Box2 = new Box2(); | ||||
|   changeTime:number = 0; | ||||
| } | ||||
| 
 | ||||
| export class InsightElement extends Element | ||||
| { | ||||
| 
 | ||||
|   _insightData:InsightData; | ||||
| 
 | ||||
|   static ensure( element:Element ) | ||||
|   { | ||||
|     let insightElement = element as InsightElement; | ||||
| 
 | ||||
|     if ( ! insightElement._insightData ) | ||||
|     { | ||||
|       insightElement._insightData = new InsightData(); | ||||
|     } | ||||
| 
 | ||||
|     return insightElement; | ||||
|   } | ||||
| 
 | ||||
|   static forAllInsightElements( root:Element, callback:( insightElement:InsightElement )=>void ) | ||||
|   { | ||||
|     let walker = HTMLNodeTreeWalker.$; | ||||
| 
 | ||||
|     walker.forAll( root,  | ||||
|       n => | ||||
|       { | ||||
|         let insightElement = n as InsightElement; | ||||
| 
 | ||||
|         if ( insightElement._insightData ) | ||||
|         { | ||||
|           callback( insightElement ); | ||||
|         } | ||||
|       }  | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export type InsightCallback = ()=>void; | ||||
| export type CallbackMapList = Map<Element,InsightCallback[]>; | ||||
| 
 | ||||
| 
 | ||||
| export class Insight | ||||
| { | ||||
|   static attribute = new ElementAttribute( "insight" ); | ||||
|   static wasSeenFlag = new ClassFlag( "was-in-sight" ); | ||||
|   static maxHeight = new ElementAttribute( "max-insight-height" ); | ||||
| 
 | ||||
|   static readonly above_sight = new ClassFlag( InsighState.above_sight ); | ||||
|   static readonly in_sight    = new ClassFlag( InsighState.in_sight ); | ||||
|   static readonly below_sight = new ClassFlag( InsighState.below_sight ); | ||||
| 
 | ||||
|   static changeFrequencyMS = 200;  | ||||
|   private _elements:Element[] = [];  | ||||
|   private _offsightCallbackElements:CallbackMapList; | ||||
|   private _insightCallbackElements:CallbackMapList; | ||||
| 
 | ||||
|   private _viewBoxSize:Vector2 = null; | ||||
|   private _hasScreenUpdate = false; | ||||
|   private _lastYOffset = 0; | ||||
|   private _yTresholdForChanges = 15; | ||||
|   private _lastGrabbedTime = 0; | ||||
|   private _afterGrabingUpdateDurationMS = 3000;  | ||||
| 
 | ||||
|   static _allInstances:Insight[] = []; | ||||
| 
 | ||||
|   static updateAll() | ||||
|   { | ||||
|     this._allInstances.forEach( i => { i._hasScreenUpdate = true; i.update() } ); | ||||
|   } | ||||
| 
 | ||||
|   constructor( publishGlobally:boolean = true ) | ||||
|   { | ||||
|     if ( publishGlobally ) | ||||
|     { | ||||
|       Insight._allInstances.push( this ); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   onInsight( element:Element, callback:InsightCallback ) | ||||
|   { | ||||
|     MapList.add( this._insightCallbackElements, element, callback ); | ||||
|   } | ||||
| 
 | ||||
|   onOffsight( element:Element, callback:InsightCallback ) | ||||
|   { | ||||
|     MapList.add( this._offsightCallbackElements, element, callback ); | ||||
|   } | ||||
| 
 | ||||
|   grabElements() | ||||
|   {      | ||||
|     this._lastGrabbedTime = new Date().getTime(); | ||||
| 
 | ||||
|     this._elements.forEach(  | ||||
|       e =>  | ||||
|       { | ||||
|         let element = InsightElement.ensure( e );  | ||||
|         element._insightData.insight = null; | ||||
|       } | ||||
|      | ||||
|     ); | ||||
| 
 | ||||
|     this._elements = Insight.attribute.queryAll( document.body );  | ||||
|     this._offsightCallbackElements = new Map<Element,InsightCallback[]>(); | ||||
|     this._insightCallbackElements = new Map<Element,InsightCallback[]>(); | ||||
| 
 | ||||
|     this._elements.forEach(  | ||||
|       e =>  | ||||
|       { | ||||
|         let element = InsightElement.ensure( e );  | ||||
|         element._insightData.insight = this; | ||||
|       } | ||||
|      | ||||
|     ); | ||||
| 
 | ||||
|     //console.log( this._elements );
 | ||||
|   } | ||||
| 
 | ||||
|   update() | ||||
|   { | ||||
|     this._updateViewBox(); | ||||
| 
 | ||||
|     let currentTime = new Date().getTime(); | ||||
|     let elapsedSinceGrabbing = currentTime - this._lastGrabbedTime; | ||||
|      | ||||
|     if ( elapsedSinceGrabbing < this._afterGrabingUpdateDurationMS ) | ||||
|     { | ||||
|       this._hasScreenUpdate = true; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     let yChange = window.pageYOffset - this._lastYOffset; | ||||
| 
 | ||||
|     if ( Math.abs( yChange ) > this._yTresholdForChanges  ) | ||||
|     { | ||||
|       this._hasScreenUpdate = true; | ||||
|     } | ||||
| 
 | ||||
|     if ( ! this._hasScreenUpdate ) | ||||
|     { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     this._lastYOffset = window.pageYOffset; | ||||
|     this._hasScreenUpdate = false; | ||||
| 
 | ||||
|     let now = new Date().getTime(); | ||||
|     this._elements.forEach( e => this.updateElement( e, now ) ); | ||||
|   } | ||||
| 
 | ||||
|   updateElement( e:Element, updateTime:number ) | ||||
|   { | ||||
|     let insightElement = InsightElement.ensure( e ); | ||||
|     let insightData =  insightElement._insightData;     | ||||
| 
 | ||||
|     let viewBox = insightData.viewBox; | ||||
|      | ||||
|     if ( ! viewBox ) | ||||
|     { | ||||
|       viewBox = this._getViewBox( e ); | ||||
|       insightData.viewBox = viewBox; | ||||
|     } | ||||
| 
 | ||||
|     let elementBox = insightData.elementBox; | ||||
| 
 | ||||
| 
 | ||||
|     Box2.updatefromClientRect( insightData.elementBox, e.getBoundingClientRect() ); | ||||
|      | ||||
|     if ( Insight.maxHeight.in( e ) ) | ||||
|     { | ||||
|       let maxHeight = Insight.maxHeight.from( e ); | ||||
|       let isPercentage = /\%/.test( maxHeight ); | ||||
|       let maxHeightPixel = parseFloat( maxHeight.replace( /\%|px/, "" ) ); | ||||
|        | ||||
|       if ( isPercentage ) | ||||
|       { | ||||
|         maxHeightPixel *= window.innerHeight / 100; | ||||
|       } | ||||
|        | ||||
|       let size = elementBox.size; | ||||
|        | ||||
|       if ( size.y > maxHeightPixel ) | ||||
|       { | ||||
|         let center = elementBox.center; | ||||
|         let offset = maxHeightPixel / 2; | ||||
|         elementBox.min.y = center.y - offset; | ||||
|         elementBox.max.y = center.y + offset;   | ||||
|       } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     let insight = viewBox.intersectsBox( elementBox ); | ||||
|     /* | ||||
|     console.log(  | ||||
|       "Is in sight", insight, | ||||
|       "DOCUMENT VIEW BOX:", viewBox,  | ||||
|       "ELEMENT BOX:", elementBox, | ||||
|       "ELEMENT", e  | ||||
|     );*/ | ||||
| 
 | ||||
|     if ( insight === insightData.isInsight ) | ||||
|     { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     let timeElapsedSinceLastChange = updateTime - insightData.changeTime; | ||||
|      | ||||
|      | ||||
|     if ( timeElapsedSinceLastChange <= Insight.changeFrequencyMS ) | ||||
|     { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     insightData.changeTime = updateTime; | ||||
|     insightData.isInsight = insight; | ||||
|      | ||||
|     if ( insight ) | ||||
|     { | ||||
|       Insight.in_sight.setAs( e, true );  | ||||
|       Insight.above_sight.setAs( e, false);  | ||||
|       Insight.below_sight.setAs( e, false );  | ||||
|        | ||||
|       if ( this._insightCallbackElements.has( e ) ) | ||||
|       { | ||||
|         this._insightCallbackElements.get( e ).forEach( c => c() ); | ||||
|       } | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|       let isAbove = elementBox.max.y < viewBox.min.y;  | ||||
|       Insight.in_sight.setAs( e, false );  | ||||
|       Insight.above_sight.setAs( e, isAbove);  | ||||
|       Insight.below_sight.setAs( e, ! isAbove );  | ||||
| 
 | ||||
|       if ( this._offsightCallbackElements.has( e ) ) | ||||
|       { | ||||
|         this._offsightCallbackElements.get( e ).forEach( c => c() ); | ||||
|       } | ||||
|     } | ||||
|          | ||||
|      | ||||
| 
 | ||||
|     if ( insightData.wasInsight ) | ||||
|     { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     setTimeout( ()=>{ Insight.wasSeenFlag.set( e ); }, 200 ); | ||||
|      | ||||
|   }  | ||||
|    | ||||
| 
 | ||||
|   private _updateViewBox() | ||||
|   { | ||||
|     if ( ! this._elements ) | ||||
|     { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     if ( ! this._needsViewBoxUpdate() ) | ||||
|     { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     this._hasScreenUpdate = true; | ||||
| 
 | ||||
|     this._viewBoxSize.x = window.innerWidth; | ||||
|     this._viewBoxSize.y = window.innerHeight; | ||||
| 
 | ||||
|     this._elements.forEach(  | ||||
|       e =>  | ||||
|       { | ||||
|         let insightElement = InsightElement.ensure( e ); | ||||
|         let data = insightElement._insightData; | ||||
|         data.viewBox = this._getViewBox( e );; | ||||
|       } | ||||
|     ); | ||||
| 
 | ||||
|   } | ||||
| 
 | ||||
|   private _needsViewBoxUpdate() | ||||
|   { | ||||
|     if ( this._viewBoxSize == null ) | ||||
|     { | ||||
|       this._viewBoxSize = new Vector2( 0, 0 ); | ||||
|       return true; | ||||
|     } | ||||
| 
 | ||||
|     if ( this._viewBoxSize.x !== window.innerWidth || this._viewBoxSize.y !== window.innerHeight ) | ||||
|     { | ||||
|       return true; | ||||
|     } | ||||
| 
 | ||||
|     return false; | ||||
|   } | ||||
|    | ||||
|    | ||||
| 
 | ||||
|   private _getViewBox( e:Element ):Box2 | ||||
|   { | ||||
| 
 | ||||
|     let relativeStart = 15; | ||||
|     let relativeEnd = 100 - relativeStart; | ||||
| 
 | ||||
|     let coords = Insight.attribute.from( e ); | ||||
|      | ||||
|     if ( coords && coords.length > 0 ) | ||||
|     { | ||||
|       let numbers = coords.split( "," ); | ||||
| 
 | ||||
|       if ( numbers.length == 1 ) | ||||
|       { | ||||
|         relativeStart = parseFloat( numbers[ 0 ] ); | ||||
|         relativeEnd = 100 - relativeStart; | ||||
|       } | ||||
|       else if ( numbers.length == 2 ) | ||||
|       { | ||||
|         relativeStart = parseFloat( numbers[ 0 ] ); | ||||
|         relativeEnd = 100 - parseFloat( numbers[ 1 ] ); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     if ( isNaN( relativeStart ) ) | ||||
|     { | ||||
|       relativeStart = 15; | ||||
|     } | ||||
| 
 | ||||
|     if ( isNaN( relativeEnd ) ) | ||||
|     { | ||||
|       relativeEnd = 100 - relativeStart; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     //console.log( "VIEW BOX START/END", relativeStart, relativeEnd );
 | ||||
| 
 | ||||
|     let startY = window.innerHeight * relativeStart / 100; | ||||
|     let endY   = window.innerHeight * relativeEnd  / 100;       | ||||
| 
 | ||||
|     return new Box2( new Vector2( -10000, startY ), new Vector2( 10000, endY ) ); | ||||
| 
 | ||||
|     | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,105 @@ | |||
| import { DOMOrientation, DOMOrientationType } from "../dom/DOMOrientation"; | ||||
| import { EventSlot } from "../events/EventSlot"; | ||||
| import { ElementType } from "./ElementType"; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| export class LandscapeScale | ||||
| { | ||||
|   private _styleElement:Element; | ||||
|   private _lastWidth:number; | ||||
|   private _lastHeight:number; | ||||
|   private _lastOrientation:DOMOrientationType; | ||||
|   private _ultraWideRatio:number = 2.2; | ||||
|   private _sw:number; | ||||
|   private _sh:number; | ||||
|   private _swidth:number; | ||||
|   private _sheight:number; | ||||
|   private _sx:number; | ||||
|   private _sy:number; | ||||
|   private _spx:number; | ||||
| 
 | ||||
|   get sw() | ||||
|   { | ||||
|     return this._sw; | ||||
|   } | ||||
|    | ||||
|   onScreenSizeChange = new EventSlot<LandscapeScale>(); | ||||
|   onOrientationChange = new EventSlot<LandscapeScale>(); | ||||
| 
 | ||||
| 
 | ||||
|   update() | ||||
|   { | ||||
|     let newWidth = window.innerWidth; | ||||
|     let newHeight = window.innerHeight; | ||||
|     let newOrientation = DOMOrientation.type; | ||||
| 
 | ||||
|     if ( newWidth === this._lastWidth && newHeight === this._lastHeight ) | ||||
|     { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     this.onScreenSizeChange.dispatch( this ); | ||||
| 
 | ||||
|     if ( this._lastOrientation != newOrientation ) | ||||
|     { | ||||
|       this._lastOrientation = newOrientation; | ||||
|       this.onOrientationChange.dispatch( this ); | ||||
|     } | ||||
|      | ||||
|     if ( this._styleElement == null ) | ||||
|     { | ||||
|       this._styleElement = ElementType.style.create(); | ||||
|       document.body.appendChild( this._styleElement ); | ||||
|     } | ||||
| 
 | ||||
|     let ratio = newWidth / newHeight; | ||||
|     this._lastWidth = newWidth; | ||||
|     this._lastHeight = newHeight; | ||||
| 
 | ||||
|     this._sw = newWidth / 100; | ||||
| 
 | ||||
|     if ( ratio > this._ultraWideRatio ) | ||||
|     { | ||||
|       this._sw *= ( this._ultraWideRatio / ratio ); | ||||
|       this._sh = this._sw / 16 * 9; | ||||
| 
 | ||||
|       this._sx = ( newWidth - ( this._sw * 100 ) ) / 2; | ||||
|       this._sy = 0; | ||||
| 
 | ||||
|       this._spx =  ratio / this._ultraWideRatio; | ||||
|        | ||||
|     }  | ||||
|     else | ||||
|     { | ||||
|       this._spx = 1; | ||||
|       this._sx = 0; | ||||
|       this._sy = 0; | ||||
|        | ||||
|     } | ||||
| 
 | ||||
|      | ||||
| 
 | ||||
|     this._swidth = this._sw  * 100; | ||||
|     this._sheight = this._sh * 100; | ||||
| 
 | ||||
| 
 | ||||
|     this._styleElement.innerHTML =  | ||||
|     ` | ||||
|       :root | ||||
|       { | ||||
|         --sw:${this._sw}px; | ||||
|         --sh:${this._sh}px; | ||||
|         --sx:${this._sx}px; | ||||
|         --sy:${this._sy}px; | ||||
|         --swidth:${this._swidth}px; | ||||
|         --sheight:${this._sheight}px; | ||||
|         --spx:${this._spx}px; | ||||
|         --spx-raw:${this._spx}; | ||||
|         --spx-inv:${1/this._spx}px; | ||||
|       } | ||||
|     ` | ||||
| 
 | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,74 @@ | |||
| export enum LinkType | ||||
| { | ||||
|   RELATIVE, | ||||
|   ROOT, | ||||
|   ABSOLUTE | ||||
| } | ||||
| 
 | ||||
| export class Link | ||||
| { | ||||
|   type:LinkType; | ||||
|   path:string; | ||||
| 
 | ||||
|    | ||||
|   clone() | ||||
|   { | ||||
|     let c = new Link(); | ||||
| 
 | ||||
|     c.type = this.type; | ||||
|     c.path = this.path; | ||||
|     return c; | ||||
|   } | ||||
| 
 | ||||
|   prettify() | ||||
|   { | ||||
|     let clone = this.clone(); | ||||
| 
 | ||||
|     if ( clone.path.endsWith( "index.php" ) ) | ||||
|     { | ||||
|       clone.path = clone.path.replace( /index\.php$/, "" ); | ||||
|     } | ||||
| 
 | ||||
|     if ( clone.path.endsWith( ".php" ) ) | ||||
|     { | ||||
|       clone.path = clone.path.replace( /\.php$/, "" ); | ||||
|     } | ||||
| 
 | ||||
|     if ( clone.path.endsWith( "/" ) ) | ||||
|     { | ||||
|       clone.path = clone.path.replace( /\/$/, "" ); | ||||
|     } | ||||
| 
 | ||||
|     return clone; | ||||
|   } | ||||
| 
 | ||||
|   static Root( path:string ) | ||||
|   { | ||||
|     let c = new Link(); | ||||
| 
 | ||||
|     c.type = LinkType.ROOT; | ||||
|     c.path = path; | ||||
|     return c; | ||||
|   } | ||||
| 
 | ||||
|   static Absolute( path:string ) | ||||
|   { | ||||
|     let c = new Link(); | ||||
| 
 | ||||
|     c.type = LinkType.ABSOLUTE; | ||||
|     c.path = path; | ||||
|     return c; | ||||
|   } | ||||
| 
 | ||||
|   static Relative( path:string ) | ||||
|   { | ||||
|     let c = new Link(); | ||||
| 
 | ||||
|     c.type = LinkType.RELATIVE; | ||||
|     c.path = path; | ||||
|     return c; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,167 @@ | |||
| 
 | ||||
| import { Loader } from "../xhttp/Loader"; | ||||
| import { PageHandler } from "./PageHandler"; | ||||
| 
 | ||||
| export enum PageDataState | ||||
| { | ||||
|   INITIAL, | ||||
|   LOADING, | ||||
|   SUCCESSED, | ||||
|   FAILED | ||||
| } | ||||
| 
 | ||||
| export class PageData | ||||
| { | ||||
|   private _pageHandler:PageHandler; | ||||
|   private _rawHTML:string; | ||||
|   get rawHTML(){ return this._rawHTML;} | ||||
|   private _pageRootStartIndex:number; | ||||
|   private _pageRootEndIndex:number; | ||||
|   private _title:string; | ||||
|   private _state:PageDataState = PageDataState.INITIAL; | ||||
|   private _absolutePath:string; | ||||
|   private _relativePath:string; | ||||
|   private _cacheTime:number; | ||||
|   private _language:string = null; | ||||
| 
 | ||||
|   scrollTop:number; | ||||
| 
 | ||||
|   constructor( pageHandler:PageHandler, absolutePath:string, relativePath:string ) | ||||
|   { | ||||
|     this._pageHandler = pageHandler; | ||||
|     this._absolutePath = absolutePath; | ||||
|     this._relativePath = relativePath; | ||||
|     this._state = PageDataState.INITIAL; | ||||
|     this.scrollTop = 0;     | ||||
|   } | ||||
| 
 | ||||
|   async load():Promise<void> | ||||
|   {  | ||||
|     if ( this.isLoading ) | ||||
|     { | ||||
|       return await this._waitUntilLoaded(); | ||||
|     } | ||||
| 
 | ||||
|     this._state = PageDataState.LOADING; | ||||
| 
 | ||||
|     let path = this._absolutePath; | ||||
|      | ||||
|     let rawHTML:string = null; | ||||
|      | ||||
|     try | ||||
|     { | ||||
|       let pageParameters = this._pageHandler.pageParameters; | ||||
|       this._cacheTime = new Date().getTime(); | ||||
|       let pageURL = path + pageParameters + "&time=" +  this._cacheTime; | ||||
|       console.log( pageURL ); | ||||
|       rawHTML = await Loader.loadText( pageURL ); | ||||
|       this._rawHTML = rawHTML; | ||||
|       this._state = PageDataState.SUCCESSED; | ||||
|     } | ||||
|     catch( e ) | ||||
|     { | ||||
|       this._rawHTML = null; | ||||
|       this._state = PageDataState.FAILED; | ||||
|     } | ||||
| 
 | ||||
|     return Promise.resolve();     | ||||
|   } | ||||
|    | ||||
|   private async _waitUntilLoaded():Promise<void> | ||||
|   { | ||||
|     let pageData = this; | ||||
| 
 | ||||
|     let promise = new Promise<void> | ||||
|     ( | ||||
|       ( resolve, reject ) => | ||||
|       {         | ||||
|         let checkForReadiness = ()=> | ||||
|         { | ||||
|           if ( pageData.ready ) | ||||
|           { | ||||
|             resolve(); | ||||
|             return; | ||||
|           } | ||||
| 
 | ||||
|           setTimeout( checkForReadiness, 100 ); | ||||
|         } | ||||
| 
 | ||||
|         checkForReadiness(); | ||||
|       } | ||||
|     ); | ||||
| 
 | ||||
|     return Promise.resolve( promise ); | ||||
|   } | ||||
| 
 | ||||
|   get ready() | ||||
|   { | ||||
|     return this._state === PageDataState.SUCCESSED || this._state === PageDataState.FAILED; | ||||
|   } | ||||
| 
 | ||||
|   get isLoading() | ||||
|   { | ||||
|     return this._state === PageDataState.LOADING; | ||||
|   } | ||||
| 
 | ||||
|   get title() | ||||
|   { | ||||
|     if ( ! this._title ) | ||||
|     { | ||||
|       let titleStartTag = "<title>"; | ||||
|       let titleEndTag = "</title>"; | ||||
| 
 | ||||
|       let titleStart = this._rawHTML.indexOf( titleStartTag ) + titleStartTag.length;   | ||||
|       let titleEnd = this._rawHTML.indexOf( titleEndTag, titleStart ) | ||||
| 
 | ||||
|       this._title = this._rawHTML.substring( titleStart, titleEnd ); | ||||
|     } | ||||
| 
 | ||||
|     return this._title; | ||||
|   } | ||||
| 
 | ||||
|   get language() | ||||
|   { | ||||
|     if ( ! this._language ) | ||||
|     { | ||||
|       let langStartMatcher = "<html lang=\""; | ||||
|       let langEndMatcher = "\""; | ||||
| 
 | ||||
|       let langStart = this._rawHTML.indexOf( langStartMatcher ); | ||||
| 
 | ||||
|       if ( langStart === -1 ) | ||||
|       { | ||||
|         this._language = "en"; | ||||
|         return this._language; | ||||
|       } | ||||
| 
 | ||||
|       let langEnd = this._rawHTML.indexOf( langEndMatcher, langStart + langStartMatcher.length + 1 ); | ||||
| 
 | ||||
|       let snippet = this._rawHTML.substring( langStart, langEnd ); | ||||
| 
 | ||||
|       let result =  /\"(.+)/.exec( snippet ); | ||||
|        | ||||
|       this._language = result[ 1 ]; | ||||
|     } | ||||
| 
 | ||||
|     return this._language; | ||||
|   } | ||||
| 
 | ||||
|   get pageRootInnerHTML() | ||||
|   { | ||||
|     if ( ! this._pageRootStartIndex ) | ||||
|     { | ||||
|       let pageRootTag = this._pageHandler.pageRootTag; | ||||
| 
 | ||||
|       let pageRootStartTagBegin = this._rawHTML.indexOf( `<${pageRootTag}` ); | ||||
|       let pageRootStartEnd = this._rawHTML.indexOf( ">", pageRootStartTagBegin ) + 1; | ||||
|       let pageRootEnd = this._rawHTML.lastIndexOf( `</${pageRootTag}>` ); | ||||
| 
 | ||||
|       this._pageRootStartIndex = pageRootStartEnd; | ||||
|       this._pageRootEndIndex = pageRootEnd; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     return this._rawHTML.substring( this._pageRootStartIndex, this._pageRootEndIndex ); | ||||
|   } | ||||
| 
 | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -0,0 +1,7 @@ | |||
| import { PageHandler } from "./PageHandler"; | ||||
| 
 | ||||
| export abstract class PageTransitionHandler | ||||
| { | ||||
|   abstract onTransitionOut( pageHandler:PageHandler ):Promise<void>; | ||||
|   abstract onTransitionIn( pageHandler:PageHandler ):Promise<void>; | ||||
| } | ||||
|  | @ -0,0 +1,228 @@ | |||
| 
 | ||||
| import { HTMLNodeTreeWalker } from "../graphs/HTMLNodeTreeWalker"; | ||||
| import { DOMNameSpaces } from "./DOMNameSpaces"; | ||||
| import { ElementAttribute } from "./ElementAttribute"; | ||||
| 
 | ||||
| export class ResolvablePathAttribute | ||||
| {  | ||||
|   private _selector:string; | ||||
|   private _attribute:ElementAttribute | ||||
| 
 | ||||
|   constructor( selector:string, attribute:string, namespace?:string ) | ||||
|   { | ||||
|     this._selector = selector; | ||||
|     this._attribute = new ElementAttribute( attribute, false, namespace ); | ||||
|   }  | ||||
| 
 | ||||
|    | ||||
|   get combinedSelector() | ||||
|   { | ||||
|     let combined = `${this._selector}${this._attribute.selector}`; | ||||
| 
 | ||||
|     return combined; | ||||
|   } | ||||
| 
 | ||||
|   get useNodeNameMatcher() | ||||
|   { | ||||
|     return this._selector === "image"; | ||||
|   } | ||||
| 
 | ||||
|   getFrom( target:Element|Document ) | ||||
|   {           | ||||
|     if ( this.useNodeNameMatcher ) | ||||
|     {       | ||||
|       let matchedElements:Element[] = []; | ||||
| 
 | ||||
|       let walker = new HTMLNodeTreeWalker(); | ||||
| 
 | ||||
|       let walkable = target;             | ||||
|       if ( ( target as Document ).documentElement ) | ||||
|       { | ||||
|         walkable = ( target as Document).documentElement; | ||||
|       } | ||||
| 
 | ||||
|       walker.forAll( walkable,  | ||||
|         n =>  | ||||
|         {  | ||||
|           if ( n.nodeName === this._selector ) | ||||
|           { | ||||
|             matchedElements.push( n as Element ); | ||||
|           } | ||||
|         } | ||||
|       ); | ||||
| 
 | ||||
|       return matchedElements; | ||||
|     } | ||||
| 
 | ||||
|     let elements = target.querySelectorAll( this.combinedSelector  ); | ||||
| 
 | ||||
|     return Array.prototype.slice.call( elements ); | ||||
|   } | ||||
|    | ||||
| 
 | ||||
|   process( element:Element, rootToken:string, rootPath:string ) | ||||
|   { | ||||
|     let value = this._attribute.from( element ); | ||||
| 
 | ||||
|     if ( value === null ) | ||||
|     { | ||||
|       if ( this._selector === "image" ) | ||||
|       {         | ||||
|         console.log( "no value for image" );         | ||||
|         this.lgGetAttribute( element, "href" );       | ||||
|         this.lgGetAttribute( element, "xlink:href" );   | ||||
|       } | ||||
| 
 | ||||
|       return; | ||||
|     } | ||||
|     else if ( this._selector === "image" ) | ||||
|     { | ||||
|        | ||||
|     }  | ||||
| 
 | ||||
|     /*if ( ! value.startsWith( rootToken ) ) | ||||
|     {      | ||||
|       if ( this._selector === "image" ) | ||||
|       {         | ||||
|         let shortValue = value; | ||||
| 
 | ||||
|         if ( value.length > 100 ) | ||||
|         { | ||||
|           shortValue = value.substring( 0, 100 ); | ||||
|         } | ||||
| 
 | ||||
|          | ||||
|       } | ||||
|       else if ( this._attribute.name === "style" ) | ||||
|       { | ||||
|         value = value.replace( rootToken, rootPath ) | ||||
|       } | ||||
| 
 | ||||
|       return; | ||||
|     }*/ | ||||
| 
 | ||||
|     value = value.replace( rootToken, rootPath ) | ||||
| 
 | ||||
|     this._attribute.to( element, value ); | ||||
|   } | ||||
| 
 | ||||
|   lgGetAttribute( element:Element, attribute:string ) | ||||
|   { | ||||
|     this.lg( `${attribute} >> ${element.getAttribute( attribute )} `); | ||||
|     this.lg( `${attribute} NS=null >> ${element.getAttributeNS( null, attribute )} `); | ||||
|     this.lg( `${attribute} NS="" >> ${element.getAttributeNS( "", attribute )} `); | ||||
|     this.lg( `${attribute} NS=XLink >> ${element.getAttributeNS( DOMNameSpaces.XLink, attribute )} `); | ||||
|     this.lg( `${attribute} NS=SVG >> ${element.getAttributeNS( DOMNameSpaces.SVG, attribute )} `); | ||||
|   } | ||||
| 
 | ||||
|   lg( value:string ) | ||||
|   { | ||||
|     let shortValue = value; | ||||
| 
 | ||||
|     if ( value.length > 100 ) | ||||
|     { | ||||
|       shortValue = value.substring( 0, 100 ) + "..."; | ||||
|     } | ||||
| 
 | ||||
|     console.log( shortValue ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| export class RootPathResolver | ||||
| { | ||||
|   static readonly customResolve = new ElementAttribute( "resolve-root-path" ); | ||||
| 
 | ||||
|   static readonly resolvables:ResolvablePathAttribute[] =  | ||||
|   [ | ||||
|     new ResolvablePathAttribute( "link", "href" ), | ||||
|     new ResolvablePathAttribute( "script", "src" ), | ||||
|     new ResolvablePathAttribute( "a", "href" ), | ||||
|     new ResolvablePathAttribute( "img", "src" ), | ||||
|     new ResolvablePathAttribute( "audio", "src" ), | ||||
|     new ResolvablePathAttribute( "source", "src" ), | ||||
|     new ResolvablePathAttribute( "video", "poster" ), | ||||
|     new ResolvablePathAttribute( "video", "src" ), | ||||
|     new ResolvablePathAttribute( "*", "style" ), | ||||
|     new ResolvablePathAttribute( "image", "xlink:href" ), | ||||
|     new ResolvablePathAttribute( "*", "data-load-audio-element" ), | ||||
|     new ResolvablePathAttribute( "meta", "content" ), | ||||
|     new ResolvablePathAttribute( "*", "data-root-href" ), | ||||
|   ]  | ||||
| 
 | ||||
|   private static _rootToken = "::/"; | ||||
| 
 | ||||
|   static clearRootPath( rootPath:string ) | ||||
|   { | ||||
|     return rootPath.replace( /^\:\:\//, "" ); | ||||
|   } | ||||
| 
 | ||||
|   static isRootPath( path:string ) | ||||
|   { | ||||
|     return path.startsWith( RootPathResolver._rootToken ); | ||||
|   } | ||||
| 
 | ||||
|    | ||||
| 
 | ||||
|   static get rootToken() | ||||
|   { | ||||
|     return RootPathResolver._rootToken; | ||||
|   } | ||||
| 
 | ||||
|   getRootPath( relativeFilePath:string ) | ||||
|   { | ||||
|     let normalizedPath = relativeFilePath.replace( /\\/g, "/" ); | ||||
| 
 | ||||
|     if ( normalizedPath.startsWith( "/" ) ) | ||||
|     { | ||||
|       normalizedPath = normalizedPath.substring( 1 ); | ||||
|     } | ||||
| 
 | ||||
|     let numParentDirectories = normalizedPath.split( "/" ).length - 1; | ||||
|     let rootPath = ""; | ||||
| 
 | ||||
|     for ( let i = 0; i < numParentDirectories; i++ ) | ||||
|     { | ||||
|       rootPath += "../"; | ||||
|     } | ||||
|      | ||||
|     return rootPath; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   process( target:Element|Document, relativeFilePath:string ) | ||||
|   { | ||||
|     let rootPath = this.getRootPath( relativeFilePath );     | ||||
|    | ||||
|     RootPathResolver.resolvables.forEach( | ||||
|       ( resolvable )=> | ||||
|       { | ||||
|         let elements = resolvable.getFrom( target ); | ||||
| 
 | ||||
|         elements.forEach(  | ||||
|           ( e:Element ) =>  | ||||
|           resolvable.process( e, RootPathResolver.rootToken, rootPath )  | ||||
|         ); | ||||
|       } | ||||
|     ) | ||||
| 
 | ||||
|     RootPathResolver.customResolve.forAll(  | ||||
|       target,  | ||||
|       ( e:Element ) => | ||||
|       { | ||||
|         let attName = RootPathResolver.customResolve.from( e ); | ||||
|         let att = new ElementAttribute( attName, false ); | ||||
|         let path = att.from( e ); | ||||
| 
 | ||||
|         console.log( `Processing custom root path "${attName}" = "${path}"` ); | ||||
|         if ( path === null ) | ||||
|         { | ||||
|           console.log( "Root Path Resolve: No path found in ", attName); | ||||
|           return; | ||||
|         } | ||||
|         path = path.replace( RootPathResolver.rootToken, rootPath ); | ||||
|         att.to( e, path ); | ||||
|       }  | ||||
|     ) | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,121 @@ | |||
| 
 | ||||
| import { ElementAttribute } from "./ElementAttribute"; | ||||
| import { UserAgentInfo } from "./UserAgentInfo"; | ||||
| 
 | ||||
| export type Android = "android"; | ||||
| export type Tablet = "tablet"; | ||||
| export type iPhone  = "iphone"; | ||||
| export type iPad    = "ipad"; | ||||
| export type Mac     = "mac"; | ||||
| export type PC      = "pc"; | ||||
| export type TV = "tv"; | ||||
| export type Other   = "Other"; | ||||
| 
 | ||||
| // export type (\w+)\s*=\s*".+;
 | ||||
| // $1 |
 | ||||
| export type UserAgentDeviceType =  | ||||
| 
 | ||||
|   Android |  | ||||
|   Tablet | | ||||
|   iPhone |  | ||||
|   iPad |  | ||||
|   Mac |  | ||||
|   PC |  | ||||
|   TV | | ||||
|   Other | ||||
| ; | ||||
| 
 | ||||
| 
 | ||||
| export class UserAgentDeviceTypes | ||||
| { | ||||
|   // export type (\w+)\s*=\s*"(.+)".*;
 | ||||
|   // static readonly $1:$1 = "$2";
 | ||||
| 
 | ||||
|   static readonly iPhone:iPhone = "iphone"; | ||||
|   static readonly iPad:iPad = "ipad"; | ||||
|   static readonly Android:Android = "android"; | ||||
|   static readonly Tablet:Tablet = "tablet"; | ||||
|   static readonly Mac:Mac = "mac"; | ||||
|   static readonly PC:PC = "pc"; | ||||
|   static readonly TV:TV = "tv"; | ||||
|   static readonly Other:Other = "Other"; | ||||
| 
 | ||||
|   private static _deviceType:UserAgentDeviceType = null; | ||||
| 
 | ||||
|   static get current() | ||||
|   { | ||||
|     if ( UserAgentDeviceTypes._deviceType !== null ) | ||||
|     { | ||||
|       return UserAgentDeviceTypes._deviceType; | ||||
|     } | ||||
| 
 | ||||
|     UserAgentDeviceTypes._deviceType = UserAgentDeviceTypes._guess(); | ||||
| 
 | ||||
|     return UserAgentDeviceTypes._deviceType; | ||||
|   } | ||||
| 
 | ||||
|   static readonly attribute = new ElementAttribute( "device-type" ); | ||||
| 
 | ||||
|   static setOnBody() | ||||
|   { | ||||
|     UserAgentDeviceTypes.attribute.to( document.body, UserAgentDeviceTypes.current ); | ||||
|   } | ||||
|    | ||||
|   private static _guess():UserAgentDeviceType | ||||
|   { | ||||
|     if ( UserAgentInfo.isIPad ) | ||||
|     { | ||||
|       return UserAgentDeviceTypes.iPad; | ||||
|     } | ||||
| 
 | ||||
|     if ( UserAgentInfo.isIPhone ) | ||||
|     { | ||||
|       return UserAgentDeviceTypes.iPhone; | ||||
|     } | ||||
| 
 | ||||
|     if ( UserAgentInfo.isMac ) | ||||
|     { | ||||
|       if ( UserAgentInfo.isTV ) | ||||
|       { | ||||
|         return UserAgentDeviceTypes.TV; | ||||
|       } | ||||
| 
 | ||||
|       return UserAgentDeviceTypes.Mac; | ||||
|     } | ||||
| 
 | ||||
|     if ( UserAgentInfo.isAndroid ) | ||||
|     { | ||||
|       if ( UserAgentInfo.isTV ) | ||||
|       { | ||||
|         return UserAgentDeviceTypes.TV; | ||||
|       } | ||||
| 
 | ||||
|       if ( UserAgentInfo.isTablet ) | ||||
|       { | ||||
|         return UserAgentDeviceTypes.Tablet; | ||||
|       } | ||||
| 
 | ||||
|       return UserAgentDeviceTypes.Android; | ||||
|     } | ||||
| 
 | ||||
|     if ( UserAgentInfo.isTablet ) | ||||
|     { | ||||
|       return UserAgentDeviceTypes.Tablet; | ||||
|     } | ||||
| 
 | ||||
|     if ( UserAgentInfo.isTV ) | ||||
|     { | ||||
|       return UserAgentDeviceTypes.TV; | ||||
|     } | ||||
| 
 | ||||
|     if ( UserAgentInfo.isOther ) | ||||
|     { | ||||
|       return UserAgentDeviceTypes.Other; | ||||
|     } | ||||
| 
 | ||||
|     return UserAgentDeviceTypes.PC; | ||||
|      | ||||
|   }  | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  | @ -0,0 +1,50 @@ | |||
| export class UserAgentInfo | ||||
| {  | ||||
|   static is( regex:RegExp ) | ||||
|   { | ||||
|     return regex.test( navigator.userAgent ); | ||||
|   } | ||||
| 
 | ||||
|   static get isIPhone() | ||||
|   { | ||||
|     return UserAgentInfo.is( /iphone/i ); | ||||
|   } | ||||
| 
 | ||||
|   static get isIPad() | ||||
|   { | ||||
|     return  UserAgentInfo.is( /ipad/i ); | ||||
|   }   | ||||
| 
 | ||||
|   static get isMac() | ||||
|   { | ||||
|     return  UserAgentInfo.is( /macintosh/i ); | ||||
|   } | ||||
| 
 | ||||
|   static get isAndroid() | ||||
|   { | ||||
|     return UserAgentInfo.is( /android/i ); | ||||
|   } | ||||
| 
 | ||||
|   static get isTablet() | ||||
|   { | ||||
|     return UserAgentInfo.is( /tablet/i ); | ||||
|   } | ||||
| 
 | ||||
|   static get isTV() | ||||
|   { | ||||
|     let isTV = UserAgentInfo.is( /smart\-?tv/i ) || UserAgentInfo.is( /net\-?cast/i ) || | ||||
|                UserAgentInfo.is( /apple\s*tv/i ) || UserAgentInfo.is( /android\s*tv/i ) || | ||||
|                UserAgentInfo.is( /opera\s*tv/i ); | ||||
| 
 | ||||
|     return isTV; | ||||
|   } | ||||
|    | ||||
|   static get isOther() | ||||
|   { | ||||
|     let other = UserAgentInfo.is( /roku/i ) || UserAgentInfo.is( /tizen/i ) || | ||||
|                 UserAgentInfo.is( /netflix/i ) || UserAgentInfo.is( /crkey/i ); | ||||
|          | ||||
|     return other; | ||||
|   } | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,69 @@ | |||
| import { Range } from "./Range"; | ||||
| import { Vector2 } from "./Vector2"; | ||||
| 
 | ||||
| export class Box2 | ||||
| { | ||||
|   min:Vector2; | ||||
|   max:Vector2; | ||||
| 
 | ||||
|   constructor( min:Vector2 = null, max:Vector2 = null ) | ||||
|   { | ||||
|     this.min = min; | ||||
|     this.max = max; | ||||
|   } | ||||
| 
 | ||||
|    | ||||
| 
 | ||||
|   get size():Vector2 | ||||
|   { | ||||
|     return this.max.clone().sub( this.min ); | ||||
|   } | ||||
| 
 | ||||
|   get center():Vector2 | ||||
|   { | ||||
|     return this.max.clone().add( this.min ).multiply( 0.5 ); | ||||
|   } | ||||
| 
 | ||||
|   get rangeX():Range | ||||
|   { | ||||
|     return new Range( this.min.x, this.max.x ); | ||||
|   } | ||||
| 
 | ||||
|   get rangeY():Range | ||||
|   { | ||||
|     return new Range( this.min.y, this.max.y ); | ||||
|   } | ||||
| 
 | ||||
|   intersectsBox( box:Box2 ) | ||||
|   { | ||||
|     return this.rangeX.overlaps( box.rangeX ) && this.rangeY.overlaps( box.rangeY ); | ||||
|   } | ||||
| 
 | ||||
|   translate( offset:Vector2 ) | ||||
|   { | ||||
|     this.min.add( offset ); | ||||
|     this.max.add( offset ); | ||||
|   } | ||||
| 
 | ||||
|   static fromClientRect( clientRect:ClientRect|DOMRect ) | ||||
|   { | ||||
|     let min = new Vector2( clientRect.left, clientRect.top ); | ||||
|     let max = new Vector2( clientRect.right, clientRect.bottom ); | ||||
|      | ||||
|     return new Box2( min, max ); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   static toDomRect( box:Box2 ) | ||||
|   { | ||||
|     let size = box.size; | ||||
|     let domRect = new DOMRect( box.min.x, box.min.y, size.x, size.y ); | ||||
|     return domRect; | ||||
|   } | ||||
| 
 | ||||
|   static updatefromClientRect( box:Box2, clientRect:ClientRect|DOMRect ) | ||||
|   { | ||||
|     box.min.set( clientRect.left, clientRect.top ); | ||||
|     box.max.set( clientRect.right, clientRect.bottom ); | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,52 @@ | |||
| export class Vector2 | ||||
| { | ||||
|   x:number = 0; | ||||
|   y:number = 0; | ||||
|    | ||||
|   constructor( x:number = 0, y:number = 0 ) | ||||
|   { | ||||
|     this.x = x; | ||||
|     this.y = y; | ||||
|   } | ||||
|    | ||||
|   clone():Vector2 | ||||
|   { | ||||
|     return new Vector2( this.x, this.y ); | ||||
|   } | ||||
| 
 | ||||
|   set( x:number, y:number ) | ||||
|   { | ||||
|     this.x = x; | ||||
|     this.y = y; | ||||
|   } | ||||
| 
 | ||||
|   add( other:Vector2 ) | ||||
|   { | ||||
|     this.x += other.x; | ||||
|     this.y += other.y; | ||||
| 
 | ||||
|     return this; | ||||
|   } | ||||
| 
 | ||||
|   sub( other:Vector2 ) | ||||
|   { | ||||
|     this.x -= other.x; | ||||
|     this.y -= other.y; | ||||
| 
 | ||||
|     return this; | ||||
|   } | ||||
| 
 | ||||
|   multiply( value:number ) | ||||
|   { | ||||
|     this.x *= value; | ||||
|     this.y *= value; | ||||
| 
 | ||||
|     return this; | ||||
|   } | ||||
| 
 | ||||
|   get length()  | ||||
|   { | ||||
|     return Math.sqrt( this.x * this.x + this.y * this.y ); | ||||
|   } | ||||
| 
 | ||||
| } | ||||
|  | @ -834,7 +834,6 @@ export class RegExpUtility | |||
| 
 | ||||
|   static createRegExp( regexp:RegExp, matching:string, replacement:string ) | ||||
|   {      | ||||
|      | ||||
|     let source = regexp.source; | ||||
|     let flags = regexp.flags; | ||||
|     source = source.replace( matching, replacement ); | ||||
|  |  | |||
|  | @ -0,0 +1,14 @@ | |||
| export class MapList | ||||
| { | ||||
|   static add<K,V>( map:Map<K,V[]>, k:K, v:V ) | ||||
|   { | ||||
|     if ( ! map.has( k ) ) | ||||
|     { | ||||
|       map.set( k, [] ); | ||||
|     } | ||||
| 
 | ||||
|     map.get( k ).push( v ); | ||||
|   } | ||||
| 
 | ||||
|    | ||||
| } | ||||
|  | @ -83,7 +83,7 @@ export class Loader | |||
|         xhr.open( method, url, true ); | ||||
|         xhr.responseType = "json"; | ||||
| 
 | ||||
|         console.log( "xhr", url, method ); | ||||
|         // console.log( "xhr", url, method );
 | ||||
| 
 | ||||
|         xhr.onload=()=> | ||||
|         { | ||||
|  | @ -129,7 +129,7 @@ export class Loader | |||
| 
 | ||||
|         xhr.onload = () => | ||||
|         { | ||||
|           console.log( "load", url, xhr ); | ||||
|           // console.log( "load", url, xhr );
 | ||||
| 
 | ||||
|           if ( xhr.status !== 200 ) | ||||
|           { | ||||
|  |  | |||
|  | @ -2,7 +2,6 @@ | |||
| import { RegExpUtility } from "../../browser/text/RegExpUtitlity"; | ||||
| import { LogColors } from "./LogColors"; | ||||
| 
 | ||||
| let pr = ( window as any ).process; | ||||
| 
 | ||||
| export class RJLog | ||||
| { | ||||
|  | @ -100,7 +99,7 @@ export class RJLog | |||
| 
 | ||||
|   static error( ...params:any[] ) | ||||
|   { | ||||
|     if ( RJLog.logAlwaysLineInfo || typeof pr === "object" ) | ||||
|     if ( RJLog.logAlwaysLineInfo || typeof process === "object" ) | ||||
|     { | ||||
|       let lineInfo = RJLog.getLineInfo( RJLog.errorColor ); | ||||
|       console.log( "\n" + lineInfo ); | ||||
|  | @ -112,7 +111,7 @@ export class RJLog | |||
| 
 | ||||
|   static warn( ...params:any[] ) | ||||
|   { | ||||
|     if ( RJLog.logAlwaysLineInfo || typeof pr === "object" ) | ||||
|     if ( RJLog.logAlwaysLineInfo || typeof process === "object" ) | ||||
|     { | ||||
|       let lineInfo = RJLog.getLineInfo( RJLog.warnColor ); | ||||
|       console.log( "\n" + lineInfo ); | ||||
|  | @ -124,7 +123,7 @@ export class RJLog | |||
| 
 | ||||
|   static log( ...params:any[] ) | ||||
|   { | ||||
|     if ( RJLog.logAlwaysLineInfo || typeof pr === "object" ) | ||||
|     if ( RJLog.logAlwaysLineInfo || typeof process === "object" ) | ||||
|     { | ||||
|       let lineInfo = RJLog.getLineInfo(); | ||||
|       console.log( "\n" + lineInfo ); | ||||
|  |  | |||
|  | @ -1,10 +1,9 @@ | |||
| { | ||||
|   "compilerOptions":  | ||||
|   { | ||||
|     "lib": ["ES2020"], | ||||
|     "types": ["node"],  // Includes Node.js types | ||||
|   }, | ||||
| 
 | ||||
| 
 | ||||
|   "include": ["./*"] | ||||
|   "compilerOptions": { | ||||
|     "target": "ESNext", | ||||
|     "module": "CommonJS", | ||||
|     "lib": ["ESNext"], | ||||
|     "moduleResolution": "Node", | ||||
|     "types": ["node"] | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,111 @@ | |||
| 
 | ||||
| import * as fs from "fs"; | ||||
| import * as path from "path"; | ||||
| import { RJLog } from "../log/RJLog"; | ||||
| 
 | ||||
| export class PagesInfo | ||||
| { | ||||
|   pages:string[] = []; | ||||
| } | ||||
| 
 | ||||
| export class PHPPagesBuilder | ||||
| { | ||||
|   inputDir:string; | ||||
|   outputDir:string; | ||||
| 
 | ||||
|   constructor( source:string, build:string )  | ||||
|   { | ||||
|     this.inputDir = source;  | ||||
|     this.outputDir = build; | ||||
|   } | ||||
| 
 | ||||
|   filterFiles( fileName:string )  | ||||
|   { | ||||
|     if ( fileName.startsWith( "__" ) ) | ||||
|     { | ||||
|       return false; | ||||
|     } | ||||
|      | ||||
|     return fileName.endsWith( ".html" ); | ||||
|   } | ||||
| 
 | ||||
|   modify( content:string )  | ||||
|   { | ||||
|     return `<?php ?>\n${content}`; | ||||
|   } | ||||
| 
 | ||||
|   apply( compiler:any )  | ||||
|   { | ||||
|     compiler.hooks.afterCompile.tapAsync( "PHPPagesBuilder",  | ||||
|       ( compilation:any, callback:any ) =>  | ||||
|       { | ||||
|        | ||||
|         if ( fs.existsSync( this.inputDir ) )  | ||||
|         { | ||||
|           fs.readdirSync( this.inputDir ) | ||||
|             .filter( this.filterFiles ) | ||||
|             .forEach(  | ||||
|               ( file ) =>  | ||||
|               { | ||||
|                 let fullPath = path.join(this.inputDir, file); | ||||
|                 compilation.fileDependencies.add(fullPath); // Mark for watching
 | ||||
|               } | ||||
|             ); | ||||
|            | ||||
|            | ||||
|         } | ||||
| 
 | ||||
|         callback(); | ||||
| 
 | ||||
|       } | ||||
|     ); | ||||
| 
 | ||||
|     compiler.hooks.emit.tapAsync( "PHPPagesBuilder",  | ||||
|       ( compilation:any, callback:any ) =>  | ||||
|       { | ||||
|         fs.readdir( this.inputDir,  | ||||
|           ( err, files ) =>  | ||||
|           { | ||||
|             if ( err )  | ||||
|             {  | ||||
|               RJLog.log( "Error", this.inputDir ); | ||||
|               return callback( err );  | ||||
|             } | ||||
| 
 | ||||
|             let pages = new PagesInfo(); | ||||
|             files.filter( this.filterFiles ).forEach( ( file ) =>  | ||||
|               { | ||||
|                 let filePath = path.join( this.inputDir, file ); | ||||
|                 let content = fs.readFileSync( filePath, "utf-8" ); | ||||
|                 let modifiedContent = this.modify( content ); | ||||
|                 let phpFileName = file.replace( ".html", ".php" ); | ||||
|                 let outputFileName = path.join( this.outputDir, phpFileName ); | ||||
| 
 | ||||
|                 pages.pages.push( phpFileName ); | ||||
| 
 | ||||
|                 compilation.assets[ outputFileName ] =  | ||||
|                 { | ||||
|                   source: () => modifiedContent, | ||||
|                   size: () => modifiedContent.length, | ||||
|                 }; | ||||
|               } | ||||
|             ); | ||||
| 
 | ||||
|             let pagesPath = path.join( this.outputDir, "pages.json" ); | ||||
| 
 | ||||
|             let pagesJSON = JSON.stringify( pages, null, "  " ); | ||||
| 
 | ||||
|             compilation.assets[ pagesPath ] =  | ||||
|             { | ||||
|               source: () => pagesJSON, | ||||
|               size: () => pagesJSON.length, | ||||
|             }; | ||||
| 
 | ||||
|             callback(); | ||||
| 
 | ||||
|           }  | ||||
|         ); | ||||
|       } | ||||
|     ); | ||||
|   } | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	 Josef
						Josef