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 ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -833,8 +833,7 @@ export class RegExpUtility | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|   static createRegExp( regexp:RegExp, matching:string, replacement:string ) |   static createRegExp( regexp:RegExp, matching:string, replacement:string ) | ||||||
|   {  |   {      | ||||||
|      |  | ||||||
|     let source = regexp.source; |     let source = regexp.source; | ||||||
|     let flags = regexp.flags; |     let flags = regexp.flags; | ||||||
|     source = source.replace( matching, replacement ); |     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.open( method, url, true ); | ||||||
|         xhr.responseType = "json"; |         xhr.responseType = "json"; | ||||||
| 
 | 
 | ||||||
|         console.log( "xhr", url, method ); |         // console.log( "xhr", url, method );
 | ||||||
| 
 | 
 | ||||||
|         xhr.onload=()=> |         xhr.onload=()=> | ||||||
|         { |         { | ||||||
|  | @ -129,7 +129,7 @@ export class Loader | ||||||
| 
 | 
 | ||||||
|         xhr.onload = () => |         xhr.onload = () => | ||||||
|         { |         { | ||||||
|           console.log( "load", url, xhr ); |           // console.log( "load", url, xhr );
 | ||||||
| 
 | 
 | ||||||
|           if ( xhr.status !== 200 ) |           if ( xhr.status !== 200 ) | ||||||
|           { |           { | ||||||
|  |  | ||||||
|  | @ -2,7 +2,6 @@ | ||||||
| import { RegExpUtility } from "../../browser/text/RegExpUtitlity"; | import { RegExpUtility } from "../../browser/text/RegExpUtitlity"; | ||||||
| import { LogColors } from "./LogColors"; | import { LogColors } from "./LogColors"; | ||||||
| 
 | 
 | ||||||
| let pr = ( window as any ).process; |  | ||||||
| 
 | 
 | ||||||
| export class RJLog | export class RJLog | ||||||
| { | { | ||||||
|  | @ -100,7 +99,7 @@ export class RJLog | ||||||
| 
 | 
 | ||||||
|   static error( ...params:any[] ) |   static error( ...params:any[] ) | ||||||
|   { |   { | ||||||
|     if ( RJLog.logAlwaysLineInfo || typeof pr === "object" ) |     if ( RJLog.logAlwaysLineInfo || typeof process === "object" ) | ||||||
|     { |     { | ||||||
|       let lineInfo = RJLog.getLineInfo( RJLog.errorColor ); |       let lineInfo = RJLog.getLineInfo( RJLog.errorColor ); | ||||||
|       console.log( "\n" + lineInfo ); |       console.log( "\n" + lineInfo ); | ||||||
|  | @ -112,7 +111,7 @@ export class RJLog | ||||||
| 
 | 
 | ||||||
|   static warn( ...params:any[] ) |   static warn( ...params:any[] ) | ||||||
|   { |   { | ||||||
|     if ( RJLog.logAlwaysLineInfo || typeof pr === "object" ) |     if ( RJLog.logAlwaysLineInfo || typeof process === "object" ) | ||||||
|     { |     { | ||||||
|       let lineInfo = RJLog.getLineInfo( RJLog.warnColor ); |       let lineInfo = RJLog.getLineInfo( RJLog.warnColor ); | ||||||
|       console.log( "\n" + lineInfo ); |       console.log( "\n" + lineInfo ); | ||||||
|  | @ -124,7 +123,7 @@ export class RJLog | ||||||
| 
 | 
 | ||||||
|   static log( ...params:any[] ) |   static log( ...params:any[] ) | ||||||
|   { |   { | ||||||
|     if ( RJLog.logAlwaysLineInfo || typeof pr === "object" ) |     if ( RJLog.logAlwaysLineInfo || typeof process === "object" ) | ||||||
|     { |     { | ||||||
|       let lineInfo = RJLog.getLineInfo(); |       let lineInfo = RJLog.getLineInfo(); | ||||||
|       console.log( "\n" + lineInfo ); |       console.log( "\n" + lineInfo ); | ||||||
|  |  | ||||||
|  | @ -1,10 +1,9 @@ | ||||||
| { | { | ||||||
|   "compilerOptions":  |   "compilerOptions": { | ||||||
|   { |     "target": "ESNext", | ||||||
|     "lib": ["ES2020"], |     "module": "CommonJS", | ||||||
|     "types": ["node"],  // Includes Node.js types |     "lib": ["ESNext"], | ||||||
|   }, |     "moduleResolution": "Node", | ||||||
| 
 |     "types": ["node"] | ||||||
| 
 |   } | ||||||
|   "include": ["./*"] |  | ||||||
| } | } | ||||||
|  | @ -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