Website Update 2025
This commit is contained in:
parent
f8edbdd581
commit
185e004839
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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";
|
||||
|
||||
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 );
|
||||
|
|
|
@ -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 )
|
||||
{
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -95,7 +95,7 @@ export class ElementType<E extends Element>
|
|||
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<E extends Element>
|
|||
}
|
||||
}
|
||||
|
||||
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<E extends Element>
|
|||
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[];
|
||||
|
|
|
@ -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 )
|
||||
|
|
|
@ -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;
|
||||
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 ) );
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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<LandscapeScale>();
|
||||
onOrientationChange = new EventSlot<LandscapeScale>();
|
||||
|
||||
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};
|
||||
}
|
||||
`
|
||||
|
||||
|
|
|
@ -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 );
|
||||
|
||||
}
|
||||
|
|
|
@ -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 ) ||
|
||||
|
|
|
@ -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;
|
||||
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 );
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
||||
}
|
|
@ -93,6 +93,25 @@ export abstract class TreeWalker<N>
|
|||
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 )
|
||||
{
|
||||
|
|
|
@ -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_MATCHER =
|
||||
new LexerMatcher( StylesProcessorLexer.ROOT_ELEMENT_REPLACER, /\[_?\]/ );
|
||||
new LexerMatcher( StylesProcessorLexer.ROOT_ELEMENT_REPLACER, /#_/ );
|
||||
constructor()
|
||||
{
|
||||
super();
|
||||
|
|
|
@ -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 ) );
|
||||
|
|
|
@ -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" );
|
||||
|
||||
|
|
Loading…
Reference in New Issue