Added Animation/Events/XHttp

This commit is contained in:
Josef 2025-03-23 08:31:36 +01:00
parent a2152bfec6
commit 7a05d26162
17 changed files with 1185 additions and 1 deletions

View File

@ -0,0 +1,21 @@
import { OnAnimationFrame } from './OnAnimationFrame';
export class AnimationFrameEvent
{
onAnimationFrame:OnAnimationFrame;
_timeMS:number = 0;
get timeMS(){ return this._timeMS; }
_lastTimeMS:number = 0;
get lastTimeMS(){ return this._lastTimeMS; }
_deltaTimeMS:number = 0;
get deltaTimeMS(){ return this._deltaTimeMS; }
update()
{
this._lastTimeMS = this._timeMS;
this._timeMS = Date.now();
this._deltaTimeMS = this._timeMS - this._lastTimeMS;
}
}

View File

@ -0,0 +1,155 @@
import { EventSlot } from '../events/EventSlot';
import { AnimationFrameEvent } from './AnimationFrameEvent';
import { sleep } from './sleep';
export type Idle = "Idle";
export type Running = "Running";
export type Stopping = "Stopping";
export type OnAnimationFrameStatus = Idle | Running | Stopping;
export class OnAnimationFrame extends EventSlot<AnimationFrameEvent>
{
static readonly Idle:Idle = "Idle";
static readonly Running:Running = "Running";
static readonly Stopping:Stopping = "Stopping";
#status:OnAnimationFrameStatus = OnAnimationFrame.Idle;
get status(){ return this.#status; }
#stopFlag = false;
#updater:()=>void = null;
#animationFrameEvent = new AnimationFrameEvent();
constructor()
{
super();
this.#animationFrameEvent.onAnimationFrame = this;
}
doUntil( condition:()=>boolean, callback:( e:AnimationFrameEvent )=>void )
{
let conditionalCallback = ( e:AnimationFrameEvent )=>
{
callback( e );
if ( ! condition() )
{
this.removeListener( conditionalCallback );
}
}
this.addListener( conditionalCallback );
}
doOnceWhen( condition:()=>boolean, callback:( e:AnimationFrameEvent )=>void )
{
let conditionalCallback = ( e:AnimationFrameEvent )=>
{
if ( ! condition() )
{
return;
}
callback( e );
this.removeListener( conditionalCallback );
}
this.addListener( conditionalCallback );
}
run()
{
if ( OnAnimationFrame.Running === this.#status )
{
return;
}
this._schedulRunning();
}
private async _schedulRunning()
{
if ( ! this.#updater )
{
this._createUpdaterFunction();
}
if ( OnAnimationFrame.Idle === this.#status )
{
this._startUpdaterLoop();
return;
}
let currentState = this.#status as OnAnimationFrameStatus;
let maxTries = 1000;
while ( OnAnimationFrame.Idle !== currentState )
{
maxTries--;
if ( maxTries <= 0 )
{
return Promise.reject();
}
await sleep( 1 );
currentState = this.#status as OnAnimationFrameStatus;
if ( OnAnimationFrame.Idle === currentState )
{
this._startUpdaterLoop();
return;
}
}
}
private _startUpdaterLoop()
{
this.#stopFlag = false;
this.#status = OnAnimationFrame.Running;
this.#updater();
}
private _createUpdaterFunction()
{
this.#updater =
( ) =>
{
this._update();
if ( this.#stopFlag )
{
this.#stopFlag = false;
this.#status = OnAnimationFrame.Idle;
}
else
{
requestAnimationFrame( this.#updater );
}
};
}
private _update()
{
this.#animationFrameEvent.update();
this.dispatch( this.#animationFrameEvent );
}
stop()
{
this.#stopFlag = true;
}
}

View File

@ -0,0 +1,32 @@
import { sleep } from "./sleep";
export class TimePin
{
protected _timeMS:number;
now()
{
this._timeMS = new Date().getTime();
}
static create()
{
let pin = new TimePin();
pin._timeMS = new Date().getTime();
return pin;
}
async forMS( durationMS:number, sleepStepsMS:number = 15 ):Promise<void>
{
let endTime = this._timeMS + durationMS;
let currentTime = new Date().getTime();
while ( currentTime < endTime )
{
await sleep( sleepStepsMS) ;
currentTime = new Date().getTime();
}
return Promise.resolve();
}
}

View File

@ -0,0 +1,11 @@
export function nextFrame():Promise<void>
{
let promise = new Promise<void>(
( resolve, reject ) =>
{
requestAnimationFrame( () => { resolve(); } );
}
);
return promise;
}

View File

@ -0,0 +1,11 @@
export function sleep( timeMS:number ):Promise<void>
{
let promise = new Promise<void>(
( resolve, reject ) =>
{
setTimeout( resolve, timeMS );
}
);
return promise;
}

View File

@ -0,0 +1,15 @@
export class ChangeEvent<T>
{
private _lastValue:T;
get lastValue() { return this._lastValue; }
private _newValue:T;
get newValue(){ return this._newValue; }
constructor( lastValue:T, newValue:T )
{
this._lastValue = lastValue;
this._newValue = newValue;
}
}

View File

@ -0,0 +1,27 @@
import { Property } from "./Property";
interface Comparable<T>
{
equals( other:T ):boolean
}
export class ComparableProperty<T extends Comparable<T> > extends Property<T>
{
isEqual( a:T, b:T )
{
let valueA = a === null ? 0 : a === undefined ? 1 : 2;
let valueB = b === null ? 0 : b === undefined ? 1 : 2;
if ( valueA !== valueB )
{
return false;
}
if ( valueA !== 2 )
{
return true;
}
return a.equals( b );
}
}

View File

@ -0,0 +1,59 @@
export type EventSlotCallback<T> = ( eventData:T ) => void;
export class EventSlot<T>
{
private _listeners:EventSlotCallback<T>[] = [];
addListener( callback:EventSlotCallback<T> )
{
this._listeners.push( callback );
}
once( callback:EventSlotCallback<T> )
{
let onceCallback = ( t:T ) =>
{
try
{
callback( t );
}
catch( e )
{
console.error( "Error on callback", e );
}
this.removeListener( onceCallback );
};
this.addListener( onceCallback );
}
removeListener( callback:EventSlotCallback<T> )
{
let index = this._listeners.indexOf( callback );
if ( index === -1 )
{
return;
}
this._listeners.splice( index, 1 );
}
removeAll()
{
this._listeners = [];
}
dispatch( eventData:T )
{
for ( let listener of this._listeners )
{
listener( eventData );
}
}
}

View File

@ -0,0 +1,148 @@
import { Arrays } from "../tools/Arrays";
import { EventSlot } from "./EventSlot";
export enum ListPropertyEventType
{
ADDED,
REMOVED,
CHANGED,
SWAPPED
}
export class ListPropertyEvent<T>
{
type:ListPropertyEventType;
indices:number[] = [];
elements:T[] = []
static create<T>( type:ListPropertyEventType, indices:number[], elements:T[] )
{
let e = new ListPropertyEvent<T>();
e.type = type;
e.indices = indices;
e.elements = elements;
return e;
}
}
export class ListProperty<T> extends EventSlot<ListPropertyEvent<T>>
{
#list:T[] = [];
#dispatch( type:ListPropertyEventType, indices:number[], elements:T[] )
{
this.dispatch( ListPropertyEvent.create( type, indices, elements ) );
}
#dispatchAdded( indices:number[], elements:T[] )
{
this.#dispatch( ListPropertyEventType.ADDED, indices, elements );
}
#dispatchRemoved( indices:number[], elements:T[] )
{
this.#dispatch( ListPropertyEventType.REMOVED, indices, elements );
}
#dispatchChanged( indices:number[], elements:T[] )
{
this.#dispatch( ListPropertyEventType.CHANGED, indices, elements );
}
#dispatchSwapped( indices:number[], elements:T[] )
{
this.#dispatch( ListPropertyEventType.SWAPPED, indices, elements );
}
get length()
{
return this.#list.length;
}
indexOf( t:T )
{
return this.#list.indexOf( t );
}
forAll( callback:( t:T, index:number ) => void )
{
for ( let i = 0; i < this.#list.length; i++ )
{
callback( this.#list[ i ], i );
}
}
add( t:T )
{
this.#list.push( t );
this.#dispatchAdded( [ this.#list.length - 1 ], [ t ] );
}
addAll( all:T[] )
{
let last = this.#list.length;
this.#list = this.#list.concat( all );
this.#dispatchAdded( [ last ], all );
}
get( index:number )
{
return this.#list[ index ];
}
set( index:number, t:T )
{
let before = this.#list[ index ];
this.#list[ index ] = t;
this.#dispatchChanged( [ index ], [ before, t ] );
}
remove( index:number )
{
let removal = this.#list[ index ];
Arrays.removeAt( this.#list, index );
this.#dispatchRemoved( [ index ], [ removal ] );
}
removeFirst()
{
if ( this.length < 1 )
{
return;
}
this.remove( 0 );
}
removeLast()
{
if ( this.length < 1 )
{
return;
}
this.remove( this.length - 1 );
}
clear()
{
let all = this.#list;
this.#list = [];
this.#dispatchRemoved( null, all );
}
swap( indexA:number, indexB:number )
{
let elementA = this.#list[ indexA ];
let elementB = this.#list[ indexB ];
this.#list[ indexA ] = elementB;
this.#list[ indexB ] = elementA;
this.#dispatchSwapped( [ indexA, indexB ], [ elementB, elementA ] );
}
}

View File

@ -0,0 +1,98 @@
import { EventSlot } from './EventSlot';
import { ChangeEvent } from './ChangeEvent';
import { isClassOf } from '../tools/TypeUtilities';
export class Property<T> extends EventSlot<ChangeEvent<T>>
{
protected _value:T;
private _silent:boolean = false;
get isSilent()
{
return this._silent;
}
constructor( initValue:T )
{
super();
this._value = initValue;
}
toString()
{
return this.value + "";
}
get value(){ return this._value; }
set value( v:T )
{
if ( this._silent )
{
return;
}
if ( this.isEqual( v, this._value ) )
{
return;
}
let valueBefore = this._value;
this._value = v;
this.dispatch( new ChangeEvent<T>( valueBefore, v ) );
}
forceDispatch()
{
this.dispatch( new ChangeEvent<T>( this._value, this._value ) );
}
silent()
{
this._silent = true;
}
unsilent()
{
this._silent = false;
}
silentRecursively():boolean
{
let silent = this._silent;
this._silent = true;
return silent;
}
unsilentRecursively( valueBefore:boolean )
{
this._silent = valueBefore;
}
setValueSilently( v:T )
{
this._value = v;
}
isEqual( a:T, b:T )
{
return a === b;
}
static resolveValue<T>( value:T|Property<T>, alternative:T = null )
{
if ( value === null || value === undefined )
{
return alternative;
}
if ( isClassOf( value, Property ) )
{
return ( value as Property<T> ).value;
}
return value as T;
}
}

View File

@ -0,0 +1,162 @@
export type NullType = "null";
export type UndefinedType = "undefined";
export type PrimitiveType = "string" | "boolean" | "number";
export type DataType = PrimitiveType | NullType | UndefinedType | "object" ;
export type Action<T> = (t:T)=>void;
export type Predicate<T> = (t:T)=>boolean;
export type Func<R> = ()=>R;
export type Func1<I1,R> = ( i1:I1 ) => R;
export type Func2<I1,I2,R> = ( i1:I1, i2:I2 ) => R;
export type Func3<I1,I2,I3,R> = ( i1:I1, i2:I2, i3:I3 ) => R;
export interface ClassConstructor
{
prototype:PrototypeFunction
}
export interface PrototypeFunction
{
isPrototypeOf:( value:any ) => boolean;
}
export interface ObjectType
{
constructor:ClassConstructor;
}
export function isClassOf( value:any, classType:ClassConstructor )
{
return classType.prototype.isPrototypeOf( value );
}
export function castClass<T>( value:any, classType:ClassConstructor, alternativeValue:T = null )
{
if ( isClassOf( value, classType ) )
{
return value as T;
}
return alternativeValue;
}
export function getClass( value:any ):ClassConstructor
{
if ( isPrimitive( value ) )
{
return null;
}
if ( isNoValue( value ) )
{
return null;
}
return value.constructor;
}
export function hasOwnProp( value:any, name:string )
{
if ( ! value )
{
return false;
}
return Object.prototype.hasOwnProperty.call( value, name );
}
export function isNoValue( value:any )
{
if ( value === null )
{
return true;
}
if ( value === undefined )
{
return true;
}
return false;
}
export function isFunction( value:any )
{
if ( isNoValue( value ) )
{
return false;
}
if ( "function" === typeof( value ) )
{
return true;
}
return false;
}
export function getMemberName( parent:any, member:any )
{
for ( let it in parent )
{
if ( parent[ it ] === member )
{
return it;
}
}
return null;
}
export function isPrimitive( value:any )
{
if ( isNoValue( value ) )
{
return false;
}
let type = typeof( value );
if ( "string" === type )
{
return true;
}
if ( "number" === type )
{
return true;
}
if ( "boolean" === type )
{
return true;
}
return false;
}
export function className( value:any )
{
if ( value === null )
{
return "null";
}
if ( value === undefined )
{
return "undefined";
}
let type = typeof value;
if ( type === "object" )
{
return value.constructor.name;
}
return type;
}

25
browser/tools/sleep.ts Normal file
View File

@ -0,0 +1,25 @@
export function sleep( ms:number ):Promise<void>
{
let promise = new Promise<void>(
( resolve, reject )=>
{
setTimeout( ()=>{ resolve(); }, ms );
}
);
return promise;
}
export function waitAround( msMin:number, msMax:number ):Promise<void>
{
let ms = msMin + Math.random() * ( msMax - msMin );
let promise = new Promise<void>(
( resolve, reject )=>
{
setTimeout( ()=>{ resolve(); }, ms );
}
);
return promise;
}

View File

@ -0,0 +1,38 @@
export type GET = "GET";
export type HEAD = "HEAD";
export type POST = "POST";
export type PUT = "PUT";
export type DELETE = "DELETE";
export type CONNECT = "CONNECT";
export type OPTIONS = "OPTIONS";
export type TRACE = "TRACE";
export type PATCH = "PATCH";
export type HTTPMethodType =
GET | HEAD | POST | PUT | DELETE | CONNECT | OPTIONS | TRACE | PATCH;
export class HTTPMethodTypes
{
static readonly GET:GET = "GET";
static readonly HEAD:HEAD = "HEAD";
static readonly POST:POST = "POST";
static readonly PUT:PUT = "PUT";
static readonly DELETE:DELETE = "DELETE";
static readonly CONNECT:CONNECT = "CONNECT";
static readonly OPTIONS:OPTIONS = "OPTIONS";
static readonly TRACE:TRACE = "TRACE";
static readonly PATCH:PATCH = "PATCH";
static readonly all:HTTPMethodType[] =
[
"GET",
"HEAD",
"POST",
"PUT",
"DELETE",
"CONNECT",
"OPTIONS",
"TRACE",
"PATCH",
];
}

213
browser/xhttp/Loader.ts Normal file
View File

@ -0,0 +1,213 @@
import { HTTPMethodType, HTTPMethodTypes } from "./HTTPMethodType";
export class Loader
{
static async request<O,T>( url:string, input:T ):Promise<O>
{
let promise = new Promise<O>
(
( resolve, reject ) =>
{
let xhr = new XMLHttpRequest();
console.log( "loading", url );
xhr.open( "POST", url, true );
xhr.responseType = "text";
xhr.onload=
() =>
{
console.log( xhr.responseURL, xhr.responseText );
if ( xhr.status !== 200 || xhr.responseText.startsWith( "ERROR:" ) )
{
reject( xhr.responseText )
}
else
{
resolve( JSON.parse( xhr.responseText ) as O );
}
};
xhr.onerror=(e)=>
{
reject( e );
}
xhr.send( JSON.stringify( input ) );
}
);
return promise;
}
static async readDataURL( blob:Blob ):Promise<FileReader>
{
let promise = new Promise<FileReader>
(
( resolve, reject ) =>
{
let fileReader = new FileReader();
fileReader.onload = ()=>
{
resolve( fileReader );
}
fileReader.onerror = ()=>
{
reject();
}
fileReader.onabort = ()=>
{
reject();
}
fileReader.readAsDataURL( blob );
}
);
return promise;
}
static async loadJSON<T>( url:string, method:HTTPMethodType = HTTPMethodTypes.GET):Promise<T>
{
let promise = new Promise<T>
(
( resolve, reject )=>
{
let xhr = new XMLHttpRequest();
xhr.open( method, url, true );
xhr.responseType = "json";
console.log( "xhr", url, method );
xhr.onload=()=>
{
console.log( "onload" );
let value = null;
try
{
value = xhr.response;
}
catch ( e )
{
reject( e );
}
resolve( value as T );
};
xhr.onerror = ( e ) =>
{
console.log( "onerror", e );
reject( e );
}
xhr.send();
}
);
return promise;
}
static loadText( url:string, method:"GET"|"POST" = "GET" ):Promise<string>
{
let promise = new Promise<string>
(
( resolve, reject ) =>
{
let xhr = new XMLHttpRequest();
xhr.open( method, url, true );
xhr.responseType = "text";
xhr.onload = () =>
{
console.log( "load", url, xhr );
if ( xhr.status !== 200 )
{
reject( xhr.response )
}
else
{
resolve( xhr.responseText );
}
};
xhr.onerror = ( e )=>
{
console.log( "error", url, xhr );
reject( e );
}
xhr.send();
}
);
return promise;
}
static loadXML( url:string ):Promise<Document>
{
let promise = new Promise<Document>
(
(resolve,reject)=>
{
let xhr = new XMLHttpRequest();
xhr.open("GET",url,true);
xhr.responseType = "document";
xhr.onload=()=>
{
resolve(xhr.responseXML);
};
xhr.onerror=(e)=>
{
reject(e);
}
xhr.send();
}
);
return promise;
}
static loadImage( url:string ):Promise<HTMLImageElement>
{
let promise = new Promise<HTMLImageElement>
(
(resolve,reject)=>
{
let img = new Image();
img.onload = () =>
{
resolve( img );
};
img.onerror = ( e ) =>
{
reject( e );
}
img.src = url;
}
);
return promise;
}
}

31
node/log/LogColors.ts Normal file
View File

@ -0,0 +1,31 @@
export class LogColors
{
static readonly reset = "\x1b[0m";
static readonly bright = "\x1b[1m";
static readonly dim = "\x1b[2m";
static readonly underscore = "\x1b[4m";
static readonly blink = "\x1b[5m";
static readonly reverse = "\x1b[7m";
static readonly hidden = "\x1b[8m";
static readonly black_Foreground = "\x1b[30m";
static readonly red_Foreground = "\x1b[31m";
static readonly green_Foreground = "\x1b[32m";
static readonly yellow_Foreground = "\x1b[33m";
static readonly blue_Foreground = "\x1b[34m";
static readonly magenta_Foreground = "\x1b[35m";
static readonly cyan_Foreground = "\x1b[36m";
static readonly white_Foreground = "\x1b[37m";
static readonly gray_Foreground = "\x1b[90m";
static readonly black_Background = "\x1b[40m";
static readonly red_Background = "\x1b[41m";
static readonly green_Background = "\x1b[42m";
static readonly yellow_Background = "\x1b[43m";
static readonly blue_Background = "\x1b[44m";
static readonly magenta_Background = "\x1b[45m";
static readonly cyan_Background = "\x1b[46m";
static readonly white_Background = "\x1b[47m";
static readonly gray_Background = "\x1b[100m";
}

135
node/log/RJLog.ts Normal file
View File

@ -0,0 +1,135 @@
import { RegExpUtility } from "../../browser/text/RegExpUtitlity";
import { LogColors } from "./LogColors";
let pr = ( window as any ).process;
export class RJLog
{
static readonly errorColor = LogColors.red_Background;
static readonly errorMessageColor = LogColors.red_Foreground;
static readonly warnColor = LogColors.yellow_Background;
static readonly logColor = LogColors.gray_Background
static readonly resetColor = LogColors.reset;
static readonly matcherWithFunction = /^\s+at\s(.+)\s\(.+?:(\d+:\d+)\)/;
static readonly matcherFile = /\(.+?\\(\w+)\.js:(\d+:\d+)\)/;
static readonly matcherAnonymous = /^\s+at\s(.+)\s\((.+)\)/;
static readonly logAlwaysLineInfo = true;
static _parseLineResult( line:string ):RegExpExecArray
{
let result = RJLog.matcherWithFunction.exec( line ) ||
RJLog.matcherFile.exec( line ) ||
RJLog.matcherAnonymous.exec( line );
return result;
}
static _parseLine( line:string ):string
{
let result = RJLog._parseLineResult( line );
if ( result )
{
return " " + result[ 1 ] + "(" + result[ 2 ] + ") ";
}
return line;
}
static logError( e:Error )
{
console.log( "\n" + RJLog._formatErrorMessage( e.stack ) );
}
static _formatErrorMessage( stackTrace:string, color:string = RJLog.errorMessageColor )
{
let lines = RegExpUtility.splitLines( stackTrace );
let output:string[] = [ color ];
lines.forEach(
( line, index ) =>
{
let lineInfo = RJLog._parseLine( line );
output.push( lineInfo );
if ( index !== lines.length - 1 )
{
output.push( "\n" );
}
}
)
output.push( RJLog.resetColor );
return output.join( "" );
}
static getLineInfo( color:string = RJLog.logColor, stackTrace?:string, lineIndex:number = 3 )
{
stackTrace = stackTrace || ( new Error().stack + "" );
let lines = RegExpUtility.splitLines( stackTrace );
let result:RegExpExecArray = null;
while ( ! result && lineIndex < lines.length )
{
let line = lines[ lineIndex ];
result = RJLog._parseLineResult( line ) ;
lineIndex ++;
}
if ( ! result )
{
console.log( stackTrace );
return color + " <Unknown> " + RJLog.resetColor ;
}
return color + " " + result[ 1 ] + "(" + result[ 2 ] + ") " + RJLog.resetColor;
}
static error( ...params:any[] )
{
if ( RJLog.logAlwaysLineInfo || typeof pr === "object" )
{
let lineInfo = RJLog.getLineInfo( RJLog.errorColor );
console.log( "\n" + lineInfo );
}
console.error.apply( console, params );
}
static warn( ...params:any[] )
{
if ( RJLog.logAlwaysLineInfo || typeof pr === "object" )
{
let lineInfo = RJLog.getLineInfo( RJLog.warnColor );
console.log( "\n" + lineInfo );
}
console.warn.apply( console, params );
}
static log( ...params:any[] )
{
if ( RJLog.logAlwaysLineInfo || typeof pr === "object" )
{
let lineInfo = RJLog.getLineInfo();
console.log( "\n" + lineInfo );
}
console.log.apply( console, params );
}
}

View File

@ -1,7 +1,10 @@
{
"compilerOptions": {
"compilerOptions":
{
"lib": ["ES2020"],
"types": ["node"], // Includes Node.js types
},
"include": ["./*"]
}