Website Update 2025
This commit is contained in:
parent
f8edbdd581
commit
185e004839
|
@ -1,12 +1,18 @@
|
||||||
import { nextFrame } from "../animation/nextFrame";
|
import { nextFrame } from "../animation/nextFrame";
|
||||||
import { OnAnimationFrame } from "../animation/OnAnimationFrame";
|
import { OnAnimationFrame } from "../animation/OnAnimationFrame";
|
||||||
|
import { sleep } from "../animation/sleep";
|
||||||
import { DateMath } from "../date/DateMath";
|
import { DateMath } from "../date/DateMath";
|
||||||
import { ActivityAnalyser } from "../dom/ActivityAnalyser";
|
import { ActivityAnalyser } from "../dom/ActivityAnalyser";
|
||||||
|
import { ClassFlag } from "../dom/ClassFlag";
|
||||||
|
import { Fullscreen } from "../dom/Fullscreen";
|
||||||
import { Insight } from "../dom/Insight";
|
import { Insight } from "../dom/Insight";
|
||||||
import { LandscapeScale } from "../dom/LandscapeScale";
|
import { LandscapeScale } from "../dom/LandscapeScale";
|
||||||
|
import { HashScroll } from "../dom/page-features/features/HashScroll";
|
||||||
import { PageHandler, PageHandlerMode } from "../dom/PageHandler";
|
import { PageHandler, PageHandlerMode } from "../dom/PageHandler";
|
||||||
import { UserAgentDeviceTypes } from "../dom/UserAgentDeviceType";
|
import { UserAgentDeviceTypes } from "../dom/UserAgentDeviceType";
|
||||||
|
import { EventSlot } from "../events/EventSlot";
|
||||||
import { TemplatesManager, TemplatesManagerMode } from "../templates/TemplatesManager";
|
import { TemplatesManager, TemplatesManagerMode } from "../templates/TemplatesManager";
|
||||||
|
import { waitAround } from "../tools/sleep";
|
||||||
import { AppPathConverter } from "./AppPathConverter";
|
import { AppPathConverter } from "./AppPathConverter";
|
||||||
|
|
||||||
export class AppInitializerData
|
export class AppInitializerData
|
||||||
|
@ -108,7 +114,7 @@ export class App
|
||||||
this.setLoaded();
|
this.setLoaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout( ()=>{ this.setLoaded() }, 3000 )
|
setTimeout( ()=>{ this.setLoaded(); App.appReadyFlag.set( document.body ) }, 3000 )
|
||||||
|
|
||||||
if ( ! data.allowWWWSubdomain )
|
if ( ! data.allowWWWSubdomain )
|
||||||
{
|
{
|
||||||
|
@ -184,12 +190,21 @@ export class App
|
||||||
|
|
||||||
this.activityAnalyser.start();
|
this.activityAnalyser.start();
|
||||||
|
|
||||||
|
Fullscreen.setFlagOnChange();
|
||||||
|
|
||||||
await this.pageHandler.initialize();
|
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()
|
async initializePage()
|
||||||
{
|
{
|
||||||
while ( ! this.loaded )
|
while ( ! this.loaded )
|
||||||
|
@ -198,12 +213,18 @@ export class App
|
||||||
}
|
}
|
||||||
|
|
||||||
//console.log( "App.initializePage" );
|
//console.log( "App.initializePage" );
|
||||||
this.insight.grabElements();
|
// this.insight.grabElements();
|
||||||
this.templatesManager.processChildren( document.body );
|
this.templatesManager.processChildren( document.body );
|
||||||
|
|
||||||
|
|
||||||
|
this.insight.grabElements();
|
||||||
|
|
||||||
this.pageHandler.replaceLinks();
|
this.pageHandler.replaceLinks();
|
||||||
|
this.pageHandler.resolveRootPaths( document );
|
||||||
|
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
static updateAll()
|
static updateAll()
|
||||||
{
|
{
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()})`;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,24 @@
|
||||||
|
import { leadingZeros } from "../text/leadingZeros";
|
||||||
import { DateHelper } from "./DateHelper";
|
import { DateHelper } from "./DateHelper";
|
||||||
|
|
||||||
export class DateMath
|
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[] )
|
static sort( dates:Date[] )
|
||||||
{
|
{
|
||||||
dates.sort( DateMath.sortDate );
|
dates.sort( DateMath.sortDate );
|
||||||
|
|
|
@ -50,6 +50,11 @@ export class ClassFlag
|
||||||
{
|
{
|
||||||
this._value = value;
|
this._value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static isIn( flag:string, element:Element )
|
||||||
|
{
|
||||||
|
return new ClassFlag( flag ).in( element );
|
||||||
|
}
|
||||||
|
|
||||||
static fromEnum( value:string, prefix = "", appendix = "" )
|
static fromEnum( value:string, prefix = "", appendix = "" )
|
||||||
{
|
{
|
||||||
|
@ -157,59 +162,27 @@ export class ClassFlag
|
||||||
new ClassFlag( value ).set( element );
|
new ClassFlag( value ).set( element );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static addClass( element:Element, classString:string )
|
static addClass( element:Element, classString:string )
|
||||||
{
|
{
|
||||||
let classAttribute = element.getAttribute( "class" ) || "";
|
element.classList.add( classString );
|
||||||
let matcher = RegExpUtility.createWordMatcher( classString );
|
|
||||||
|
|
||||||
if ( matcher.test( classAttribute ) )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
classAttribute += " " + classString;
|
|
||||||
|
|
||||||
ClassFlag.setNormalizedClassAttribute( element, classAttribute );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static removeClass( element:Element, classString:string )
|
static removeClass( element:Element, classString:string )
|
||||||
{
|
{
|
||||||
let matcher = RegExpUtility.createWordMatcher( classString );
|
element.classList.remove( classString );
|
||||||
let classAttribute = element.getAttribute( "class" ) || "";
|
|
||||||
|
|
||||||
classAttribute = classAttribute.replace( matcher, "" );
|
|
||||||
ClassFlag.setNormalizedClassAttribute( element, classAttribute );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static hasClass( element:Element, classString:string )
|
static hasClass( element:Element, classString:string )
|
||||||
{
|
{
|
||||||
let matcher = RegExpUtility.createWordMatcher( classString );
|
return element.classList.contains( classString );
|
||||||
let classAttribute = element.getAttribute( "class" ) || "";
|
|
||||||
|
|
||||||
return matcher.test( classAttribute );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static toggleClass( element:Element, className:string )
|
static toggleClass( element:Element, className:string )
|
||||||
{
|
{
|
||||||
let flag = ! ClassFlag.hasClass( element, className );
|
element.classList.toggle( className );
|
||||||
ClassFlag.setClass( element, className, flag );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 )
|
static setClass( element:Element, classString:string, enable:boolean )
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
import { HTMLNodeTreeWalker } from "../graphs/HTMLNodeTreeWalker";
|
||||||
|
import { TreeWalker } from "../graphs/TreeWalker";
|
||||||
|
|
||||||
export class DOMEditor
|
export class DOMEditor
|
||||||
{
|
{
|
||||||
static nodeListToArray( list:NodeList )
|
static nodeListToArray( list:NodeList )
|
||||||
|
@ -99,6 +102,43 @@ export class DOMEditor
|
||||||
return clones;
|
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 _cssCreationRegex:RegExp;
|
||||||
|
|
||||||
static get cssCreationRegex()
|
static get cssCreationRegex()
|
||||||
|
|
|
@ -96,7 +96,7 @@ export class DOMHitTest
|
||||||
static getRelativeWindowPosition( e:MouseEvent|TouchEvent )
|
static getRelativeWindowPosition( e:MouseEvent|TouchEvent )
|
||||||
{
|
{
|
||||||
let position = this.getPointerPosition( e );
|
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 );
|
position.sub( windowBox.min );
|
||||||
let size = windowBox.size;
|
let size = windowBox.size;
|
||||||
|
|
|
@ -23,6 +23,7 @@ export class ElementAttribute
|
||||||
static readonly allowfullscreen = new ElementAttribute( "allowfullscreen", false );
|
static readonly allowfullscreen = new ElementAttribute( "allowfullscreen", false );
|
||||||
static readonly controls = new ElementAttribute( "controls", false );
|
static readonly controls = new ElementAttribute( "controls", false );
|
||||||
static readonly preload = new ElementAttribute( "preload", false );
|
static readonly preload = new ElementAttribute( "preload", false );
|
||||||
|
static readonly playsinline = new ElementAttribute( "playsinline", false );
|
||||||
|
|
||||||
static readonly selected = new ElementAttribute( "selected", false );
|
static readonly selected = new ElementAttribute( "selected", false );
|
||||||
|
|
||||||
|
@ -408,5 +409,28 @@ export class ElementAttribute
|
||||||
this.to( target, this.from( source ) );
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -95,7 +95,7 @@ export class ElementType<E extends Element>
|
||||||
return this._type;
|
return this._type;
|
||||||
}
|
}
|
||||||
|
|
||||||
forAll( target:Element|Document, callback:(element:E)=>any)
|
forEach( target:Element|Document, callback:(element:E)=>any)
|
||||||
{
|
{
|
||||||
let elements = this.queryAll( target );
|
let elements = this.queryAll( target );
|
||||||
|
|
||||||
|
@ -105,9 +105,9 @@ export class ElementType<E extends Element>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
forAllInDoc( callback:(element:E)=>any)
|
forEachInDoc( callback:(element:E)=>any)
|
||||||
{
|
{
|
||||||
this.forAll( document, callback );
|
this.forEach( document, callback );
|
||||||
}
|
}
|
||||||
|
|
||||||
queryDoc():E
|
queryDoc():E
|
||||||
|
@ -120,6 +120,11 @@ export class ElementType<E extends Element>
|
||||||
return element.querySelector( this._rawType ) as E;
|
return element.querySelector( this._rawType ) as E;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
selects( element:Element )
|
||||||
|
{
|
||||||
|
return element.nodeName.toLowerCase() == this._rawType.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
queryAll( element:Document|Element ):E[]
|
queryAll( element:Document|Element ):E[]
|
||||||
{
|
{
|
||||||
return DOMEditor.nodeListToArray( element.querySelectorAll( this._rawType ) ) as E[];
|
return DOMEditor.nodeListToArray( element.querySelectorAll( this._rawType ) ) as E[];
|
||||||
|
|
|
@ -44,6 +44,9 @@ export type load = "load";
|
||||||
|
|
||||||
export type selectstart = "selectstart";
|
export type selectstart = "selectstart";
|
||||||
|
|
||||||
|
export type hashchange = "hashchange";
|
||||||
|
export type visibilitychange = "visibilitychange";
|
||||||
|
|
||||||
// export type (\w+).+
|
// export type (\w+).+
|
||||||
// $1 |
|
// $1 |
|
||||||
export type EventListenerType =
|
export type EventListenerType =
|
||||||
|
@ -86,7 +89,10 @@ export type EventListenerType =
|
||||||
|
|
||||||
load |
|
load |
|
||||||
|
|
||||||
selectstart
|
selectstart |
|
||||||
|
|
||||||
|
hashchange |
|
||||||
|
visibilitychange
|
||||||
|
|
||||||
;
|
;
|
||||||
|
|
||||||
|
@ -140,6 +146,8 @@ export class Events
|
||||||
static readonly load = "load";
|
static readonly load = "load";
|
||||||
|
|
||||||
static readonly selectstart:selectstart = "selectstart";
|
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
|
export class OnInput
|
||||||
{
|
{
|
||||||
static add( element:HTMLElement, callback:( e:InputEvent )=>void, options:any = undefined )
|
static add( element:HTMLElement, callback:( e:InputEvent )=>void, options:any = undefined )
|
||||||
|
|
|
@ -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<void>
|
||||||
|
{
|
||||||
|
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<void>
|
||||||
|
{
|
||||||
|
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<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.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<void>
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,7 +26,7 @@ export class InsightData
|
||||||
isInsight = false;
|
isInsight = false;
|
||||||
wasInsight = false;
|
wasInsight = false;
|
||||||
viewBox:Box2 = null;
|
viewBox:Box2 = null;
|
||||||
elementBox:Box2 = new Box2();
|
elementBox:Box2 = Box2.polar( 0 );
|
||||||
changeTime:number = 0;
|
changeTime:number = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,7 +142,7 @@ export class Insight
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
//console.log( this._elements );
|
console.log( this._elements );
|
||||||
}
|
}
|
||||||
|
|
||||||
update()
|
update()
|
||||||
|
@ -183,6 +183,8 @@ export class Insight
|
||||||
let insightData = insightElement._insightData;
|
let insightData = insightElement._insightData;
|
||||||
|
|
||||||
let viewBox = insightData.viewBox;
|
let viewBox = insightData.viewBox;
|
||||||
|
|
||||||
|
// console.log( viewBox );
|
||||||
|
|
||||||
if ( ! viewBox )
|
if ( ! viewBox )
|
||||||
{
|
{
|
||||||
|
@ -192,6 +194,7 @@ export class Insight
|
||||||
|
|
||||||
let elementBox = insightData.elementBox;
|
let elementBox = insightData.elementBox;
|
||||||
|
|
||||||
|
// console.log( elementBox, insightData, e );
|
||||||
|
|
||||||
Box2.updatefromClientRect( insightData.elementBox, e.getBoundingClientRect() );
|
Box2.updatefromClientRect( insightData.elementBox, e.getBoundingClientRect() );
|
||||||
|
|
||||||
|
@ -219,13 +222,13 @@ export class Insight
|
||||||
}
|
}
|
||||||
|
|
||||||
let insight = viewBox.intersectsBox( elementBox );
|
let insight = viewBox.intersectsBox( elementBox );
|
||||||
/*
|
|
||||||
console.log(
|
// console.log(
|
||||||
"Is in sight", insight,
|
// "Is in sight", insight,
|
||||||
"DOCUMENT VIEW BOX:", viewBox,
|
// "DOCUMENT VIEW BOX:", viewBox,
|
||||||
"ELEMENT BOX:", elementBox,
|
// "ELEMENT BOX:", elementBox,
|
||||||
"ELEMENT", e
|
// "ELEMENT", e
|
||||||
);*/
|
// );
|
||||||
|
|
||||||
if ( insight === insightData.isInsight )
|
if ( insight === insightData.isInsight )
|
||||||
{
|
{
|
||||||
|
@ -256,6 +259,7 @@ export class Insight
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
||||||
let isAbove = elementBox.max.y < viewBox.min.y;
|
let isAbove = elementBox.max.y < viewBox.min.y;
|
||||||
Insight.in_sight.setAs( e, false );
|
Insight.in_sight.setAs( e, false );
|
||||||
Insight.above_sight.setAs( e, isAbove);
|
Insight.above_sight.setAs( e, isAbove);
|
||||||
|
@ -365,7 +369,7 @@ export class Insight
|
||||||
let startY = window.innerHeight * relativeStart / 100;
|
let startY = window.innerHeight * relativeStart / 100;
|
||||||
let endY = window.innerHeight * relativeEnd / 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 ) );
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,26 @@
|
||||||
import { DOMOrientation, DOMOrientationType } from "../dom/DOMOrientation";
|
import { DOMOrientation, DOMOrientationType } from "../dom/DOMOrientation";
|
||||||
import { EventSlot } from "../events/EventSlot";
|
import { EventSlot } from "../events/EventSlot";
|
||||||
import { ElementType } from "./ElementType";
|
import { ElementType } from "./ElementType";
|
||||||
|
import { UserAgentInfo } from "./UserAgentInfo";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export class LandscapeScale
|
export class LandscapeScale
|
||||||
{
|
{
|
||||||
private _styleElement:Element;
|
_styleElement:Element;
|
||||||
private _lastWidth:number;
|
_lastWidth:number;
|
||||||
private _lastHeight:number;
|
_lastHeight:number;
|
||||||
private _lastOrientation:DOMOrientationType;
|
_lastOrientation:DOMOrientationType;
|
||||||
private _ultraWideRatio:number = 2.2;
|
_ultraWideRatio:number = 2.2;
|
||||||
private _sw:number;
|
_sw:number;
|
||||||
private _sh:number;
|
_sh:number;
|
||||||
private _swidth:number;
|
_swidth:number;
|
||||||
private _sheight:number;
|
_sheight:number;
|
||||||
private _sx:number;
|
_sx:number;
|
||||||
private _sy:number;
|
_sy:number;
|
||||||
private _spx:number;
|
_spx:number;
|
||||||
|
_fs:number;
|
||||||
|
|
||||||
|
|
||||||
get sw()
|
get sw()
|
||||||
{
|
{
|
||||||
|
@ -27,6 +30,7 @@ export class LandscapeScale
|
||||||
onScreenSizeChange = new EventSlot<LandscapeScale>();
|
onScreenSizeChange = new EventSlot<LandscapeScale>();
|
||||||
onOrientationChange = new EventSlot<LandscapeScale>();
|
onOrientationChange = new EventSlot<LandscapeScale>();
|
||||||
|
|
||||||
|
alerted = false;
|
||||||
|
|
||||||
update()
|
update()
|
||||||
{
|
{
|
||||||
|
@ -58,7 +62,9 @@ export class LandscapeScale
|
||||||
this._lastWidth = newWidth;
|
this._lastWidth = newWidth;
|
||||||
this._lastHeight = newHeight;
|
this._lastHeight = newHeight;
|
||||||
|
|
||||||
|
console.log( "Updating size:", this._lastWidth, this._lastHeight );
|
||||||
this._sw = newWidth / 100;
|
this._sw = newWidth / 100;
|
||||||
|
this._sh = newHeight / 100;
|
||||||
|
|
||||||
if ( ratio > this._ultraWideRatio )
|
if ( ratio > this._ultraWideRatio )
|
||||||
{
|
{
|
||||||
|
@ -84,6 +90,29 @@ export class LandscapeScale
|
||||||
this._swidth = this._sw * 100;
|
this._swidth = this._sw * 100;
|
||||||
this._sheight = this._sh * 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 =
|
this._styleElement.innerHTML =
|
||||||
`
|
`
|
||||||
|
@ -98,6 +127,8 @@ export class LandscapeScale
|
||||||
--spx:${this._spx}px;
|
--spx:${this._spx}px;
|
||||||
--spx-raw:${this._spx};
|
--spx-raw:${this._spx};
|
||||||
--spx-inv:${1/this._spx}px;
|
--spx-inv:${1/this._spx}px;
|
||||||
|
--fs:${fsValue};
|
||||||
|
--firefoxOffset:${ffOffset};
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|
|
@ -60,6 +60,18 @@ export class PageHandler
|
||||||
private _rootPathResolver = new RootPathResolver();
|
private _rootPathResolver = new RootPathResolver();
|
||||||
get rootPathResolver(){ return this._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 )
|
setPagesPath( path:string )
|
||||||
{
|
{
|
||||||
this._pagesPath = path;
|
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 )
|
static setLocalHostPort( port:number )
|
||||||
{
|
{
|
||||||
|
@ -165,8 +169,7 @@ export class PageHandler
|
||||||
|
|
||||||
get localAdress()
|
get localAdress()
|
||||||
{
|
{
|
||||||
let adress = this.trimmedLocation;
|
let adress = this.trimmedLocation;
|
||||||
|
|
||||||
|
|
||||||
if ( adress.startsWith( PageHandler._localHost ) )
|
if ( adress.startsWith( PageHandler._localHost ) )
|
||||||
{
|
{
|
||||||
|
@ -191,7 +194,7 @@ export class PageHandler
|
||||||
|
|
||||||
if ( adress.startsWith( PageHandler._localHost ) )
|
if ( adress.startsWith( PageHandler._localHost ) )
|
||||||
{
|
{
|
||||||
console.log( "Is starting with localhost" );
|
// console.log( "Is starting with localhost" );
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,11 +215,8 @@ export class PageHandler
|
||||||
{
|
{
|
||||||
return this._isFileMode;
|
return this._isFileMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private _pagesLoaded = false;
|
|
||||||
|
|
||||||
async initialize()
|
async initialize()
|
||||||
{
|
{
|
||||||
|
@ -330,6 +330,12 @@ export class PageHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
let pageData = await this._getPageData( next );
|
let pageData = await this._getPageData( next );
|
||||||
|
|
||||||
|
if ( pageData == null )
|
||||||
|
{
|
||||||
|
console.log( "No page data found:", page, next );
|
||||||
|
}
|
||||||
|
|
||||||
this._currentLanguage = pageData.language;
|
this._currentLanguage = pageData.language;
|
||||||
|
|
||||||
let elementType = new ElementType( this.pageRootTag );
|
let elementType = new ElementType( this.pageRootTag );
|
||||||
|
@ -599,18 +605,18 @@ export class PageHandler
|
||||||
{
|
{
|
||||||
let trimmedLocation = window.location.href;
|
let trimmedLocation = window.location.href;
|
||||||
|
|
||||||
let hash = /#.+$/.exec( trimmedLocation );
|
let hash = /#.*$/.exec( trimmedLocation );
|
||||||
|
|
||||||
if ( hash )
|
if ( hash )
|
||||||
{
|
{
|
||||||
trimmedLocation = trimmedLocation.replace( /#.+$/, "" );
|
trimmedLocation = trimmedLocation.replace( /#.*$/, "" );
|
||||||
}
|
}
|
||||||
|
|
||||||
let search = /\?.+$/.exec( trimmedLocation );
|
let search = /\?.*$/.exec( trimmedLocation );
|
||||||
|
|
||||||
if ( search )
|
if ( search )
|
||||||
{
|
{
|
||||||
trimmedLocation = trimmedLocation.replace( /\?.+$/, "" );
|
trimmedLocation = trimmedLocation.replace( /\?.*$/, "" );
|
||||||
}
|
}
|
||||||
|
|
||||||
return trimmedLocation;
|
return trimmedLocation;
|
||||||
|
@ -620,7 +626,7 @@ export class PageHandler
|
||||||
{
|
{
|
||||||
let trimmedLocation = window.location.href;
|
let trimmedLocation = window.location.href;
|
||||||
|
|
||||||
let hash = /#\[.+\]$/.exec( trimmedLocation );
|
let hash = /#\[.*\]$/.exec( trimmedLocation );
|
||||||
|
|
||||||
if ( hash )
|
if ( hash )
|
||||||
{
|
{
|
||||||
|
@ -649,7 +655,7 @@ export class PageHandler
|
||||||
|
|
||||||
private _startUpdater()
|
private _startUpdater()
|
||||||
{
|
{
|
||||||
if ( 'scrollRestoration' in window.history)
|
if ( 'scrollRestoration' in window.history )
|
||||||
{
|
{
|
||||||
window.history.scrollRestoration = 'manual';
|
window.history.scrollRestoration = 'manual';
|
||||||
}
|
}
|
||||||
|
@ -1095,6 +1101,11 @@ export class PageHandler
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( hrefValue.startsWith( "#" ) )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if ( PageHandler.isFunctionHandleLink( hrefValue ) )
|
if ( PageHandler.isFunctionHandleLink( hrefValue ) )
|
||||||
{
|
{
|
||||||
PageHandler.makeFunctionHandleLink( a );
|
PageHandler.makeFunctionHandleLink( a );
|
||||||
|
@ -1103,9 +1114,13 @@ export class PageHandler
|
||||||
|
|
||||||
if ( AttributeValue.target_blank.in( a ) )
|
if ( AttributeValue.target_blank.in( a ) )
|
||||||
{
|
{
|
||||||
|
console.log( "Not processing blank link:", ElementAttribute.href.from( a ), a );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
console.log( "Processing blank link:", ElementAttribute.href.from( a ), a );
|
||||||
|
}
|
||||||
this.makePageHandleLink( a );
|
this.makePageHandleLink( a );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,24 @@ export class UserAgentInfo
|
||||||
return UserAgentInfo.is( /tablet/i );
|
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()
|
static get isTV()
|
||||||
{
|
{
|
||||||
let isTV = UserAgentInfo.is( /smart\-?tv/i ) || UserAgentInfo.is( /net\-?cast/i ) ||
|
let isTV = UserAgentInfo.is( /smart\-?tv/i ) || UserAgentInfo.is( /net\-?cast/i ) ||
|
||||||
|
|
|
@ -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 = `<span class="label">${textValue}</span>`;
|
||||||
|
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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 );
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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 );
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,10 +6,18 @@ export class Box2
|
||||||
min:Vector2;
|
min:Vector2;
|
||||||
max:Vector2;
|
max:Vector2;
|
||||||
|
|
||||||
constructor( min:Vector2 = null, max:Vector2 = null )
|
static create( min:Vector2, max:Vector2 ):Box2
|
||||||
{
|
{
|
||||||
this.min = min;
|
let box = new Box2();
|
||||||
this.max = max;
|
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 min = new Vector2( clientRect.left, clientRect.top );
|
||||||
let max = new Vector2( clientRect.right, clientRect.bottom );
|
let max = new Vector2( clientRect.right, clientRect.bottom );
|
||||||
|
|
||||||
return new Box2( min, max );
|
return Box2.create( min, max );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { MathX } from "../math/MathX";
|
||||||
|
|
||||||
export class Vector2
|
export class Vector2
|
||||||
{
|
{
|
||||||
x:number = 0;
|
x:number = 0;
|
||||||
|
@ -49,4 +51,10 @@ export class Vector2
|
||||||
return Math.sqrt( this.x * this.x + this.y * this.y );
|
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 );
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -93,6 +93,25 @@ export abstract class TreeWalker<N>
|
||||||
let num = this.numChildren( node );
|
let num = this.numChildren( node );
|
||||||
return num <= 0?null:this.childAt( node, num-1 );
|
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 )
|
forAll( node:N, callback:( node:N ) => void )
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
<style>
|
||||||
|
|
||||||
|
break--space
|
||||||
|
{
|
||||||
|
display: block;
|
||||||
|
height: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
<style>
|
||||||
|
|
||||||
|
column--break,
|
||||||
|
.column--break
|
||||||
|
{
|
||||||
|
break-before: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
<style>
|
||||||
|
|
||||||
|
@media ( orientation: portrait )
|
||||||
|
{
|
||||||
|
.landscape--only,
|
||||||
|
landscape--only
|
||||||
|
{
|
||||||
|
display:none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
<style>
|
||||||
|
|
||||||
|
@media ( orientation: landscape )
|
||||||
|
{
|
||||||
|
.portrait--only,
|
||||||
|
portrait--only
|
||||||
|
{
|
||||||
|
display:none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
|
@ -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:ROOT_ELEMENT_REPLACER = "ROOT_ELEMENT_REPLACER";
|
||||||
static readonly ROOT_ELEMENT_REPLACER_MATCHER =
|
static readonly ROOT_ELEMENT_REPLACER_MATCHER =
|
||||||
new LexerMatcher( StylesProcessorLexer.ROOT_ELEMENT_REPLACER, /\[_?\]/ );
|
new LexerMatcher( StylesProcessorLexer.ROOT_ELEMENT_REPLACER, /#_/ );
|
||||||
constructor()
|
constructor()
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
|
|
|
@ -826,6 +826,12 @@ export class RegExpUtility
|
||||||
return new RegExp( source );
|
return new RegExp( source );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static createClassMatcher( source:string )
|
||||||
|
{
|
||||||
|
source = "(^|\\s)" + RegExpUtility.toRegexSource( source ) + "(\\s|$)";
|
||||||
|
return new RegExp( source );
|
||||||
|
}
|
||||||
|
|
||||||
static createMatcher( source:string )
|
static createMatcher( source:string )
|
||||||
{
|
{
|
||||||
return new RegExp( RegExpUtility.toRegexSource( source ) );
|
return new RegExp( RegExpUtility.toRegexSource( source ) );
|
||||||
|
|
|
@ -12,12 +12,12 @@ export class TemplatesInfo
|
||||||
|
|
||||||
export class TemplatesIndexBuilder
|
export class TemplatesIndexBuilder
|
||||||
{
|
{
|
||||||
inputDir:string;
|
inputDirectories:string[];
|
||||||
outputDir:string;
|
outputDir:string;
|
||||||
|
|
||||||
constructor( source:string, build:string )
|
constructor( source:string[], build:string )
|
||||||
{
|
{
|
||||||
this.inputDir = source;
|
this.inputDirectories = source;
|
||||||
this.outputDir = build;
|
this.outputDir = build;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,26 +37,29 @@ export class TemplatesIndexBuilder
|
||||||
compiler.hooks.afterCompile.tapAsync( "TemplatesIndexBuilder",
|
compiler.hooks.afterCompile.tapAsync( "TemplatesIndexBuilder",
|
||||||
async ( compilation:any, callback:any ) =>
|
async ( compilation:any, callback:any ) =>
|
||||||
{
|
{
|
||||||
await Files.forAllIn( this.inputDir, null,
|
for ( let inputDirectory of this.inputDirectories )
|
||||||
async ( p:PathReference ) =>
|
{
|
||||||
{
|
await Files.forAllIn( inputDirectory, null,
|
||||||
if ( await p.isDirectory() )
|
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();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
);
|
||||||
let fileName = p.fileName;
|
}
|
||||||
|
|
||||||
if ( ! this.filterFiles( fileName ) )
|
|
||||||
{
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
compilation.fileDependencies.add( p.absolutePath );
|
|
||||||
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
callback();
|
callback();
|
||||||
|
|
||||||
|
@ -66,47 +69,52 @@ export class TemplatesIndexBuilder
|
||||||
compiler.hooks.emit.tapAsync( "TemplatesIndexBuilder",
|
compiler.hooks.emit.tapAsync( "TemplatesIndexBuilder",
|
||||||
async ( compilation:any, callback:any ) =>
|
async ( compilation:any, callback:any ) =>
|
||||||
{
|
{
|
||||||
let templates = new TemplatesInfo();
|
let templates = new TemplatesInfo();
|
||||||
let templatesPathReference = new PathReference( path.resolve( this.inputDir ) );
|
|
||||||
|
|
||||||
// RJLog.log( templatesPathReference.absolutePath );
|
for ( let inputDirectory of this.inputDirectories )
|
||||||
|
{
|
||||||
|
let templatesPathReference = new PathReference( path.resolve( inputDirectory ) );
|
||||||
|
|
||||||
await Files.forAllIn( templatesPathReference.absolutePath, null,
|
// RJLog.log( templatesPathReference.absolutePath );
|
||||||
async ( p:PathReference ) =>
|
|
||||||
{
|
await Files.forAllIn( templatesPathReference.absolutePath, null,
|
||||||
if ( await p.isDirectory() )
|
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();
|
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" );
|
let templatesPath = path.join( this.outputDir, "index.json" );
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue