diff --git a/browser/app/App.ts b/browser/app/App.ts index fcf88b5..302b6a4 100644 --- a/browser/app/App.ts +++ b/browser/app/App.ts @@ -1,12 +1,18 @@ import { nextFrame } from "../animation/nextFrame"; import { OnAnimationFrame } from "../animation/OnAnimationFrame"; +import { sleep } from "../animation/sleep"; import { DateMath } from "../date/DateMath"; import { ActivityAnalyser } from "../dom/ActivityAnalyser"; +import { ClassFlag } from "../dom/ClassFlag"; +import { Fullscreen } from "../dom/Fullscreen"; import { Insight } from "../dom/Insight"; import { LandscapeScale } from "../dom/LandscapeScale"; +import { HashScroll } from "../dom/page-features/features/HashScroll"; import { PageHandler, PageHandlerMode } from "../dom/PageHandler"; import { UserAgentDeviceTypes } from "../dom/UserAgentDeviceType"; +import { EventSlot } from "../events/EventSlot"; import { TemplatesManager, TemplatesManagerMode } from "../templates/TemplatesManager"; +import { waitAround } from "../tools/sleep"; import { AppPathConverter } from "./AppPathConverter"; export class AppInitializerData @@ -108,7 +114,7 @@ export class App this.setLoaded(); } - setTimeout( ()=>{ this.setLoaded() }, 3000 ) + setTimeout( ()=>{ this.setLoaded(); App.appReadyFlag.set( document.body ) }, 3000 ) if ( ! data.allowWWWSubdomain ) { @@ -184,12 +190,21 @@ export class App this.activityAnalyser.start(); + Fullscreen.setFlagOnChange(); + await this.pageHandler.initialize(); - this.initializePage(); + await this.initializePage(); + HashScroll.applyOnDocument( this.onAnimationFrame ); + + await sleep( 300 ); + + App.appReadyFlag.set( document.body ); } + static appReadyFlag = new ClassFlag( "app-ready" ); + async initializePage() { while ( ! this.loaded ) @@ -198,12 +213,18 @@ export class App } //console.log( "App.initializePage" ); - this.insight.grabElements(); + // this.insight.grabElements(); this.templatesManager.processChildren( document.body ); + + + this.insight.grabElements(); + this.pageHandler.replaceLinks(); + this.pageHandler.resolveRootPaths( document ); + return Promise.resolve(); - } + } static updateAll() { diff --git a/browser/colors/ColorStop.ts b/browser/colors/ColorStop.ts new file mode 100644 index 0000000..0f390c3 --- /dev/null +++ b/browser/colors/ColorStop.ts @@ -0,0 +1,63 @@ +import { HTMLColor } from "./HTMLColor"; + + +export class ColorStop +{ + color:HTMLColor; + stop:number; + + static create( color:HTMLColor, stop:number ) + { + let cs = new ColorStop(); + cs.color = color; + cs.stop = stop; + return cs; + } + + getLengthCSS() + { + return `${this.color.toString()} ${this.stop}%`; + } + + getDegreesCSS() + { + return `${this.color.toString()} ${this.stop}deg`; + } + + + static createFrom( colors:HTMLColor[], scale = 1 ) + { + let colorStops:ColorStop[] = []; + + let normalizer = 100 / ( colors.length -1 ); + + for ( let i = 0; i < colors.length; i++ ) + { + let colorStop = new ColorStop(); + colorStop.color = colors[ i ]; + colorStop.stop = i * normalizer * scale; + + colorStops.push( colorStop ); + } + + return colorStops; + } + + static createEqualSpread( factor:number, ...colors:HTMLColor[] ) + { + let normalizer = factor / ( colors.length - 1 ); + + let colorStops:ColorStop[] = []; + + for ( let i = 0; i < colors.length; i++ ) + { + let colorStop = new ColorStop(); + colorStop.color = colors[ i ]; + colorStop.stop = i * normalizer; + + colorStops.push( colorStop ); + } + + return colorStops; + } +} \ No newline at end of file diff --git a/browser/colors/Gradient.ts b/browser/colors/Gradient.ts new file mode 100644 index 0000000..2cc4b8e --- /dev/null +++ b/browser/colors/Gradient.ts @@ -0,0 +1,101 @@ + +import { ColorStop } from "./ColorStop"; +import { HTMLColor } from "./HTMLColor"; + +export abstract class HTMLGradient +{ + colorStops:ColorStop[]; + + getColorStopsLengthCSS() + { + let colorStopValues = this.colorStops.map( c => c.getLengthCSS() ); + + return colorStopValues.join( ", " ); + } + + getColorStopsDegressCSS() + { + let colorStopValues = this.colorStops.map( c => c.getDegreesCSS() ); + + return colorStopValues.join( ", " ); + } + + applyToBackground( e:HTMLElement ) + { + e.style.background = this.toString(); + } + + abstract toString():string; +} + +export class LinearGradient extends HTMLGradient +{ + repeating = false; + position = "180deg"; + + get type() + { + return this.repeating ? "repeating-linear-gradient" : "linear-gradient"; + } + + toString() + { + return `${this.type}(${this.position}, ${this.getColorStopsLengthCSS()})` + ( this.repeating ? "" : " no-repeat" ); + } + + static createVertical( color:HTMLColor, h:number = 10, s:number = 5, l:number = 5 ) + { + let hslColor = color.toHSL().clone(); + + let colorA = hslColor.clone().shift( -h, -s, -l ); + let colorB = hslColor.clone().shift( h, s, l ); + + let lg = new LinearGradient(); + lg.colorStops = ColorStop.createEqualSpread( 100, colorA, colorB ); + return lg; + } + + static createHorizontal( left:HTMLColor, right:HTMLColor, normalizedState:number, normalizedWidth:number ) + { + let lg = new LinearGradient(); + lg.position = "90deg"; + lg.colorStops = + [ + ColorStop.create( left, normalizedState - normalizedWidth ), + ColorStop.create( right, normalizedState + normalizedWidth ), + ] + + return lg; + } + + static create( colors:HTMLColor[] ) + { + let lg = new LinearGradient(); + lg.colorStops = ColorStop.createFrom( colors ); + return lg; + } +} + +export class RadialGradient extends HTMLGradient +{ + repeating = false; + position = "circle at center"; + + get type() + { + return this.repeating ? "repeating-radial-gradient" : "radial-gradient"; + } + + toString() + { + return `${this.type}(${this.position}, ${this.getColorStopsLengthCSS()})`; + } +} + +export class ConicGradient extends HTMLGradient +{ + toString() + { + return `conic-gradient(${this.getColorStopsDegressCSS()})`; + } +} \ No newline at end of file diff --git a/browser/date/DateMath.ts b/browser/date/DateMath.ts index 9600a7e..2ddb783 100644 --- a/browser/date/DateMath.ts +++ b/browser/date/DateMath.ts @@ -1,8 +1,24 @@ +import { leadingZeros } from "../text/leadingZeros"; import { DateHelper } from "./DateHelper"; export class DateMath { - + static getMinutesAndSeconds( allSeconds:number ) + { + let minutes = Math.floor( allSeconds / 60 ); + let seconds =Math.round( allSeconds - minutes * 60 ); + + return { minutes, seconds }; + } + + static formatToMinutesAndSeconds( allSeconds:number ) + { + let values = this.getMinutesAndSeconds( allSeconds ); + let minutes = isNaN( values.minutes ) ? "--" : leadingZeros( values.minutes, 2 ); + let seconds = isNaN( values.seconds ) ? "--" : leadingZeros( values.seconds, 2 ); + return `${minutes}:${seconds}`; + } + static sort( dates:Date[] ) { dates.sort( DateMath.sortDate ); diff --git a/browser/dom/ClassFlag.ts b/browser/dom/ClassFlag.ts index 22c4ad6..3a84e92 100644 --- a/browser/dom/ClassFlag.ts +++ b/browser/dom/ClassFlag.ts @@ -50,6 +50,11 @@ export class ClassFlag { this._value = value; } + + static isIn( flag:string, element:Element ) + { + return new ClassFlag( flag ).in( element ); + } static fromEnum( value:string, prefix = "", appendix = "" ) { @@ -157,59 +162,27 @@ export class ClassFlag new ClassFlag( value ).set( element ); } + static addClass( element:Element, classString:string ) { - let classAttribute = element.getAttribute( "class" ) || ""; - let matcher = RegExpUtility.createWordMatcher( classString ); - - if ( matcher.test( classAttribute ) ) - { - return; - } - - classAttribute += " " + classString; - - ClassFlag.setNormalizedClassAttribute( element, classAttribute ); + element.classList.add( classString ); } static removeClass( element:Element, classString:string ) { - let matcher = RegExpUtility.createWordMatcher( classString ); - let classAttribute = element.getAttribute( "class" ) || ""; - - classAttribute = classAttribute.replace( matcher, "" ); - ClassFlag.setNormalizedClassAttribute( element, classAttribute ); + element.classList.remove( classString ); } static hasClass( element:Element, classString:string ) { - let matcher = RegExpUtility.createWordMatcher( classString ); - let classAttribute = element.getAttribute( "class" ) || ""; - - return matcher.test( classAttribute ); + return element.classList.contains( classString ); } static toggleClass( element:Element, className:string ) { - let flag = ! ClassFlag.hasClass( element, className ); - ClassFlag.setClass( element, className, flag ); + element.classList.toggle( className ); } - static setNormalizedClassAttribute( element:Element, classAttribute:string ) - { - classAttribute = classAttribute.replace( /\s+/g, " " ); - classAttribute = classAttribute.trim(); - - if ( "" === classAttribute ) - { - element.removeAttribute( "class" ); - } - else - { - element.setAttribute( "class", classAttribute ); - } - - } static setClass( element:Element, classString:string, enable:boolean ) { diff --git a/browser/dom/DOMEditor.ts b/browser/dom/DOMEditor.ts index 7c15365..566c721 100644 --- a/browser/dom/DOMEditor.ts +++ b/browser/dom/DOMEditor.ts @@ -1,3 +1,6 @@ +import { HTMLNodeTreeWalker } from "../graphs/HTMLNodeTreeWalker"; +import { TreeWalker } from "../graphs/TreeWalker"; + export class DOMEditor { static nodeListToArray( list:NodeList ) @@ -99,6 +102,43 @@ export class DOMEditor return clones; } + static reverseChildren( element:Element ) + { + Array.from( element.childNodes ).reverse().forEach( node => { + element.appendChild( node ); + }); + } + + static getChildrenWidth( parent:Element, callback:( e:Element )=>boolean ) + { + HTMLNodeTreeWalker.$.forAll + } + + static swapNodes( parent:Element, node1:Node, node2:Node ) + { + if ( node1 === node2 ) + { + return; + } + + let next1 = node1.nextSibling; + let next2 = node2.nextSibling; + + if ( next1 === node2 ) + { + parent.insertBefore( node2, node1 ); + } + else if ( next2 === node1 ) + { + parent.insertBefore( node1, node2 ); + } + else + { + parent.insertBefore( node1, next2 ); + parent.insertBefore( node2, next1 ); + } + } + static _cssCreationRegex:RegExp; static get cssCreationRegex() diff --git a/browser/dom/DOMHitTest.ts b/browser/dom/DOMHitTest.ts index a04917c..0248893 100644 --- a/browser/dom/DOMHitTest.ts +++ b/browser/dom/DOMHitTest.ts @@ -96,7 +96,7 @@ export class DOMHitTest static getRelativeWindowPosition( e:MouseEvent|TouchEvent ) { let position = this.getPointerPosition( e ); - let windowBox = new Box2( new Vector2( 0, 0 ), new Vector2( window.innerWidth, window.innerHeight ) ); + let windowBox = Box2.create( new Vector2( 0, 0 ), new Vector2( window.innerWidth, window.innerHeight ) ); position.sub( windowBox.min ); let size = windowBox.size; diff --git a/browser/dom/ElementAttribute.ts b/browser/dom/ElementAttribute.ts index 5856b1e..09aeb0e 100644 --- a/browser/dom/ElementAttribute.ts +++ b/browser/dom/ElementAttribute.ts @@ -23,6 +23,7 @@ export class ElementAttribute static readonly allowfullscreen = new ElementAttribute( "allowfullscreen", false ); static readonly controls = new ElementAttribute( "controls", false ); static readonly preload = new ElementAttribute( "preload", false ); + static readonly playsinline = new ElementAttribute( "playsinline", false ); static readonly selected = new ElementAttribute( "selected", false ); @@ -408,5 +409,28 @@ export class ElementAttribute this.to( target, this.from( source ) ); } + asNumberFrom( element:Element, alternative:number = null ) + { + if ( ! this.in( element ) ) + { + return alternative; + } + + let value = this.from( element ); + + if ( ! value ) + { + return alternative; + } + + let numberValue = parseFloat( value ); + + if ( isNaN( numberValue ) ) + { + return alternative; + } + + return numberValue; + } } \ No newline at end of file diff --git a/browser/dom/ElementType.ts b/browser/dom/ElementType.ts index 86571e9..7545b1c 100644 --- a/browser/dom/ElementType.ts +++ b/browser/dom/ElementType.ts @@ -95,7 +95,7 @@ export class ElementType return this._type; } - forAll( target:Element|Document, callback:(element:E)=>any) + forEach( target:Element|Document, callback:(element:E)=>any) { let elements = this.queryAll( target ); @@ -105,9 +105,9 @@ export class ElementType } } - forAllInDoc( callback:(element:E)=>any) + forEachInDoc( callback:(element:E)=>any) { - this.forAll( document, callback ); + this.forEach( document, callback ); } queryDoc():E @@ -120,6 +120,11 @@ export class ElementType return element.querySelector( this._rawType ) as E; } + selects( element:Element ) + { + return element.nodeName.toLowerCase() == this._rawType.toLowerCase(); + } + queryAll( element:Document|Element ):E[] { return DOMEditor.nodeListToArray( element.querySelectorAll( this._rawType ) ) as E[]; diff --git a/browser/dom/EventListeners.ts b/browser/dom/EventListeners.ts index 7d48c95..2f5554b 100644 --- a/browser/dom/EventListeners.ts +++ b/browser/dom/EventListeners.ts @@ -44,6 +44,9 @@ export type load = "load"; export type selectstart = "selectstart"; +export type hashchange = "hashchange"; +export type visibilitychange = "visibilitychange"; + // export type (\w+).+ // $1 | export type EventListenerType = @@ -86,7 +89,10 @@ export type EventListenerType = load | - selectstart + selectstart | + + hashchange | + visibilitychange ; @@ -140,6 +146,8 @@ export class Events static readonly load = "load"; static readonly selectstart:selectstart = "selectstart"; + static readonly hashchange:hashchange = "hashchange"; + static readonly visibilitychange:visibilitychange = "visibilitychange"; @@ -814,6 +822,33 @@ export class OnChange } } +export class OnHashChange +{ + static add( window:Window, callback:( e:Event )=>void, options:any = undefined ) + { + window.addEventListener( Events.hashchange, callback, options ); + } + + static remove( window:Window, callback:( e:Event )=>void ) + { + window.removeEventListener( Events.hashchange, callback ); + } +} + +export class OnVisibilityChange +{ + static add( document:Document, callback:( e:Event )=>void, options:any = undefined ) + { + window.addEventListener( Events.visibilitychange, callback, options ); + } + + static remove( document:Document, callback:( e:Event )=>void ) + { + window.removeEventListener( Events.visibilitychange, callback ); + } +} + + export class OnInput { static add( element:HTMLElement, callback:( e:InputEvent )=>void, options:any = undefined ) diff --git a/browser/dom/Fullscreen.ts b/browser/dom/Fullscreen.ts new file mode 100644 index 0000000..ddd4912 --- /dev/null +++ b/browser/dom/Fullscreen.ts @@ -0,0 +1,203 @@ +import { ClassFlag } from "./ClassFlag"; +import { ElementAttribute } from "./ElementAttribute"; +import { ElementType } from "./ElementType"; + +export class Fullscreen +{ + static readonly flag = new ClassFlag( "is-fullscreen" ); + + static _listensToChange = false; + + static setFlagOnChange() + { + if ( Fullscreen._listensToChange ) + { + return; + } + + Fullscreen._listensToChange = true; + + document.addEventListener( "fullscreenchange", + () => + { + Fullscreen.flag.setAs( document.body, Fullscreen.isFullscreen() ); + } + ); + } + + static isFullscreen(): boolean + { + let anyDocument:any = document; + + if ( anyDocument.fullscreenElement != null ) + { + return true; + } + + if ( anyDocument.webkitFullscreenElement != null ) + { + return true; + } + + if ( anyDocument.mozFullScreenElement != null ) + { + return true; + } + + if ( anyDocument.msFullscreenElement != null ) + { + return true; + } + + return false; + } + + protected static async enterFullscreen( element:HTMLElement ): Promise + { + let anyElement:any = element; + + + + if ( element.requestFullscreen != null ) + { + return element.requestFullscreen(); + } + + if ( anyElement.webkitRequestFullscreen != null ) + { + return anyElement.webkitRequestFullscreen(); + } + + if ( anyElement.mozRequestFullScreen != null ) + { + return anyElement.mozRequestFullScreen(); + } + + if ( anyElement.msRequestFullscreen != null ) + { + return anyElement.msRequestFullscreen(); + } + + return Promise.reject( new Error( "fullscreen api not supported" )); + } + + static async exitFullscreen(): Promise + { + let anyDocument: any = document; + + if ( anyDocument.exitFullscreen != null ) + { + return anyDocument.exitFullscreen(); + } + + if ( anyDocument.webkitExitFullscreen != null ) + { + return anyDocument.webkitExitFullscreen(); + } + + if ( anyDocument.mozCancelFullScreen != null ) + { + return anyDocument.mozCancelFullScreen(); + } + + if ( anyDocument.msExitFullscreen != null ) + { + return anyDocument.msExitFullscreen(); + } + + return Promise.reject( new Error( "fullscreen exit not supported" )); + } + + static async lockOrientation( orientation:string ): Promise + { + let anyScreen: any = ( screen as any ); + let api: any = screen.orientation != null + ? screen.orientation + : anyScreen.orientation != null + ? anyScreen.orientation + : anyScreen.msOrientation; + + if ( api == null || typeof api.lock !== "function" ) + { + return; + } + + try + { + await api.lock( orientation ); + } + catch + { + // fail quietly + } + } + + static unlockOrientation(): void + { + let anyScreen: any = ( screen as any ); + let api: any = screen.orientation != null + ? screen.orientation + : anyScreen.orientation != null + ? anyScreen.orientation + : anyScreen.msOrientation; + + if ( api == null || typeof api.unlock !== "function" ) + { + return; + } + + try + { + api.unlock(); + } + catch + { + // fail quietly + } + } + + static async toggleFullscreen( videoContainer:HTMLElement, handleOrientation:"auto"|"portrait"|"landscape"=null ): Promise + { + let videoElement = ElementType.video.query( videoContainer ); + ElementAttribute.playsinline.to( videoContainer ); + ( videoContainer as any ).playsInline = true; + + if ( Fullscreen.isFullscreen() ) + { + await Fullscreen.exitFullscreen(); + + if ( handleOrientation ) + { + Fullscreen.unlockOrientation(); + } + + return Promise.resolve(); + } + + let target:string = null; + + if ( handleOrientation !== null ) + { + if ( handleOrientation === "auto" ) + { + let vw = videoElement.videoWidth || videoElement.clientWidth || 16; + let vh = videoElement.videoHeight || videoElement.clientHeight || 9; + + target = vw >= vh ? "landscape" : "portrait"; + } + else + { + target = handleOrientation; + } + } + + await Fullscreen.enterFullscreen( videoContainer ); + + if ( target != null ) + { + await Fullscreen.lockOrientation( target ); + } + + return Promise.resolve(); + } +} \ No newline at end of file diff --git a/browser/dom/Insight.ts b/browser/dom/Insight.ts index 27759f2..432438f 100644 --- a/browser/dom/Insight.ts +++ b/browser/dom/Insight.ts @@ -26,7 +26,7 @@ export class InsightData isInsight = false; wasInsight = false; viewBox:Box2 = null; - elementBox:Box2 = new Box2(); + elementBox:Box2 = Box2.polar( 0 ); changeTime:number = 0; } @@ -142,7 +142,7 @@ export class Insight ); - //console.log( this._elements ); + console.log( this._elements ); } update() @@ -183,6 +183,8 @@ export class Insight let insightData = insightElement._insightData; let viewBox = insightData.viewBox; + + // console.log( viewBox ); if ( ! viewBox ) { @@ -192,6 +194,7 @@ export class Insight let elementBox = insightData.elementBox; + // console.log( elementBox, insightData, e ); Box2.updatefromClientRect( insightData.elementBox, e.getBoundingClientRect() ); @@ -219,13 +222,13 @@ export class Insight } let insight = viewBox.intersectsBox( elementBox ); - /* - console.log( - "Is in sight", insight, - "DOCUMENT VIEW BOX:", viewBox, - "ELEMENT BOX:", elementBox, - "ELEMENT", e - );*/ + + // console.log( + // "Is in sight", insight, + // "DOCUMENT VIEW BOX:", viewBox, + // "ELEMENT BOX:", elementBox, + // "ELEMENT", e + // ); if ( insight === insightData.isInsight ) { @@ -256,6 +259,7 @@ export class Insight } else { + let isAbove = elementBox.max.y < viewBox.min.y; Insight.in_sight.setAs( e, false ); Insight.above_sight.setAs( e, isAbove); @@ -365,7 +369,7 @@ export class Insight let startY = window.innerHeight * relativeStart / 100; let endY = window.innerHeight * relativeEnd / 100; - return new Box2( new Vector2( -10000, startY ), new Vector2( 10000, endY ) ); + return Box2.create( new Vector2( -10000, startY ), new Vector2( 10000, endY ) ); } diff --git a/browser/dom/LandscapeScale.ts b/browser/dom/LandscapeScale.ts index 2097366..2f5ac43 100644 --- a/browser/dom/LandscapeScale.ts +++ b/browser/dom/LandscapeScale.ts @@ -1,23 +1,26 @@ import { DOMOrientation, DOMOrientationType } from "../dom/DOMOrientation"; import { EventSlot } from "../events/EventSlot"; import { ElementType } from "./ElementType"; +import { UserAgentInfo } from "./UserAgentInfo"; 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; + _styleElement:Element; + _lastWidth:number; + _lastHeight:number; + _lastOrientation:DOMOrientationType; + _ultraWideRatio:number = 2.2; + _sw:number; + _sh:number; + _swidth:number; + _sheight:number; + _sx:number; + _sy:number; + _spx:number; + _fs:number; + get sw() { @@ -27,6 +30,7 @@ export class LandscapeScale onScreenSizeChange = new EventSlot(); onOrientationChange = new EventSlot(); + alerted = false; update() { @@ -58,7 +62,9 @@ export class LandscapeScale this._lastWidth = newWidth; this._lastHeight = newHeight; + console.log( "Updating size:", this._lastWidth, this._lastHeight ); this._sw = newWidth / 100; + this._sh = newHeight / 100; if ( ratio > this._ultraWideRatio ) { @@ -84,6 +90,29 @@ export class LandscapeScale this._swidth = this._sw * 100; this._sheight = this._sh * 100; + this._fs = this._sw / 10; + + let isChrome = window.innerWidth != window.visualViewport.width; + + let fsValue = this._fs + "px"; + let ffOffset = "0px"; + + console.log( "is chrome:", isChrome, window.innerWidth, window.visualViewport.width ); + + if ( ! isChrome ) + { + fsValue = `calc( ${this._fs}px * var(--firefoxScale ) * 0.01 )`; + ffOffset = "19px"; + } + + // if ( ! this.alerted ) + // { + // this.alerted = true; + // // alert( "is chrome:" + isChrome ); + // } + + + this._styleElement.innerHTML = ` @@ -98,6 +127,8 @@ export class LandscapeScale --spx:${this._spx}px; --spx-raw:${this._spx}; --spx-inv:${1/this._spx}px; + --fs:${fsValue}; + --firefoxOffset:${ffOffset}; } ` diff --git a/browser/dom/PageHandler.ts b/browser/dom/PageHandler.ts index bcbab05..ee5a85d 100644 --- a/browser/dom/PageHandler.ts +++ b/browser/dom/PageHandler.ts @@ -60,6 +60,18 @@ export class PageHandler private _rootPathResolver = new RootPathResolver(); get rootPathResolver(){ return this._rootPathResolver; } + private _preloadImagesFlag:boolean = true; + private _maxPreloadingDurationMS:number = 2000; + + private static _localHost = "http://localhost:8080"; + private static _localNetworkRegexPattern = /^(http:\/\/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}?:XXXX)/ + private static _localNetworkRegex = RegExpUtility.createRegExp( this._localNetworkRegexPattern, "XXXX", 8080 + "" ); + private _isHTMLMode:boolean = false; + private _fileLocation:string = ""; + private _isFileMode:boolean = false; + + private _pagesLoaded = false; + setPagesPath( path:string ) { this._pagesPath = path; @@ -115,15 +127,7 @@ export class PageHandler } - private _preloadImagesFlag:boolean = true; - private _maxPreloadingDurationMS:number = 2000; - - private static _localHost = "http://localhost:8080"; - private static _localNetworkRegexPattern = /^(http:\/\/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}?:XXXX)/ - private static _localNetworkRegex = RegExpUtility.createRegExp( this._localNetworkRegexPattern, "XXXX", 8080 + "" ); - private _isHTMLMode:boolean = false; - private _fileLocation:string = ""; - private _isFileMode:boolean = false; + static setLocalHostPort( port:number ) { @@ -165,8 +169,7 @@ export class PageHandler get localAdress() { - let adress = this.trimmedLocation; - + let adress = this.trimmedLocation; if ( adress.startsWith( PageHandler._localHost ) ) { @@ -191,7 +194,7 @@ export class PageHandler if ( adress.startsWith( PageHandler._localHost ) ) { - console.log( "Is starting with localhost" ); + // console.log( "Is starting with localhost" ); return true; } @@ -212,11 +215,8 @@ export class PageHandler { return this._isFileMode; } - - - private _pagesLoaded = false; async initialize() { @@ -330,6 +330,12 @@ export class PageHandler } let pageData = await this._getPageData( next ); + + if ( pageData == null ) + { + console.log( "No page data found:", page, next ); + } + this._currentLanguage = pageData.language; let elementType = new ElementType( this.pageRootTag ); @@ -599,18 +605,18 @@ export class PageHandler { let trimmedLocation = window.location.href; - let hash = /#.+$/.exec( trimmedLocation ); + let hash = /#.*$/.exec( trimmedLocation ); if ( hash ) { - trimmedLocation = trimmedLocation.replace( /#.+$/, "" ); + trimmedLocation = trimmedLocation.replace( /#.*$/, "" ); } - let search = /\?.+$/.exec( trimmedLocation ); + let search = /\?.*$/.exec( trimmedLocation ); if ( search ) { - trimmedLocation = trimmedLocation.replace( /\?.+$/, "" ); + trimmedLocation = trimmedLocation.replace( /\?.*$/, "" ); } return trimmedLocation; @@ -620,7 +626,7 @@ export class PageHandler { let trimmedLocation = window.location.href; - let hash = /#\[.+\]$/.exec( trimmedLocation ); + let hash = /#\[.*\]$/.exec( trimmedLocation ); if ( hash ) { @@ -649,7 +655,7 @@ export class PageHandler private _startUpdater() { - if ( 'scrollRestoration' in window.history) + if ( 'scrollRestoration' in window.history ) { window.history.scrollRestoration = 'manual'; } @@ -1095,6 +1101,11 @@ export class PageHandler return; } + if ( hrefValue.startsWith( "#" ) ) + { + return; + } + if ( PageHandler.isFunctionHandleLink( hrefValue ) ) { PageHandler.makeFunctionHandleLink( a ); @@ -1103,9 +1114,13 @@ export class PageHandler if ( AttributeValue.target_blank.in( a ) ) { + console.log( "Not processing blank link:", ElementAttribute.href.from( a ), a ); return; } - + else + { + console.log( "Processing blank link:", ElementAttribute.href.from( a ), a ); + } this.makePageHandleLink( a ); } diff --git a/browser/dom/UserAgentInfo.ts b/browser/dom/UserAgentInfo.ts index 236acc1..df3248e 100644 --- a/browser/dom/UserAgentInfo.ts +++ b/browser/dom/UserAgentInfo.ts @@ -30,6 +30,24 @@ export class UserAgentInfo return UserAgentInfo.is( /tablet/i ); } + static get isChrome() + { + let ua = navigator.userAgent; + return ua.toLowerCase().includes( "chrome" ) && ! ua.toLowerCase().includes( "edg" ); + } + + static get isFirefox() + { + let ua = navigator.userAgent; + return ua.toLowerCase().includes( "firefox" ); + } + + static get isSafari() + { + let ua = navigator.userAgent; + return ua.toLowerCase().includes( "safari" ); + } + static get isTV() { let isTV = UserAgentInfo.is( /smart\-?tv/i ) || UserAgentInfo.is( /net\-?cast/i ) || diff --git a/browser/dom/page-features/components/MediaGallery.ts b/browser/dom/page-features/components/MediaGallery.ts new file mode 100644 index 0000000..40adb34 --- /dev/null +++ b/browser/dom/page-features/components/MediaGallery.ts @@ -0,0 +1,258 @@ +import { TimePin } from "../../../animation/TimePin"; +import { LinearGradient } from "../../../colors/Gradient"; +import { HSLColor } from "../../../colors/HSLColor"; +import { DateHelper } from "../../../date/DateHelper"; +import { DateMath } from "../../../date/DateMath"; +import { TimestampMessage } from "../../../messages/TimestampMessage"; +import { RegExpUtility } from "../../../text/RegExpUtitlity"; +import { ClassFlag } from "../../ClassFlag"; +import { DOMHitTest } from "../../DOMHitTest"; +import { DOMOrientation } from "../../DOMOrientation"; +import { ElementAttribute } from "../../ElementAttribute"; +import { ElementType } from "../../ElementType"; +import { OnClick, OnMouseEnter, OnMouseMove, OnPause, OnPlay, OnTimeUpdate, OnTouchDrag, OnTouchEnd, OnTouchStart } from "../../EventListeners"; +import { Fullscreen } from "../../Fullscreen"; +import { Insight } from "../../Insight"; + +export class MediaGallery +{ + static videoElements:HTMLElement[]; + static activeVideoElement:HTMLElement; + static attachedGlobalListeners = false; + static insight:Insight; + + static readonly mediaVideo = new ElementType( "media-video" ); + static readonly mediaImage = new ElementType( "media-image" ); + static readonly activeFlag = new ClassFlag( "active-video" ); + static readonly itemIndex = new ElementAttribute( "item-index" ); + static readonly container = new ElementType ( "media-container" ); + static readonly showPlayButton = new ClassFlag( "show-play-button" ); + + static readonly fullScreenButton = new ElementType( "fullscreen-button" ); + static readonly progressIndicator = new ElementType( "progress-indicator" ); + + static applyOnDocument( insight:Insight ) + { + this.insight = insight; + this.attachGlobalListener(); + + MediaGallery.videoElements = MediaGallery.mediaVideo.queryAll( document.body ) as HTMLElement[]; + + MediaGallery.videoElements.forEach( v => this.applyOnVideoElement( v ) ); + } + + static attachGlobalListener() + { + if ( MediaGallery.attachedGlobalListeners ) + { + return; + } + + MediaGallery.attachedGlobalListeners = true; + + // OnMouseMove.add( + // document.body, + // ( e )=> + // { + + // if ( DOMOrientation.isPortrait ) + // { + // return; + // } + + // if ( ! MediaGallery.activeVideoElement ) + // { + // return; + // } + + // let isOver = DOMHitTest.isPointerOver( e, MediaGallery.activeVideoElement ); + + // if ( isOver ) + // { + // return; + // } + + // MediaGallery.updateActiveVideo( null ); + + // } + // ) + } + + static applyOnVideoElement( element:Element ) + { + let videoElement = ElementType.video.query( element ); + let fs = MediaGallery.fullScreenButton.query( element ); + let pi = MediaGallery.progressIndicator.query( element ) as HTMLElement; + + OnClick.add( pi, + ( e )=> + { + let relative = DOMHitTest.getNormalizedPointerPosition( e, pi ); + let position = Math.round( videoElement.duration * relative.x ); + videoElement.currentTime = position; + + e.preventDefault(); + e.stopImmediatePropagation(); + e.stopPropagation(); + } + ); + + let updateVideoTimeCode = ( text:string = null )=> + { + let textValue = text != null ? text : ( DateMath.formatToMinutesAndSeconds( videoElement.currentTime ) + " / " + + DateMath.formatToMinutesAndSeconds( videoElement.duration ) ); + + pi.innerHTML = `${textValue}`; + let percent = text != null ? 0 : (videoElement.currentTime / videoElement.duration) * 100; + let leftColor = new HSLColor( 0, 0, 100, 0.3 ); + let rightColor = new HSLColor( 0, 0, 100, 0.1 ); + + let linearBG = LinearGradient.createHorizontal( leftColor, rightColor, percent, 1 ); + + pi.style.background = linearBG + ", hsl(0,0%,0%,0.3)"; + }; + + OnTimeUpdate.add( videoElement, () => { updateVideoTimeCode(); } ); + + + OnClick.add( fs as HTMLElement, + ()=> + { + + Fullscreen.toggleFullscreen( element as HTMLElement, "auto" ); + + } + ); + + updateVideoTimeCode( "--:--" ); + + // OnMouseEnter.add( element as HTMLElement, + // ()=> + // { + // if ( DOMOrientation.isPortrait ) + // { + // return; + // } + + // MediaGallery.updateActiveVideo( element ); + // } + // ); + + MediaGallery.showPlayButton.setAs( element, true ); + + OnClick.add( element as HTMLElement, + ( me ) => + { + if ( DOMOrientation.isPortrait ) + { + return; + } + + if ( videoElement.paused ) + { + this.updateActiveVideo( element ); + MediaGallery.showPlayButton.setAs( element, false ); + } + else + { + videoElement.pause(); + MediaGallery.showPlayButton.setAs( element, true ); + } + } + ); + + OnTouchStart.add( element as HTMLElement, + ( touchStartEvent )=> + { + if ( touchStartEvent.target == pi || DOMOrientation.isLandscape ) + { + return; + } + + + console.log( touchStartEvent ); + let startPosition = DOMHitTest.getRelativeWindowPosition( touchStartEvent ); + let moved = false; + let treshold = 0.1; + + let onDrag = ( dragEvent:TouchEvent )=> + { + if ( moved ) + { + return; + } + + let dragPosition = DOMHitTest.getRelativeWindowPosition( dragEvent ); + moved = dragPosition.sub( startPosition ).length > treshold; + } + + let onEnd = ()=> + { + + OnTouchEnd.remove( element as HTMLElement, onEnd ); + OnTouchDrag.remove( element as HTMLElement, onDrag ); + + if ( moved ) + { + return; + } + + let videoElement = ElementType.video.query( element ); + + if ( videoElement.paused ) + { + this.updateActiveVideo( element ); + MediaGallery.showPlayButton.setAs( element, false ); + } + else + { + videoElement.pause(); + MediaGallery.showPlayButton.setAs( element, true ); + } + + } + + OnTouchEnd.add( element as HTMLElement, onEnd ); + OnTouchDrag.add( element as HTMLElement, onDrag ); + } + ) + + } + + static updateActiveVideo( e:Element ) + { + MediaGallery.activeVideoElement = e as HTMLElement; + + this.videoElements.forEach( + ( v )=> + { + let isActive = MediaGallery.activeVideoElement === v; + this.activeFlag.setAs( v, isActive ); + + let video = ElementType.video.query( v ); + + if ( ! video ) + { + return; + } + + if ( video.paused === ! isActive ) + { + return; + } + + if ( isActive ) + { + // video.currentTime = 0; + video.play(); + } + else + { + video.pause(); + MediaGallery.showPlayButton.setAs( v, true ); + } + + } + ) + } +} \ No newline at end of file diff --git a/browser/dom/page-features/components/MediaGallerySelector.ts b/browser/dom/page-features/components/MediaGallerySelector.ts new file mode 100644 index 0000000..c3f4250 --- /dev/null +++ b/browser/dom/page-features/components/MediaGallerySelector.ts @@ -0,0 +1,234 @@ +import { Vector2 } from "../../../geometry/Vector2"; +import { MathX } from "../../../math/MathX"; +import { ClassFlag } from "../../ClassFlag"; +import { DOMHitTest } from "../../DOMHitTest"; +import { DOMOrientation } from "../../DOMOrientation"; +import { ElementAttribute } from "../../ElementAttribute"; +import { ElementType } from "../../ElementType"; +import { OnClick, OnDrag } from "../../EventListeners"; +import { MediaGallery } from "./MediaGallery"; + +export class OffsetElement extends HTMLElement +{ + _position:Vector2; + + static applyOffset( e:OffsetElement ) + { + e.style.transform = `translate(${e._position.x}px, ${e._position.y}px)`; + } + +} + +export class MediaGallerySelector +{ + static type = new ElementType( "media-gallery-selector" ); + static target = new ElementAttribute( "target" ); + + static leftArrow = new ElementAttribute( "left-arrow" ); + static rightArrow = new ElementAttribute( "right-arrow" ); + static state = new ElementAttribute( "state" ); + + static active = new ClassFlag( "active" ); + static index = new ElementAttribute( "index" ); + + static applyOnDocument() + { + MediaGallerySelector.type.forEachInDoc( e => this.applyOnElement( e ) ); + } + + + static applyOnElement( e:Element ) + { + let target = MediaGallerySelector.target.queryValueInDoc( e ); + let items = MediaGallery.itemIndex.queryAll( target ); + + items.sort( + ( a:any, b:any )=> + { + let indexA = MediaGallery.itemIndex.asNumberFrom( a ); + let indexB = MediaGallery.itemIndex.asNumberFrom( b ); + return indexB - indexA ; + } + ); + + let offsetElement = target as OffsetElement; + offsetElement._position = new Vector2(); + + let startPosition = new Vector2(); + + let direction = 0; + + + OnDrag.add( offsetElement, + ( ev )=> + { + if ( DOMOrientation.isLandscape ) + { + return; + } + + let position = DOMHitTest.getPointerPosition( ev); + + if ( /start|down/.test( ev.type ) ) + { + startPosition = position.clone(); + direction = 0; + } + else if ( /end|up/.test( ev.type ) ) + { + if ( direction == 1 ) + { + let index = MediaGallerySelector.index.asNumberFrom( e ); + let indexOffset = ( offsetElement._position.x > 0 ? -1 : 1 ); + console.log( "offset:", offsetElement._position.x, "index offset:", indexOffset ); + index += indexOffset; + + let videos = MediaGallery.mediaVideo.queryAll( target ); + let images = MediaGallery.mediaImage.queryAll( target ); + let num = videos.length + images.length; + + index = MathX.clamp( index, 0, num - 1 ); + + MediaGallerySelector.index.to( e, index + "" ); + MediaGallerySelector.redraw( e ); + } + + offsetElement._position.set( 0, 0 ); + OffsetElement.applyOffset( offsetElement ); + + } + else + { + + let diff = position.clone().sub( startPosition ); + + if ( direction == 0 && diff.length > 5 ) + { + direction = Math.abs( diff.x ) > Math.abs( diff.y ) ? 1 : -1; + } + + if ( direction == -1 ) + { + offsetElement._position.set( 0, 0 ); + OffsetElement.applyOffset( offsetElement ); + return; + } + + diff.multiply( 0.2 ); + + offsetElement._position.set( diff.x, diff.y ); + offsetElement._position.clampPolar( window.innerWidth / 10, 0 ); + OffsetElement.applyOffset( offsetElement ); + } + + if ( direction > 0 ) + { + ev.stopImmediatePropagation(); + ev.stopPropagation(); + ev.preventDefault(); + } + } + ); + + MediaGallerySelector.leftArrow.forAll( e, + l => + { + OnClick.add( l as HTMLElement, + ()=> + { + console.log( "WHI L" ); + let index = MediaGallerySelector.index.asNumberFrom( e ); + + if ( index == 0 ) + { + return; + } + + index --; + + MediaGallerySelector.index.to( e, index + "" ); + MediaGallerySelector.redraw( e ); + } + ); + } + ); + + + MediaGallerySelector.rightArrow.forAll( e, + r => + { + OnClick.add( r as HTMLElement, + ()=> + { + console.log( "WHI R" ); + let index = MediaGallerySelector.index.asNumberFrom( e ); + + if ( index == ( items.length - 1 ) ) + { + return; + } + + index ++; + + MediaGallerySelector.index.to( e, index + "" ); + MediaGallerySelector.redraw( e ); + } + ); + } + ); + + MediaGallerySelector.state.forAll( e, + s => + { + OnClick.add( s as HTMLElement, + ()=> + { + + let stateIndex = MediaGallerySelector.index.asNumberFrom( s ); + console.log( "WHI INDEX", stateIndex ); + MediaGallerySelector.index.to( e, stateIndex + "" ); + MediaGallerySelector.redraw( e ); + } + ); + + } + ); + + MediaGallerySelector.redraw( e ); + } + + static redraw( e:Element ) + { + let index = MediaGallerySelector.index.asNumberFrom( e ); + let target = MediaGallerySelector.target.queryValueInDoc( e ); + let container = MediaGallery.container.query( target ) as HTMLElement; + + let offset = index * -103; + let value = `translate3d(${offset}vw,0vw,0vw)`; + + container.style.transform = value; + + console.log( "current-index", index ); + + let numElements = 3; + + MediaGallerySelector.leftArrow.forAll( e, + l => MediaGallerySelector.active.setAs( l, index > 0 ) + ); + + MediaGallerySelector.rightArrow.forAll( e, + r => MediaGallerySelector.active.setAs( r, index < ( numElements - 1 )) + ); + + MediaGallerySelector.state.forAll( e, + s => + { + let stateIndex = MediaGallerySelector.index.asNumberFrom( s ); + MediaGallerySelector.active.setAs( s, index == stateIndex ); + } + ); + + + + } +} \ No newline at end of file diff --git a/browser/dom/page-features/features/HashScroll.ts b/browser/dom/page-features/features/HashScroll.ts new file mode 100644 index 0000000..b4cd281 --- /dev/null +++ b/browser/dom/page-features/features/HashScroll.ts @@ -0,0 +1,67 @@ +import { OnAnimationFrame } from "../../../animation/OnAnimationFrame"; +import { OnChange, OnHashChange, OnVisibilityChange } from "../../EventListeners"; + +export class HashScroll +{ + static _hasScrolled = false; + static _hasListener = false; + + static applyOnDocument( onAnimationFrame:OnAnimationFrame ) + { + HashScroll._hasScrolled = false; + HashScroll.scroll(); + + + if ( this._hasListener ) + { + return; + } + + OnVisibilityChange.add( document, HashScroll._visibilityHandler ); + + HashScroll._hasListener = true; + + OnHashChange.add( window, ()=> HashScroll.scroll() ); + + + + } + + static scroll() + { + console.log( "Scrolling:", document.hidden ); + let hash = location.hash; + + if ( hash ) + { + let el = document.querySelector( hash ); + + if ( el ) + { + el.scrollIntoView( { behavior: "smooth", block: "start" } ); + } + } + + } + + static _doScrollOnce() + { + if ( this._hasScrolled ) + { + return; + } + + HashScroll.scroll(); + + HashScroll._hasScrolled = true; + document.removeEventListener( "visibilitychange", HashScroll._visibilityHandler ); + } + + static _visibilityHandler = () => + { + if ( ! document.hidden ) + { + HashScroll._doScrollOnce(); + } + } +} diff --git a/browser/dom/page-features/features/ToggleClass.ts b/browser/dom/page-features/features/ToggleClass.ts new file mode 100644 index 0000000..fcfdb54 --- /dev/null +++ b/browser/dom/page-features/features/ToggleClass.ts @@ -0,0 +1,64 @@ +import { ClassFlag } from "../../ClassFlag"; +import { ElementAttribute } from "../../ElementAttribute"; +import { OnClick } from "../../EventListeners"; + +export class ToggleClass +{ + static readonly attribute = new ElementAttribute( "-toggle--class" ); + static readonly assignedAttribute = new ElementAttribute( "-toggle--class--assigned" ); + static readonly blockDurationMS = 500; + + static applyOnDocument() + { + console.log( "APPLY" ); + ToggleClass.attribute.forEachInDoc( ( e ) => ToggleClass.applyOnElement( e ) ); + } + + static applyOnElement( element:Element ) + { + console.log( "APPLY", element ); + + if ( ToggleClass.assignedAttribute.in( element ) ) + { + return; + } + + ToggleClass.assignedAttribute.to( element, "0" ); + + let action = ()=> + { + let now = new Date().getTime(); + let time = parseFloat( ToggleClass.assignedAttribute.from( element ) ); + let elapsed = now - time; + + if ( elapsed < ToggleClass.blockDurationMS ) + { + return; + } + + ToggleClass.assignedAttribute.to( element, now + ""); + + let parameters = ToggleClass.attribute.from( element ).split( ";" ); + let selector = parameters[ 0 ]; + let className = parameters[ 1 ]; + + + let selectedElements = document.querySelectorAll( selector ); + + selectedElements.forEach( + ( selectedElement )=> + { + console.log( selector, selectedElement ); + ClassFlag.toggleClass( selectedElement, className ); + } + ) + + + } + + OnClick.add( element as HTMLElement, action ); + + + } + +} \ No newline at end of file diff --git a/browser/dom/page-features/features/VideoAutoPlay.ts b/browser/dom/page-features/features/VideoAutoPlay.ts new file mode 100644 index 0000000..09c5f24 --- /dev/null +++ b/browser/dom/page-features/features/VideoAutoPlay.ts @@ -0,0 +1,44 @@ +import { ElementAttribute } from "../../ElementAttribute"; +import { ElementType } from "../../ElementType"; +import { OnMouseDown, OnTouchStart } from "../../EventListeners"; + +export class VideoAutoPlay +{ + static applyOnDocument() + { + let startAutoPlayVideos = ()=> + { + ElementType.video.forEachInDoc( + async ( v )=> + { + if ( ! ElementAttribute.autoplay.in( v ) ) + { + return; + } + + if ( ! v.paused ) + { + return; + } + + try + { + await v.play(); + } + catch ( e ) + { + + } + + } + ); + + OnMouseDown.remove( document.body, startAutoPlayVideos ); + OnTouchStart.remove( document.body, startAutoPlayVideos ); + }; + + + OnMouseDown.add( document.body, startAutoPlayVideos ); + OnTouchStart.add( document.body, startAutoPlayVideos ); + } +} \ No newline at end of file diff --git a/browser/dom/page-features/features/VideoSpeedAttribute.ts b/browser/dom/page-features/features/VideoSpeedAttribute.ts new file mode 100644 index 0000000..056d412 --- /dev/null +++ b/browser/dom/page-features/features/VideoSpeedAttribute.ts @@ -0,0 +1,21 @@ +import { ElementAttribute } from "../../ElementAttribute"; +import { ElementType } from "../../ElementType"; +import { OnMouseDown, OnTouchStart } from "../../EventListeners"; + +export class VideoSpeedAttribute +{ + static readonly attribute = new ElementAttribute( "video-speed" ); + + static applyOnDocument() + { + VideoSpeedAttribute.attribute.forEachInDoc( + ( e )=> + { + let video = e as HTMLVideoElement; + + let speed = VideoSpeedAttribute.attribute.asNumberFrom( e ); + video.playbackRate = speed; + } + ); + } +} \ No newline at end of file diff --git a/browser/geometry/Box2.ts b/browser/geometry/Box2.ts index 578d684..0a25813 100644 --- a/browser/geometry/Box2.ts +++ b/browser/geometry/Box2.ts @@ -6,10 +6,18 @@ export class Box2 min:Vector2; max:Vector2; - constructor( min:Vector2 = null, max:Vector2 = null ) + static create( min:Vector2, max:Vector2 ):Box2 { - this.min = min; - this.max = max; + let box = new Box2(); + box.min = min; + box.max = max; + + return box; + } + + static polar( value:number ):Box2 + { + return this.create( new Vector2( -value, -value ), new Vector2( value, value ) ); } @@ -50,7 +58,7 @@ export class Box2 let min = new Vector2( clientRect.left, clientRect.top ); let max = new Vector2( clientRect.right, clientRect.bottom ); - return new Box2( min, max ); + return Box2.create( min, max ); } diff --git a/browser/geometry/Vector2.ts b/browser/geometry/Vector2.ts index 41e1977..605d8d3 100644 --- a/browser/geometry/Vector2.ts +++ b/browser/geometry/Vector2.ts @@ -1,3 +1,5 @@ +import { MathX } from "../math/MathX"; + export class Vector2 { x:number = 0; @@ -49,4 +51,10 @@ export class Vector2 return Math.sqrt( this.x * this.x + this.y * this.y ); } + clampPolar( x:number, y:number) + { + this.x = MathX.clamp( this.x, -x, x ); + this.y = MathX.clamp( this.y, -y, y ); + } + } \ No newline at end of file diff --git a/browser/graphs/TreeWalker.ts b/browser/graphs/TreeWalker.ts index ffe0982..74e5cca 100644 --- a/browser/graphs/TreeWalker.ts +++ b/browser/graphs/TreeWalker.ts @@ -93,6 +93,25 @@ export abstract class TreeWalker let num = this.numChildren( node ); return num <= 0?null:this.childAt( node, num-1 ); } + + getAll( node:N, selector:( n:N )=> boolean ) + { + let output:N[] = []; + + for ( let i = 0; i < this.numChildren( node ); i++ ) + { + let n = this.childAt( node, i ); + + if ( ! selector( n ) ) + { + continue; + } + + output.push( n ); + } + + return output; + } forAll( node:N, callback:( node:N ) => void ) { diff --git a/browser/templates/built-in/break--space.html b/browser/templates/built-in/break--space.html new file mode 100644 index 0000000..0e930dc --- /dev/null +++ b/browser/templates/built-in/break--space.html @@ -0,0 +1,10 @@ + + \ No newline at end of file diff --git a/browser/templates/built-in/column--break.html b/browser/templates/built-in/column--break.html new file mode 100644 index 0000000..25cb66f --- /dev/null +++ b/browser/templates/built-in/column--break.html @@ -0,0 +1,10 @@ + + \ No newline at end of file diff --git a/browser/templates/built-in/landscape--only.html b/browser/templates/built-in/landscape--only.html new file mode 100644 index 0000000..4dca5a1 --- /dev/null +++ b/browser/templates/built-in/landscape--only.html @@ -0,0 +1,13 @@ + + \ No newline at end of file diff --git a/browser/templates/built-in/portrait--only.html b/browser/templates/built-in/portrait--only.html new file mode 100644 index 0000000..89fcc83 --- /dev/null +++ b/browser/templates/built-in/portrait--only.html @@ -0,0 +1,12 @@ + diff --git a/browser/templates/styles-processor/StylesProcessorLexer.ts b/browser/templates/styles-processor/StylesProcessorLexer.ts index 4c093c6..3d3f813 100644 --- a/browser/templates/styles-processor/StylesProcessorLexer.ts +++ b/browser/templates/styles-processor/StylesProcessorLexer.ts @@ -25,7 +25,7 @@ export class StylesProcessorLexer extends Lexer static readonly ROOT_ELEMENT_REPLACER:ROOT_ELEMENT_REPLACER = "ROOT_ELEMENT_REPLACER"; static readonly ROOT_ELEMENT_REPLACER_MATCHER = - new LexerMatcher( StylesProcessorLexer.ROOT_ELEMENT_REPLACER, /\[_?\]/ ); + new LexerMatcher( StylesProcessorLexer.ROOT_ELEMENT_REPLACER, /#_/ ); constructor() { super(); diff --git a/browser/text/RegExpUtitlity.ts b/browser/text/RegExpUtitlity.ts index 9ab9008..ed5f19b 100644 --- a/browser/text/RegExpUtitlity.ts +++ b/browser/text/RegExpUtitlity.ts @@ -826,6 +826,12 @@ export class RegExpUtility return new RegExp( source ); } + static createClassMatcher( source:string ) + { + source = "(^|\\s)" + RegExpUtility.toRegexSource( source ) + "(\\s|$)"; + return new RegExp( source ); + } + static createMatcher( source:string ) { return new RegExp( RegExpUtility.toRegexSource( source ) ); diff --git a/node/webpack/TemplatesIndexBuilder.ts b/node/webpack/TemplatesIndexBuilder.ts index 03895f8..71407f5 100644 --- a/node/webpack/TemplatesIndexBuilder.ts +++ b/node/webpack/TemplatesIndexBuilder.ts @@ -12,12 +12,12 @@ export class TemplatesInfo export class TemplatesIndexBuilder { - inputDir:string; + inputDirectories:string[]; outputDir:string; - constructor( source:string, build:string ) + constructor( source:string[], build:string ) { - this.inputDir = source; + this.inputDirectories = source; this.outputDir = build; } @@ -37,26 +37,29 @@ export class TemplatesIndexBuilder compiler.hooks.afterCompile.tapAsync( "TemplatesIndexBuilder", async ( compilation:any, callback:any ) => { - await Files.forAllIn( this.inputDir, null, - async ( p:PathReference ) => - { - if ( await p.isDirectory() ) + for ( let inputDirectory of this.inputDirectories ) + { + await Files.forAllIn( inputDirectory, null, + async ( p:PathReference ) => { + if ( await p.isDirectory() ) + { + return Promise.resolve(); + } + + let fileName = p.fileName; + + if ( ! this.filterFiles( fileName ) ) + { + return Promise.resolve(); + } + + compilation.fileDependencies.add( p.absolutePath ); + return Promise.resolve(); } - - let fileName = p.fileName; - - if ( ! this.filterFiles( fileName ) ) - { - return Promise.resolve(); - } - - compilation.fileDependencies.add( p.absolutePath ); - - return Promise.resolve(); - } - ); + ); + } callback(); @@ -66,47 +69,52 @@ export class TemplatesIndexBuilder compiler.hooks.emit.tapAsync( "TemplatesIndexBuilder", async ( compilation:any, callback:any ) => { - let templates = new TemplatesInfo(); - let templatesPathReference = new PathReference( path.resolve( this.inputDir ) ); + let templates = new TemplatesInfo(); - // RJLog.log( templatesPathReference.absolutePath ); + for ( let inputDirectory of this.inputDirectories ) + { + let templatesPathReference = new PathReference( path.resolve( inputDirectory ) ); - await Files.forAllIn( templatesPathReference.absolutePath, null, - async ( p:PathReference ) => - { - if ( await p.isDirectory() ) + // RJLog.log( templatesPathReference.absolutePath ); + + await Files.forAllIn( templatesPathReference.absolutePath, null, + async ( p:PathReference ) => { + if ( await p.isDirectory() ) + { + return Promise.resolve(); + } + + let fileName = p.fileName; + + if ( ! this.filterFiles( fileName ) ) + { + return Promise.resolve(); + } + + let absoluteFilePath = p.absolutePath; + let inputRelativePath = templatesPathReference.createRelativeFromAbsolute( absoluteFilePath, true ); + + // RJLog.log( "Abs to Rel", absoluteFilePath, inputRelativePath.relativePath ); + let outputFileName = path.join( this.outputDir, inputRelativePath.relativePath ); + + templates.templates.push( inputRelativePath.relativePath ); + + let content = fs.readFileSync( p.absolutePath, "utf-8" ); + + // RJLog.log( "Adding", p.absolutePath, outputFileName ); + compilation.assets[ outputFileName ] = + { + source: () => content, + size: () => content.length, + }; + + return Promise.resolve(); } - - let fileName = p.fileName; - - if ( ! this.filterFiles( fileName ) ) - { - return Promise.resolve(); - } - - let absoluteFilePath = p.absolutePath; - let inputRelativePath = templatesPathReference.createRelativeFromAbsolute( absoluteFilePath, true ); - - // RJLog.log( "Abs to Rel", absoluteFilePath, inputRelativePath.relativePath ); - let outputFileName = path.join( this.outputDir, inputRelativePath.relativePath ); - - templates.templates.push( inputRelativePath.relativePath ); - - let content = fs.readFileSync( p.absolutePath, "utf-8" ); - - // RJLog.log( "Adding", p.absolutePath, outputFileName ); - compilation.assets[ outputFileName ] = - { - source: () => content, - size: () => content.length, - }; - - - return Promise.resolve(); - } - ); + ); + + } let templatesPath = path.join( this.outputDir, "index.json" );