2020-04-07 21:44:46 +02:00
class VTUtils {
static random ( min , max ) {
let rand = Math . random ( ) ;
if ( typeof min === 'undefined' ) {
return rand ;
} else if ( typeof max === 'undefined' ) {
if ( min instanceof Array ) {
return min [ Math . floor ( rand * min . length ) ] ;
} else {
return rand * min ;
}
} else {
if ( min > max ) {
let tmp = min ;
min = max ;
max = tmp ;
}
return rand * ( max - min ) + min ;
}
} ;
static randomInt ( min , max ) {
return Math . floor ( VTUtils . random ( min , max ) ) ;
}
static normalize ( val , max , min ) {
return ( val - min ) / ( max - min ) ;
} ;
static distance ( x , y , x2 , y2 ) {
let a = x - x2 ;
let b = y - y2 ;
return Math . sqrt ( a * a + b * b ) ;
}
static map ( n , start1 , stop1 , start2 , stop2 , withinBounds ) {
let newVal = ( n - start1 ) / ( stop1 - start1 ) * ( stop2 - start2 ) + start2 ;
if ( ! withinBounds ) {
return newVal ;
}
if ( start2 < stop2 ) {
return this . constrain ( newVal , start2 , stop2 ) ;
} else {
return this . constrain ( newVal , stop2 , start2 ) ;
}
} ;
static constrain ( n , low , high ) {
return Math . max ( Math . min ( n , high ) , low ) ;
}
static hsvToRgb ( h , s , v ) {
2020-08-05 11:24:59 +02:00
let r , g , b ,
i = Math . floor ( h * 6 ) ,
f = h * 6 - i ,
p = v * ( 1 - s ) ,
q = v * ( 1 - f * s ) ,
t = v * ( 1 - ( 1 - f ) * s ) ;
2020-04-07 21:44:46 +02:00
switch ( i % 6 ) {
case 0 :
r = v , g = t , b = p ;
break ;
case 1 :
r = q , g = v , b = p ;
break ;
case 2 :
r = p , g = v , b = t ;
break ;
case 3 :
r = p , g = q , b = v ;
break ;
case 4 :
r = t , g = p , b = v ;
break ;
case 5 :
r = v , g = p , b = q ;
break ;
}
return { r : r , g : g , b : b } ;
}
static peakRGB ( peak ) {
return {
r : peak ,
g : 1 - peak ,
b : 0
} ;
}
}
class VTVector {
constructor ( x , y , z ) {
this . x = x || 0 ;
this . y = y || 0 ;
this . z = z || 0 ;
}
//helper
static createRandom ( x , y , z ) {
x = x || 1 ;
y = y || 1 ;
z = z || 0 ;
return new VTVector ( VTUtils . random ( - x , x ) , VTUtils . random ( - y , y ) , VTUtils . random ( - z , z ) ) ;
}
mult ( times ) {
this . x *= times ;
this . y *= times ;
this . z *= times ;
}
set ( vector ) {
this . x = vector . x ;
this . y = vector . y ;
this . z = vector . z ;
}
add ( vector ) {
this . x = this . x + vector . x ;
this . y = this . y + vector . y ;
this . z = this . z + vector . z ;
}
addXYZ ( x , y , z ) {
this . x += x ;
this . y += y ;
this . z += z ;
}
setXYZ ( x , y , z ) {
this . x = x || 0 ;
this . y = y || 0 ;
this . z = z || 0 ;
}
clone ( ) {
return new VTVector ( this . x , this . y , this . z ) ;
}
}
2020-08-05 11:24:59 +02:00
function $ ( sel , s ) {
s = s || document ;
return s . querySelector ( sel ) ;
}
function $$ ( sel , s ) {
s = s || document ;
return s . querySelectorAll ( sel ) ;
}
Node . prototype . addDelegatedEventListener = function ( type , aim , cb ) {
this . addEventListener ( type , ( event ) => {
let target = event . target ;
if ( target . matches ( aim ) ) {
cb ( event , target ) ;
} else {
let parent = target . closest ( aim ) ;
if ( parent ) {
2020-08-06 23:44:37 +02:00
try {
cb ( event , parent ) ;
} catch ( e ) {
NotificationHandler . createNotification ( "FATAL ERROR WITHIN HANDLER!" , "error" , 1000 ) ;
//nothing!
}
2020-08-05 11:24:59 +02:00
}
}
} )
} ;
Node . prototype . hasClass = function ( className ) {
let items = className . split ( ',' ) ,
has = null ;
for ( let item of items ) {
if ( has === false ) {
break ;
}
has = this . classList . contains ( item . trim ( ) ) ;
}
return has === true ;
}
Node . prototype . addClass = function ( className ) {
let items = className . split ( ',' ) ;
for ( let item of items ) {
this . classList . add ( item . trim ( ) ) ;
}
return this ;
}
Node . prototype . removeClass = function ( className ) {
let items = className . split ( ',' ) ;
for ( let item of items ) {
this . classList . remove ( item . trim ( ) ) ;
}
return this ;
}
Node . prototype . toggleClass = function ( className , force ) {
let items = className . split ( ',' ) ;
for ( let item of items ) {
this . classList . toggle ( item . trim ( ) , force ) ;
}
}
Node . prototype . switchClass = function ( clOne , clTwo , twoOne ) {
let cl = this . classList ;
if ( twoOne ) {
cl . remove ( clOne ) ;
cl . add ( clTwo )
} else {
cl . remove ( clTwo )
cl . add ( clOne )
}
}
Node . prototype . toggleCheck = function ( className , force ) {
let cl = this . classList ;
let items = className . split ( ',' ) ;
for ( let item of items ) {
let clOne = item . trim ( ) ;
if ( force ) {
cl . add ( clOne ) ;
} else {
cl . remove ( clOne )
}
}
}
2020-08-07 19:31:30 +02:00
String . prototype . firstUpper = function ( ) {
return this . charAt ( 0 ) . toUpperCase ( ) + this . slice ( 1 ) ;
}
2020-08-05 11:24:59 +02:00
File . prototype . toBase64 = function ( cb ) {
const reader = new FileReader ( ) ;
reader . onloadend = cb ;
reader . readAsDataURL ( this ) ;
}
function b64toBlob ( b64Data , type ) {
const byteCharacters = atob ( b64Data ) ;
const byteNumbers = new Array ( byteCharacters . length ) ;
for ( let i = 0 ; i < byteCharacters . length ; i ++ ) {
byteNumbers [ i ] = byteCharacters . charCodeAt ( i ) ;
}
const byteArray = new Uint8Array ( byteNumbers ) ;
return new Blob ( [ byteArray ] , { type : type } ) ;
}
function create ( name , content ) {
let d = document . createElement ( name ) ;
if ( content ) {
d . innerHTML = content ;
}
return d ;
}
function append ( to , array ) {
for ( let item of array ) {
to . appendChild ( item ) ;
}
}
2020-08-06 23:44:37 +02:00
function hexToRgb ( hex ) {
hex = hex . replace ( "#" , "" ) ;
let bigint = parseInt ( hex , 16 ) ,
r = ( bigint >> 16 ) & 255 ,
g = ( bigint >> 8 ) & 255 ,
b = bigint & 255 ;
return [ r / 255 , g / 255 , b / 255 ] ;
2020-08-05 11:24:59 +02:00
}
// most of the functions are from https://webglfundamentals.org/webgl/resources/m4.js! but i doesnt want to use them all and make some adjustment to them!
2020-04-07 21:44:46 +02:00
class TDUtils {
2020-08-06 23:44:37 +02:00
static lastMatrix = { m : null } ;
2020-04-07 21:44:46 +02:00
static multiply ( a , b ) {
2020-08-05 11:24:59 +02:00
let b00 = b [ 0 ] ;
let b01 = b [ 1 ] ;
let b02 = b [ 2 ] ;
let b03 = b [ 3 ] ;
let b10 = b [ 4 ] ;
let b11 = b [ 5 ] ;
let b12 = b [ 6 ] ;
let b13 = b [ 7 ] ;
let b20 = b [ 8 ] ;
let b21 = b [ 9 ] ;
let b22 = b [ 10 ] ;
let b23 = b [ 11 ] ;
let b30 = b [ 12 ] ;
let b31 = b [ 13 ] ;
let b32 = b [ 14 ] ;
let b33 = b [ 15 ] ;
let a00 = a [ 0 ] ;
let a01 = a [ 1 ] ;
let a02 = a [ 2 ] ;
let a03 = a [ 3 ] ;
let a10 = a [ 4 ] ;
let a11 = a [ 5 ] ;
let a12 = a [ 6 ] ;
let a13 = a [ 7 ] ;
let a20 = a [ 8 ] ;
let a21 = a [ 9 ] ;
let a22 = a [ 10 ] ;
let a23 = a [ 11 ] ;
let a30 = a [ 12 ] ;
let a31 = a [ 13 ] ;
let a32 = a [ 14 ] ;
let a33 = a [ 15 ] ;
return [
b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30 ,
b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31 ,
b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32 ,
b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33 ,
b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30 ,
b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31 ,
b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32 ,
b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33 ,
b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30 ,
b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31 ,
b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32 ,
b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33 ,
b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30 ,
b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31 ,
b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32 ,
b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33
] ;
}
static translate ( m , tx , ty , tz , dst ) {
dst = dst || new Float32Array ( 16 ) ;
let m00 = m [ 0 ] ,
m01 = m [ 1 ] ,
m02 = m [ 2 ] ,
m03 = m [ 3 ] ,
m10 = m [ 4 ] ,
m11 = m [ 5 ] ,
m12 = m [ 6 ] ,
m13 = m [ 7 ] ,
m20 = m [ 8 ] ,
m21 = m [ 9 ] ,
m22 = m [ 10 ] ,
m23 = m [ 11 ] ,
m30 = m [ 12 ] ,
m31 = m [ 13 ] ,
m32 = m [ 14 ] ,
m33 = m [ 15 ] ;
dst [ 0 ] = m00 ;
dst [ 1 ] = m01 ;
dst [ 2 ] = m02 ;
dst [ 3 ] = m03 ;
dst [ 4 ] = m10 ;
dst [ 5 ] = m11 ;
dst [ 6 ] = m12 ;
dst [ 7 ] = m13 ;
dst [ 8 ] = m20 ;
dst [ 9 ] = m21 ;
dst [ 10 ] = m22 ;
dst [ 11 ] = m23 ;
dst [ 12 ] = m00 * tx + m10 * ty + m20 * tz + m30 ;
dst [ 13 ] = m01 * tx + m11 * ty + m21 * tz + m31 ;
dst [ 14 ] = m02 * tx + m12 * ty + m22 * tz + m32 ;
dst [ 15 ] = m03 * tx + m13 * ty + m23 * tz + m33 ;
2020-04-07 21:44:46 +02:00
return dst ;
}
2020-08-06 23:44:37 +02:00
static xRotation ( angle ) {
angle = TDUtils . degToRad ( angle ) ;
let c = Math . cos ( angle ) ;
let s = Math . sin ( angle ) ;
2020-04-07 21:44:46 +02:00
return [
1 , 0 , 0 , 0 ,
0 , c , s , 0 ,
0 , - s , c , 0 ,
0 , 0 , 0 , 1 ,
] ;
}
2020-08-06 23:44:37 +02:00
static yRotation ( angle ) {
angle = TDUtils . degToRad ( angle ) ;
let c = Math . cos ( angle ) ;
let s = Math . sin ( angle ) ;
2020-04-07 21:44:46 +02:00
return [
c , 0 , - s , 0 ,
0 , 1 , 0 , 0 ,
s , 0 , c , 0 ,
0 , 0 , 0 , 1 ,
] ;
}
2020-08-06 23:44:37 +02:00
static zRotation ( angle ) {
angle = TDUtils . degToRad ( angle ) ;
let c = Math . cos ( angle ) ;
let s = Math . sin ( angle ) ;
2020-04-07 21:44:46 +02:00
return [
c , s , 0 , 0 ,
- s , c , 0 , 0 ,
0 , 0 , 1 , 0 ,
0 , 0 , 0 , 1 ,
] ;
}
static degToRad ( d ) {
return d * Math . PI / 180 ;
}
2020-08-05 11:24:59 +02:00
static scale ( sx , sy , sz , dst ) {
dst = dst || new Float32Array ( 16 ) ;
dst [ 0 ] = sx ;
dst [ 5 ] = sy ;
dst [ 10 ] = sz ;
return dst ;
}
2020-04-07 21:44:46 +02:00
2020-08-05 11:24:59 +02:00
static lookAt ( cameraPosition , target , up , dst ) {
dst = dst || new Float32Array ( 16 ) ;
let zAxis = TDUtils . normalize (
TDUtils . subtractVectors ( cameraPosition , target ) ) ;
let xAxis = TDUtils . normalize ( TDUtils . cross ( up , zAxis ) ) ;
let yAxis = TDUtils . normalize ( TDUtils . cross ( zAxis , xAxis ) ) ;
dst [ 0 ] = xAxis [ 0 ] ;
dst [ 1 ] = xAxis [ 1 ] ;
dst [ 2 ] = xAxis [ 2 ] ;
dst [ 4 ] = yAxis [ 0 ] ;
dst [ 5 ] = yAxis [ 1 ] ;
dst [ 6 ] = yAxis [ 2 ] ;
dst [ 8 ] = zAxis [ 0 ] ;
dst [ 9 ] = zAxis [ 1 ] ;
dst [ 10 ] = zAxis [ 2 ] ;
dst [ 12 ] = cameraPosition [ 0 ] ;
dst [ 13 ] = cameraPosition [ 1 ] ;
dst [ 14 ] = cameraPosition [ 2 ] ;
dst [ 15 ] = 1 ;
2020-04-07 21:44:46 +02:00
2020-08-05 11:24:59 +02:00
return dst ;
}
static cross ( a , b , dst ) {
dst = dst || new Float32Array ( 3 ) ;
dst [ 0 ] = a [ 1 ] * b [ 2 ] - a [ 2 ] * b [ 1 ] ;
dst [ 1 ] = a [ 2 ] * b [ 0 ] - a [ 0 ] * b [ 2 ] ;
dst [ 2 ] = a [ 0 ] * b [ 1 ] - a [ 1 ] * b [ 0 ] ;
return dst ;
}
static normalize ( v , dst ) {
dst = dst || new Float32Array ( 3 ) ;
let length = Math . sqrt ( v [ 0 ] * v [ 0 ] + v [ 1 ] * v [ 1 ] + v [ 2 ] * v [ 2 ] ) ;
if ( length > 0.00001 ) {
dst [ 0 ] = v [ 0 ] / length ;
dst [ 1 ] = v [ 1 ] / length ;
dst [ 2 ] = v [ 2 ] / length ;
2020-04-07 21:44:46 +02:00
}
2020-08-05 11:24:59 +02:00
return dst ;
}
2020-04-07 21:44:46 +02:00
2020-08-05 11:24:59 +02:00
static subtractVectors ( a , b , dst ) {
dst = dst || new Float32Array ( 3 ) ;
dst [ 0 ] = a [ 0 ] - b [ 0 ] ;
dst [ 1 ] = a [ 1 ] - b [ 1 ] ;
dst [ 2 ] = a [ 2 ] - b [ 2 ] ;
return dst ;
}
2020-08-01 21:51:54 +02:00
2020-08-05 11:24:59 +02:00
static perspective ( fieldOfViewInRadians , aspect , near , far , dst ) {
dst = dst || new Float32Array ( 16 ) ;
let f = Math . tan ( Math . PI * 0.5 - 0.5 * fieldOfViewInRadians ) ,
rangeInv = 1.0 / ( near - far ) ;
dst [ 0 ] = f / aspect ;
dst [ 5 ] = f ;
dst [ 10 ] = ( near + far ) * rangeInv ;
dst [ 11 ] = - 1 ;
dst [ 14 ] = near * far * rangeInv * 2 ;
return dst ;
2020-04-07 21:44:46 +02:00
}
2020-08-05 11:24:59 +02:00
static inverse ( m , dst ) {
dst = dst || new Float32Array ( 16 ) ;
let m00 = m [ 0 ] ,
m01 = m [ 1 ] ,
m02 = m [ 2 ] ,
m03 = m [ 3 ] ,
m10 = m [ 4 ] ,
m11 = m [ 5 ] ,
m12 = m [ 6 ] ,
m13 = m [ 7 ] ,
m20 = m [ 8 ] ,
m21 = m [ 9 ] ,
m22 = m [ 10 ] ,
m23 = m [ 11 ] ,
m30 = m [ 12 ] ,
m31 = m [ 13 ] ,
m32 = m [ 14 ] ,
m33 = m [ 15 ] ,
tmp _0 = m22 * m33 ,
tmp _1 = m32 * m23 ,
tmp _2 = m12 * m33 ,
tmp _3 = m32 * m13 ,
tmp _4 = m12 * m23 ,
tmp _5 = m22 * m13 ,
tmp _6 = m02 * m33 ,
tmp _7 = m32 * m03 ,
tmp _8 = m02 * m23 ,
tmp _9 = m22 * m03 ,
tmp _10 = m02 * m13 ,
tmp _11 = m12 * m03 ,
tmp _12 = m20 * m31 ,
tmp _13 = m30 * m21 ,
tmp _14 = m10 * m31 ,
tmp _15 = m30 * m11 ,
tmp _16 = m10 * m21 ,
tmp _17 = m20 * m11 ,
tmp _18 = m00 * m31 ,
tmp _19 = m30 * m01 ,
tmp _20 = m00 * m21 ,
tmp _21 = m20 * m01 ,
tmp _22 = m00 * m11 ,
tmp _23 = m10 * m01 ,
t0 = ( tmp _0 * m11 + tmp _3 * m21 + tmp _4 * m31 ) -
( tmp _1 * m11 + tmp _2 * m21 + tmp _5 * m31 ) ,
t1 = ( tmp _1 * m01 + tmp _6 * m21 + tmp _9 * m31 ) -
( tmp _0 * m01 + tmp _7 * m21 + tmp _8 * m31 ) ,
t2 = ( tmp _2 * m01 + tmp _7 * m11 + tmp _10 * m31 ) -
( tmp _3 * m01 + tmp _6 * m11 + tmp _11 * m31 ) ,
t3 = ( tmp _5 * m01 + tmp _8 * m11 + tmp _11 * m21 ) -
( tmp _4 * m01 + tmp _9 * m11 + tmp _10 * m21 ) ,
d = 1.0 / ( m00 * t0 + m10 * t1 + m20 * t2 + m30 * t3 ) ;
dst [ 0 ] = d * t0 ;
dst [ 1 ] = d * t1 ;
dst [ 2 ] = d * t2 ;
dst [ 3 ] = d * t3 ;
dst [ 4 ] = d * ( ( tmp _1 * m10 + tmp _2 * m20 + tmp _5 * m30 ) -
( tmp _0 * m10 + tmp _3 * m20 + tmp _4 * m30 ) ) ;
dst [ 5 ] = d * ( ( tmp _0 * m00 + tmp _7 * m20 + tmp _8 * m30 ) -
( tmp _1 * m00 + tmp _6 * m20 + tmp _9 * m30 ) ) ;
dst [ 6 ] = d * ( ( tmp _3 * m00 + tmp _6 * m10 + tmp _11 * m30 ) -
( tmp _2 * m00 + tmp _7 * m10 + tmp _10 * m30 ) ) ;
dst [ 7 ] = d * ( ( tmp _4 * m00 + tmp _9 * m10 + tmp _10 * m20 ) -
( tmp _5 * m00 + tmp _8 * m10 + tmp _11 * m20 ) ) ;
dst [ 8 ] = d * ( ( tmp _12 * m13 + tmp _15 * m23 + tmp _16 * m33 ) -
( tmp _13 * m13 + tmp _14 * m23 + tmp _17 * m33 ) ) ;
dst [ 9 ] = d * ( ( tmp _13 * m03 + tmp _18 * m23 + tmp _21 * m33 ) -
( tmp _12 * m03 + tmp _19 * m23 + tmp _20 * m33 ) ) ;
dst [ 10 ] = d * ( ( tmp _14 * m03 + tmp _19 * m13 + tmp _22 * m33 ) -
( tmp _15 * m03 + tmp _18 * m13 + tmp _23 * m33 ) ) ;
dst [ 11 ] = d * ( ( tmp _17 * m03 + tmp _20 * m13 + tmp _23 * m23 ) -
( tmp _16 * m03 + tmp _21 * m13 + tmp _22 * m23 ) ) ;
dst [ 12 ] = d * ( ( tmp _14 * m22 + tmp _17 * m32 + tmp _13 * m12 ) -
( tmp _16 * m32 + tmp _12 * m12 + tmp _15 * m22 ) ) ;
dst [ 13 ] = d * ( ( tmp _20 * m32 + tmp _12 * m02 + tmp _19 * m22 ) -
( tmp _18 * m22 + tmp _21 * m32 + tmp _13 * m02 ) ) ;
dst [ 14 ] = d * ( ( tmp _18 * m12 + tmp _23 * m32 + tmp _15 * m02 ) -
( tmp _22 * m32 + tmp _14 * m02 + tmp _19 * m12 ) ) ;
dst [ 15 ] = d * ( ( tmp _22 * m22 + tmp _16 * m02 + tmp _21 * m12 ) -
( tmp _20 * m12 + tmp _23 * m22 + tmp _17 * m02 ) ) ;
return dst ;
}
static aspectView ( aspect ) {
return [
1 * aspect , 0 , 0 , 0 ,
0 , 1 , 0 , 0 ,
0 , 0 , 1 , 0 ,
0 , 0 , 0 , 1
]
}
static getMatrix ( fov , aspect , near , far , camAngle , radius ) {
let lMat = this . lastMatrix ,
u = TDUtils ;
if ( ! u . isSame ( 'fov' , fov )
|| ! u . isSame ( 'aspect' , aspect )
|| ! u . isSame ( 'near' , near )
|| ! u . isSame ( 'far' , far )
|| ! u . isSame ( 'cam' , camAngle )
|| ! u . isSame ( 'radius' , radius )
) {
let matrix = TDUtils . perspective ( TDUtils . degToRad ( fov ) , aspect , near , far ) ,
cameraMatrix = TDUtils . yRotation ( TDUtils . degToRad ( camAngle ) ) ;
cameraMatrix = TDUtils . translate ( cameraMatrix , 0 , 0 , radius * 1.5 ) ;
let viewMatrix = TDUtils . inverse ( cameraMatrix ) ;
matrix = TDUtils . multiply ( matrix , viewMatrix )
lMat . m = matrix ;
}
return lMat . m ;
}
static isSame ( key , value ) {
let lMat = this . lastMatrix ;
if ( lMat [ key ] !== value ) {
lMat [ key ] = value ;
return false ;
}
return true ;
2020-04-07 21:44:46 +02:00
}
2020-08-06 23:44:37 +02:00
static updateRotate ( rotation , def ) {
let value = vConf . get ( rotation , def ) + vConf . get ( rotation + '-inc' , 0 )
if ( value > 360 ) {
value -= 360 ;
} else if ( value < - 360 ) {
value += 360 ;
}
vConf . set ( rotation , value ) ;
}
2020-04-07 21:44:46 +02:00
}
2020-08-01 21:51:54 +02:00
class Template {
constructor ( ) {
this . tpl = { } ;
}
async loadTemplate ( name ) {
let self = this ;
if ( ! this . tpl [ name ] ) {
await fetch ( templateDir + name + ".tpl" ) . then ( ( r ) => r . text ( ) ) . then ( c => {
self . tpl [ name ] = c ;
} )
}
}
async loadArray ( names ) {
for ( let name of names ) {
await this . loadTemplate ( name ) ;
}
}
parseTemplate ( name , data ) {
if ( ! this . tpl [ name ] ) {
return ""
}
let m , d = this . tpl [ name ] ;
while ( ( m = templateEx . exec ( d ) ) !== null ) {
if ( m . index === templateEx . lastIndex ) {
templateEx . lastIndex ++ ;
}
let key = m [ 0 ] ;
2020-08-06 23:44:37 +02:00
let value = data [ m [ 1 ] ] ;
if ( value === undefined || value === null ) {
value = "" ;
}
d = d . replace ( key , value )
2020-08-01 21:51:54 +02:00
}
return d ;
}
parseFromAPI ( url , name , cb ) {
fetch ( url ) . then ( ( r ) => r . json ( ) ) . then ( d => {
cb ( this . parseTemplate ( name , d ) )
} ) . catch ( console . error )
}
}
const templateEx = /\$(.*?)\$/gm ;
const templateDir = "out/tpl/"
2020-04-07 21:44:46 +02:00
class ShaderHandler {
constructor ( gl ) {
this . gl = gl ;
this . shaderNames = [ ] ;
this . shaders = { } ;
this . programs = { } ;
}
setGL ( gl ) {
this . gl = gl ;
}
async loadShader ( name , path ) {
this . shaderNames . push ( name ) ;
await this . load ( name , path + name + ".vert" , this . gl . VERTEX _SHADER ) ;
await this . load ( name , path + name + ".frag" , this . gl . FRAGMENT _SHADER ) ;
}
async load ( name , url , type ) {
let realName = name + "_" + type ;
if ( ! this . shaders [ realName ] ) {
let data = await fetch ( url ) ;
let shader = this . createShader ( await data . text ( ) , type ) ;
if ( shader ) {
this . shaders [ realName ] = shader ;
}
}
return ! ! this . shaders [ realName ] ;
}
getShader ( name , type ) {
let realName = name + "_" + type ;
return this . shaders [ realName ] ;
}
getAllShaders ( ) {
return this . shaderNames ;
}
async createProgramForEach ( arr ) {
arr = arr || this . shaderNames ;
for ( let i = 0 ; i < arr . length ; i ++ ) {
let shader = arr [ i ] ;
let v = await shaderHandler . createProgram ( shader , [ shader ] )
if ( ! v ) {
return false ;
}
}
return true ;
}
createShader ( source , type ) {
let gl = this . gl ;
let shader = gl . createShader ( type ) ;
gl . shaderSource ( shader , source ) ;
gl . compileShader ( shader ) ;
if ( gl . getShaderParameter ( shader , gl . COMPILE _STATUS ) ) {
return shader ;
}
console . error ( gl . getShaderInfoLog ( shader ) ) ;
gl . deleteShader ( shader ) ;
return null ;
}
createProgram ( name , shaders ) {
let gl = this . gl ;
let pro = gl . createProgram ( ) ;
for ( let i = 0 ; i < shaders . length ; i ++ ) {
gl . attachShader ( pro , this . getShader ( shaders [ i ] , gl . VERTEX _SHADER ) ) ;
gl . attachShader ( pro , this . getShader ( shaders [ i ] , gl . FRAGMENT _SHADER ) ) ;
}
gl . linkProgram ( pro ) ;
var success = gl . getProgramParameter ( pro , gl . LINK _STATUS ) ;
if ( success ) {
this . programs [ name ] = pro ;
return pro ;
}
console . log ( gl . getProgramInfoLog ( pro ) ) ;
gl . deleteProgram ( pro ) ;
return null ;
}
getProgram ( name ) {
return this . programs [ name ] ;
}
2020-08-01 21:51:54 +02:00
use ( name ) {
let pro = this . programs [ name ] ;
this . gl . useProgram ( pro ) ;
return pro ;
}
2020-04-07 21:44:46 +02:00
async loadArray ( list , path ) {
let self = this ;
for ( const e of list ) {
await self . loadShader ( e , path )
}
await self . createProgramForEach ( list )
}
}
2020-08-01 21:51:54 +02:00
const AudioContext = window . AudioContext || window . webkitAudioContext ;
2020-04-07 21:44:46 +02:00
class AudioHandler {
async init ( ) {
2020-08-01 21:51:54 +02:00
let self = this ;
self . isStarted = false ;
self . audioFile = new Audio ( ) ;
2020-08-05 11:24:59 +02:00
self . actx = new AudioContext ( ) ;
self . analyser = self . actx . createAnalyser ( ) ;
2020-08-01 21:51:54 +02:00
self . analyser . fftSize = 4096 ;
self . lastSong = null ;
await self . connectAll ( ) ;
}
async connectAll ( ) {
let self = this ;
self . source = self . actx . createMediaElementSource ( self . audioFile ) ;
self . source . connect ( self . analyser ) ;
self . analyser . connect ( self . actx . destination ) ;
self . audioFile . addEventListener ( 'ended' , player . nextSong . bind ( player ) ) ;
}
async start ( ) {
if ( this . audioFile . src === '' ) {
return ;
}
if ( ! this . isStarted ) {
this . isStarted = true ;
await this . actx . resume ( ) ;
}
}
async stop ( ) {
if ( this . isStarted ) {
this . isStarted = false ;
await this . actx . suspend ( ) ;
}
}
fftSize ( size ) {
this . analyser . fftSize = size ;
}
smoothing ( float ) {
this . analyser . smoothingTimeConstant = float ;
}
2020-08-06 23:44:37 +02:00
loadSong ( file ) {
if ( ! file ) {
NotificationHandler . createNotification ( "Sorry!<br> Currently no Song is uploaded!" , "error" , 2000 ) ;
return false ;
}
let self = this ,
src = file . file ;
2020-08-01 21:51:54 +02:00
if ( self . lastSong ) {
URL . revokeObjectURL ( self . lastSong ) ;
}
self . lastSong = this . audioFile . src = URL . createObjectURL ( src ) ;
if ( ! this . isStarted ) {
2020-08-05 11:24:59 +02:00
this . start ( ) . catch ( alert ) ;
2020-08-01 21:51:54 +02:00
}
2020-08-05 11:24:59 +02:00
this . audioFile . play ( ) . then ( e => {
2020-08-06 23:44:37 +02:00
if ( pConf . get ( "showPlaying" , "true" ) ) {
NotificationHandler . createNotification ( "<span class='now-playing'>Now Playing:</span>" + file . getAudioName ( ) , "info" , pConf . get ( "showPlayingTime" , 1000 ) ) ;
}
2020-08-05 11:24:59 +02:00
window . dispatchEvent ( new CustomEvent ( 'playSong' ) ) ;
} ) . catch ( e => {
2020-08-06 23:44:37 +02:00
NotificationHandler . createNotification ( e . message , "error" , 1000 ) ;
2020-08-05 11:24:59 +02:00
player . nextSong ( ) ;
} ) ;
2020-08-01 21:51:54 +02:00
}
getIntArray ( steps ) {
let dataArray = new Uint8Array ( steps ) ;
this . analyser . getByteFrequencyData ( dataArray ) ;
return dataArray ;
}
getFloatArray ( ) {
2020-08-05 11:24:59 +02:00
let dataArray = new Float32Array ( this . analyser . fftSize ) ;
2020-08-01 21:51:54 +02:00
this . analyser . getFloatTimeDomainData ( dataArray ) ;
return dataArray ;
2020-04-07 21:44:46 +02:00
}
}
2020-08-06 23:44:37 +02:00
// Handler around the Playlist file to keep track on the ID3 and more!
class AudioPlayerFile {
constructor ( file , index ) {
this . file = file ;
this . name = this . getName ( ) ;
this . id3 = null ;
this . index = index ;
}
getName ( ) {
let name = this . file . name . split ( "." ) ;
name . pop ( ) ;
name = name . join ( "." ) ;
return name ;
}
getID3Tag ( force ) {
if ( ! force && this . id3 !== null ) {
return this . id3 ;
}
eventHandler . sendData ( 'getData' , {
file : this . file ,
name : this . name ,
index : this . index ,
force : force === true
} ) ;
return {
title : this . name ,
artist : 'VA' ,
}
}
getAudioName ( ) {
let tag = this . getID3Tag ( ) ;
return template . parseTemplate ( 'audio-information' , tag ) ;
}
}
class PlayerConfigHandler {
async init ( ) {
await template . loadArray ( [
'config/nav' ,
'config/content' ,
'config/visualitem'
] ) ;
this . last = 'base' ;
$ ( '.settings-icon' ) . addEventListener ( 'click' , this . open . bind ( this ) ) ;
$ ( 'modal-content' ) . addDelegatedEventListener ( 'click' , '.config-nav .item' , this . navHandler . bind ( this ) ) ;
}
open ( ) {
if ( this . content === undefined ) {
let content = template . parseTemplate ( 'config/nav' , { } ) ;
content += template . parseTemplate ( 'config/content' , { content : "" } ) ;
this . content = content ;
}
gui . modal . renderModal ( 'Settings' , this . content , "by VersusTuneZ" ) ;
this . handleById ( ) ;
gui . modal . showModal ( ) ;
}
navHandler ( e , el ) {
this . last = el . dataset . id ;
this . handleById ( ) ;
}
handleById ( ) {
let id = this . last ;
new VisualConfig ( id === 'visual' , id === 'base' ) ;
let active = $ ( '.config-nav .item.active' ) ,
current = $ ( '.config-nav .item[data-id="' + id + '"]' ) ;
if ( active ) {
active . removeClass ( 'active' ) ;
}
if ( current ) {
current . addClass ( 'active' ) ;
}
}
}
class VisualConfig {
static visualTemplates = { } ;
constructor ( showVisual , renderBase ) {
this . content = $ ( 'modal-content .config-content' ) ;
if ( showVisual ) {
this . renderVisualConfig ( visual . c ) ;
} else {
if ( renderBase ) {
this . renderBase ( ) ;
} else {
this . renderVisuals ( ) ;
}
}
}
renderVisuals ( ) {
let keys = Object . keys ( visual . visuals ) ,
content = '<section class="visuals">' ;
for ( let i = 0 ; i < keys . length ; i ++ ) {
content += template . parseTemplate ( 'config/visualitem' , {
title : visual . visuals [ keys [ i ] ] . name ,
id : keys [ i ] ,
active : keys [ i ] === visual . c ? 'active' : ''
} )
}
content += '</div>' ;
this . content . innerHTML = content ;
}
async renderBase ( ) {
let data = await this . loadVisualConfig ( 'base' ) ,
div = create ( 'section' ) ;
div . addClass ( 'base' ) ;
div . innerHTML = GUIHelper . fromJSON ( data , pConf ) ;
this . content . innerHTML = div . outerHTML ;
}
// the name loads the json and handle it!
async renderVisualConfig ( name ) {
let data = await this . loadVisualConfig ( name , vConf ) ,
div = create ( 'section' ) ;
div . addClass ( 'visual' ) ;
div . innerHTML = GUIHelper . fromJSON ( data , vConf ) ;
div . innerHTML += GUIHelper . createButton ( {
action : "resetVConf" ,
name : "Reset Visual Config"
} ) ;
div . innerHTML += GUIHelper . createButton ( {
action : "makeModalTransparent" ,
name : "toggle Modal Opacity"
} )
this . content . innerHTML = div . outerHTML ;
}
async loadVisualConfig ( name ) {
let tem = VisualConfig . visualTemplates ;
if ( ! tem [ name ] ) {
//load config and save it
tem [ name ] = await fetch ( '/out/gui/' + name + ".json" ) . then ( ( res ) => res . json ( ) ) ;
}
return tem [ name ] ;
}
}
2020-04-07 21:44:46 +02:00
class Player {
async init ( ) {
2020-08-01 21:51:54 +02:00
this . playlist = new Playlist ( ) ;
}
nextSong ( ) {
let next = this . playlist . getNext ( ) ;
2020-08-06 23:44:37 +02:00
audioHandler . loadSong ( next ) ;
2020-08-01 21:51:54 +02:00
}
2020-04-07 21:44:46 +02:00
2020-08-01 21:51:54 +02:00
prevSong ( ) {
let next = this . playlist . getPrevious ( ) ;
2020-08-06 23:44:37 +02:00
audioHandler . loadSong ( next ) ;
2020-08-01 21:51:54 +02:00
}
playStop ( ) {
if ( ! audioHandler . lastSong ) {
let next = this . playlist . getCurrent ( ) ;
2020-08-06 23:44:37 +02:00
audioHandler . loadSong ( next ) ;
2020-08-05 11:24:59 +02:00
return ;
2020-08-01 21:51:54 +02:00
}
let audioFile = audioHandler . audioFile ;
if ( audioFile . paused ) {
audioFile . play ( ) ;
} else {
audioFile . pause ( ) ;
}
2020-08-05 11:24:59 +02:00
window . dispatchEvent ( new CustomEvent ( 'playSong' ) ) ;
2020-08-01 21:51:54 +02:00
}
playByID ( number ) {
2020-08-05 11:24:59 +02:00
this . playlist . index = number ;
let next = this . playlist . getCurrent ( ) ;
2020-08-06 23:44:37 +02:00
audioHandler . loadSong ( next ) ;
2020-08-01 21:51:54 +02:00
}
}
const PAGINATIONLIMIT = 50 ;
class Playlist {
constructor ( ) {
this . list = [ ] ;
this . shuffled = [ ] ;
this . index = 0 ;
this . page = 0 ;
2020-08-07 19:31:30 +02:00
this . isShuffle = pConf . get ( "shuffle" , false ) ;
2020-08-01 21:51:54 +02:00
$ ( 'body' ) . addDelegatedEventListener ( 'change' , 'input[type="file"]' , this . changeFiles . bind ( this ) ) ;
$ ( 'body' ) . addDelegatedEventListener ( 'click' , '.pagination .item' , this . handlePagination . bind ( this ) ) ;
2020-08-06 23:44:37 +02:00
eventHandler . addEvent ( 'id3-request' , this . handle . bind ( this ) ) ;
eventHandler . addEvent ( 'id3-request-force' , this . forceID3 . bind ( this ) ) ;
2020-08-01 21:51:54 +02:00
}
shuffle ( ) {
// only shuffle if more then 2 elements are in
let len = this . list . length ;
if ( len < 3 ) {
2020-08-06 23:44:37 +02:00
this . shuffled = [ 0 , 1 , 2 ] ;
return ;
2020-08-01 21:51:54 +02:00
}
// the current-list need to be shuffled...
for ( let i = 0 ; i < len ; i ++ ) {
let random = VTUtils . randomInt ( 0 , len - 1 ) ;
this . swap ( i , random ) ;
}
}
swap ( a , b ) {
2020-08-06 23:44:37 +02:00
this . shuffled [ a ] = b ;
this . shuffled [ b ] = a ;
2020-08-01 21:51:54 +02:00
}
getNext ( ) {
2020-08-06 23:44:37 +02:00
let items = this . list ,
2020-08-01 21:51:54 +02:00
len = items . length - 1 ,
next = this . index + 1 ;
if ( next > len ) {
next = 0 ;
}
this . index = next ;
2020-08-06 23:44:37 +02:00
return items [ this . getRealIndex ( ) ] ;
2020-08-01 21:51:54 +02:00
}
getPrevious ( ) {
2020-08-06 23:44:37 +02:00
let items = this . list ,
2020-08-01 21:51:54 +02:00
len = items . length - 1 ,
next = this . index - 1 ;
if ( next < 0 ) {
next = len ;
}
this . index = next ;
2020-08-06 23:44:37 +02:00
return items [ this . getRealIndex ( ) ] ;
2020-08-01 21:51:54 +02:00
}
getCurrent ( ) {
2020-08-06 23:44:37 +02:00
return this . list [ this . getRealIndex ( ) ] ;
2020-08-01 21:51:54 +02:00
}
// on new upload... this has to be an array!
setPlaylist ( files ) {
this . index = 0 ;
2020-08-06 23:44:37 +02:00
this . forceData = undefined ;
2020-08-01 21:51:54 +02:00
this . list = files ;
this . shuffle ( ) ;
}
handlePagination ( event , el ) {
if ( el . hasClass ( 'inactive' ) ) {
return ;
}
if ( el . hasClass ( 'next-site' ) ) {
this . renderPagination ( this . page + 1 ) ;
} else {
this . renderPagination ( this . page - 1 ) ;
}
}
renderPagination ( page ) {
2020-08-06 23:44:37 +02:00
if ( page === undefined ) {
page = this . page ;
}
2020-08-01 21:51:54 +02:00
let length = this . list . length ,
maxSite = Math . ceil ( length / PAGINATIONLIMIT ) - 1 ;
if ( page < 0 ) {
page = 0 ;
}
if ( page > maxSite ) {
page = maxSite ;
}
let s = page * PAGINATIONLIMIT ,
e = s + PAGINATIONLIMIT ,
data = "" ;
this . page = page ;
if ( e >= length ) {
e = length ;
}
if ( length > 0 ) {
2020-08-06 23:44:37 +02:00
let items = this . list ;
2020-08-01 21:51:54 +02:00
for ( let i = s ; i < e ; i ++ ) {
let obj = {
2020-08-05 11:24:59 +02:00
index : i . toString ( ) ,
2020-08-01 21:51:54 +02:00
nr : i + 1 ,
2020-08-06 23:44:37 +02:00
title : items [ this . getRealIndex ( i ) ] . getAudioName ( ) ,
2020-08-05 11:24:59 +02:00
active : ! audioHandler . audioFile . paused && i === this . index ? 'active' : ''
2020-08-01 21:51:54 +02:00
}
data += template . parseTemplate ( "playlist-item" , obj ) ;
}
} else {
data = "<h1>No Songs uploaded!</h1>" ;
}
let hasNext = maxSite > 1 && page < maxSite ;
gui . modal . renderModal (
"Playlist" ,
template . parseTemplate ( "playlist" , {
content : data ,
} ) ,
template . parseTemplate ( 'playlist-footer' , {
prevActive : page > 0 ? 'active' : 'inactive' ,
nextActive : hasNext ? 'active' : 'inactive' ,
page : ( page + 1 ) + ' / ' + parseInt ( maxSite + 1 ) ,
} )
) ;
}
//playlist handler for file input!
changeFiles ( e , el ) {
2020-08-05 11:24:59 +02:00
if ( el . id !== 'upload-dir' ) {
return ;
}
2020-08-01 21:51:54 +02:00
let files = [ ] ;
let i = 0 ;
for ( let file of el . files ) {
if ( file && file . type . indexOf ( 'audio' ) !== - 1 && file . name . match ( ".m3u" ) === null ) {
2020-08-06 23:44:37 +02:00
let audioFile = new AudioPlayerFile ( file , i ++ ) ;
files . push ( audioFile ) ;
2020-08-01 21:51:54 +02:00
}
}
this . setPlaylist ( files ) ;
if ( files . length > 0 ) {
2020-08-06 23:44:37 +02:00
NotificationHandler . createNotification ( "Songs added successfully!<br> Songs: " + files . length , "success" , 3000 ) ;
2020-08-01 21:51:54 +02:00
this . renderPagination ( 0 ) ;
} else {
2020-08-06 23:44:37 +02:00
NotificationHandler . createNotification ( "File Upload failed!" , "error" , 3000 ) ;
}
}
getRealIndex ( index ) {
if ( index === undefined ) {
index = this . index ;
}
if ( this . isShuffle ) {
return this . shuffled [ index ] ;
}
return index ;
}
handle ( data ) {
let index = data . index ;
if ( data . status === "waiting" ) {
return ;
}
this . list [ index ] . id3 = data ;
if ( this . timeout ) {
window . clearTimeout ( this . timeout ) ;
}
this . timeout = setTimeout ( this . renderPagination . bind ( this ) , 100 ) ;
}
forceID3 ( data ) {
let self = this ;
if ( ! self . forceData ) {
self . forceData = { } ;
self . forceNotification = NotificationHandler . createNotification ( "TagReader -> 0 / " + self . list . length , "info" , - 1 ) ;
}
let index = data . index ;
if ( data . status === "waiting" ) {
return ;
}
self . list [ index ] . id3 = data ;
self . forceData [ index ] = true ;
let leng = Object . keys ( self . forceData ) . length ;
this . forceNotification . updateMessageOnly ( "TagReader -> " + leng + " / " + self . list . length ) ;
if ( leng === self . list . length ) {
self . forceNotification . remove ( )
2020-08-01 21:51:54 +02:00
}
2020-04-07 21:44:46 +02:00
}
}
class GUI {
2020-08-01 21:51:54 +02:00
async init ( ) {
this . data = { } ;
this . modal = new Modal ( ) ;
// load first vis window!
await template . loadArray ( [
'playlist-item' ,
'playlist' ,
2020-08-06 23:44:37 +02:00
'playlist-footer' ,
'audio-information' ,
'inputs/color' ,
'inputs/input' ,
'inputs/slider' ,
'inputs/switch' ,
'inputs/select' ,
2020-08-07 19:31:30 +02:00
'inputs/option' ,
'help' ,
2020-08-01 21:51:54 +02:00
] ) ;
this . initDropZone ( ) ;
}
2020-08-07 19:31:30 +02:00
openHelp ( ) {
gui . modal . renderModal ( "Help" , template . parseTemplate ( 'help' , { } ) ) ;
gui . modal . showModal ( ) ;
}
2020-08-01 21:51:54 +02:00
initDropZone ( ) {
let items = 'drag dragstart dragend dragover dragenter dragleave drop' . split ( ' ' ) ;
items . forEach ( el => {
window . addEventListener ( el , async e => {
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
if ( e . type === 'drop' ) {
if ( e . dataTransfer . files . length > 0 ) {
2020-08-05 11:24:59 +02:00
e . dataTransfer . id = 'upload-dir' ;
2020-08-01 21:51:54 +02:00
player . playlist . changeFiles ( e , e . dataTransfer ) ;
} else {
alert ( "Sorry you need to upload files!" ) ;
}
}
} ) ;
} ) ;
} ;
}
2020-08-06 23:44:37 +02:00
// create config Inputs from JSON
2020-08-07 19:31:30 +02:00
//@todo add support for gui grouping!
2020-08-06 23:44:37 +02:00
class GUIHelper {
static fromJSON ( json , conf ) {
let data = [ ] ;
for ( let item of json ) {
switch ( item . type ) {
case 'slider' :
data . push ( GUIHelper . createSliders ( item , conf ) ) ;
break ;
case 'color' :
data . push ( GUIHelper . createColorPicker ( item , conf ) ) ;
break ;
case 'checkbox' :
data . push ( GUIHelper . createCheckbox ( item , conf ) ) ;
break ;
case 'input' :
data . push ( GUIHelper . createInputField ( item , conf ) ) ;
break ;
case 'select' :
data . push ( GUIHelper . createSelect ( item , conf ) ) ;
break ;
case 'button' :
data . push ( GUIHelper . createButton ( item , conf ) ) ;
break ;
default :
console . error ( ` Unknown Type: ${ item . type } ` ) ;
}
}
return data . join ( " " ) ;
}
static createSliders ( data , conf ) {
let content = "" ;
if ( typeof data . name === "object" ) {
for ( let i = 0 ; i < data . name . length ; i ++ ) {
let newData = { } ;
Object . assign ( newData , data ) ;
2020-08-07 19:31:30 +02:00
newData . showName = GUIHelper . richShowName ( data , data . name [ i ] . firstUpper ( ) ) ;
2020-08-06 23:44:37 +02:00
newData . name = GUIHelper . richName ( data , data . props [ i ] ) ;
content += GUIHelper . createSlider ( newData , conf ) ;
}
} else {
content = GUIHelper . createSlider ( data , conf ) ;
}
return content ;
}
static createSlider ( data , conf ) {
let newData = { } ;
Object . assign ( newData , data ) ;
newData . value = conf . get ( newData . name , newData . value ) ;
return template . parseTemplate ( 'inputs/slider' , newData )
}
static createColorPicker ( data , conf ) {
let newData = { } ;
Object . assign ( newData , data ) ;
newData . value = conf . get ( newData . name , newData . value ) ;
return template . parseTemplate ( 'inputs/color' , newData )
}
static createCheckbox ( data , conf ) {
let newData = { } ;
Object . assign ( newData , data ) ;
newData . value = conf . get ( newData . name , newData . value ) ? 'checked' : '' ;
return template . parseTemplate ( 'inputs/switch' , newData )
}
static createInputField ( data , conf ) {
let newData = { } ;
Object . assign ( newData , data ) ;
newData . value = conf . get ( newData . name , newData . value ) ;
return template . parseTemplate ( 'inputs/input' , newData )
}
static createSelect ( data , conf ) {
let newData = { } ;
Object . assign ( newData , data ) ;
newData . value = conf . get ( newData . name , newData . value ) ;
let options = '' ;
for ( let i = 0 ; i < newData . options . length ; i ++ ) {
options += template . parseTemplate ( 'inputs/option' , {
value : newData . options [ i ]
} )
}
newData . options = options ;
newData . event = 'visualConf' ;
newData . conf = conf . type ;
return template . parseTemplate ( 'inputs/select' , newData )
}
static richName ( data , name ) {
if ( data . group !== "" ) {
return data . group + "-" + name
}
return name ;
}
2020-08-07 19:31:30 +02:00
static richShowName ( data , name ) {
if ( data . group !== "" ) {
return data . group . firstUpper ( ) + ' ' + name
}
return name ;
}
2020-08-06 23:44:37 +02:00
static createButton ( item , conf ) {
return ` <div class='button spaced' data-action=" ${ item . action } "> ${ item . name } </div> `
}
}
2020-08-01 21:51:54 +02:00
class Modal {
constructor ( ) {
let self = this ;
self . currentModal = '' ;
self . modal = $ ( '#modal' ) ;
self . parent = self . modal . parentNode ;
self . modal . addDelegatedEventListener ( 'click' , 'header .close' , this . closeModal . bind ( this ) ) ;
}
resetModal ( ) {
this . renderModal ( '' , '' , '' ) ;
}
renderModal ( title , content , footer ) {
2020-08-06 23:44:37 +02:00
$ ( '#modal' ) . removeClass ( 'lightMode' )
2020-08-01 21:51:54 +02:00
this . currentModal = title ;
this . renderHeader ( title ) ;
this . renderContent ( content ) ;
this . renderFooter ( footer ) ;
}
renderHeader ( header ) {
let h = $ ( 'header .headline' , this . modal ) ;
h . innerHTML = header ;
}
renderContent ( content ) {
let con = $ ( 'modal-content' , this . modal ) ;
con . innerHTML = content ;
}
renderFooter ( footer ) {
2020-08-06 23:44:37 +02:00
let con = $ ( 'modal-footer .inner' , this . modal ) ;
con . innerHTML = footer || "by VersusTuneZ" ;
2020-08-01 21:51:54 +02:00
}
closeModal ( ) {
this . parent . addClass ( "hide" )
}
isCurrent ( title ) {
return title === this . currentModal ;
}
2020-04-07 21:44:46 +02:00
2020-08-01 21:51:54 +02:00
showModal ( ) {
this . parent . removeClass ( "hide" )
2020-04-07 21:44:46 +02:00
}
}
class Visual {
constructor ( ) {
this . data = [ ] ; //for drawing
this . dataArray = [ ] ;
2020-08-06 23:44:37 +02:00
this . name = "Default" ;
2020-04-07 21:44:46 +02:00
}
2020-08-01 21:51:54 +02:00
updateData ( ) {
2020-08-06 23:44:37 +02:00
}
2020-08-01 21:51:54 +02:00
2020-08-06 23:44:37 +02:00
updateFFT ( fftSize ) {
2020-08-01 21:51:54 +02:00
}
2020-04-07 21:44:46 +02:00
draw ( ) {
}
setup ( ) {
}
}
class VisualDrawer {
2020-08-01 21:51:54 +02:00
constructor ( ) {
this . visuals = {
2020-08-06 23:44:37 +02:00
//"sphere": new Sphere(),
2020-08-01 21:51:54 +02:00
"wave" : new Wave ( ) ,
2020-08-06 23:44:37 +02:00
"wave2d" : new Wave2D ( ) ,
//"water": new Water()
}
this . lastMainColor = {
base : '#-1' ,
color : [ 0 , 0 , 0 ]
} ;
this . lastSecondColor = {
base : '#-1' ,
color : [ 0 , 0 , 0 ]
2020-08-01 21:51:54 +02:00
}
}
init ( ) {
2020-08-06 23:44:37 +02:00
this . switch ( pConf . get ( "visual" , "wave2d" ) ) ;
2020-08-01 21:51:54 +02:00
this . updateLoop ( ) ;
}
2020-04-07 21:44:46 +02:00
2020-08-01 21:51:54 +02:00
switch ( visual ) {
if ( this . visuals [ visual ] != null ) {
this . c = visual ;
2020-08-05 11:24:59 +02:00
vConf . loadConfigByName ( this . c ) ;
2020-08-01 21:51:54 +02:00
this . visuals [ this . c ] . setup ( ) ;
2020-08-06 23:44:37 +02:00
pConf . set ( "visual" , this . c ) ;
pConf . save ( ) ;
2020-08-01 21:51:54 +02:00
}
}
updateLoop ( ) {
let self = this ;
let vis = self . visuals [ self . c ] ;
2020-08-06 23:44:37 +02:00
let pro = shaderHandler . use ( self . c ) ;
this . updateSeekbar ( ) ;
this . prepare ( pro ) ;
2020-08-01 21:51:54 +02:00
vis . updateData ( ) ;
vis . draw ( pro ) ;
requestAnimationFrame ( self . updateLoop . bind ( self ) )
}
2020-08-05 11:24:59 +02:00
2020-08-06 23:44:37 +02:00
updateSeekbar ( ) {
cInfo . width = window . innerWidth ;
cInfo . height = window . innerHeight ;
let audioFile = audioHandler . audioFile ;
ctx . clearRect ( 0 , 0 , cInfo . width , cInfo . height ) ;
if ( ! audioFile . paused && pConf . get ( "showSeekbar" , true ) ) {
//show seekbar
let dur = audioFile . duration ,
cur = audioFile . currentTime ,
percent = cur / dur * cInfo . width ;
ctx . fillStyle = pConf . get ( "seekColor" , '#fff' ) ;
ctx . fillRect ( 0 , c . height - 10 , percent , c . height ) ;
}
}
prepare ( pro ) {
2020-08-05 11:24:59 +02:00
c . width = window . innerWidth ;
c . height = window . innerHeight ;
gl . viewport ( 0 , 0 , gl . canvas . width , gl . canvas . height ) ;
gl . clearColor ( 0 , 0 , 0 , parseFloat ( pConf . get ( "alphaValue" , 0 ) ) ) ;
gl . clear ( gl . COLOR _BUFFER _BIT | gl . DEPTH _BUFFER _BIT ) ;
gl . enable ( gl . DEPTH _TEST ) ;
gl . depthFunc ( gl . LEQUAL ) ;
gl . enable ( gl . CULL _FACE ) ;
2020-08-06 23:44:37 +02:00
// u_baseColor || u_maxColor
this . setColor ( pro ) ;
}
setColor ( program ) {
let baseColor = gl . getUniformLocation ( program , "u_baseColor" ) ,
maxColor = gl . getUniformLocation ( program , "u_maxColor" ) ,
self = this ,
mainColor = self . lastMainColor ,
secondColor = self . lastSecondColor ;
this . updateColor ( 'lastMainColor' , 'baseColor' ) ;
this . updateColor ( 'lastSecondColor' , 'gradientToColor' ) ;
gl . uniform3fv ( baseColor , mainColor . color ) ;
gl . uniform3fv ( maxColor , secondColor . color ) ;
}
updateColor ( index , col ) {
let color = this [ index ] ,
value = vConf . get ( col , '#ffffff' )
if ( value !== color . base ) {
color . color = hexToRgb ( value ) ;
color . base = value ;
}
2020-08-05 11:24:59 +02:00
}
}
class ImageUploader {
async init ( ) {
this . image = pConf . get ( "bgURL" , "" ) ;
this . color = pConf . get ( "bgColour" , "#000000" ) ;
this . alpha = pConf . get ( "alphaValue" , 0.5 ) ;
this . getRealImage ( ) ;
this . applyValues ( ) ;
$ ( '#modal' ) . addDelegatedEventListener ( 'change' , '#image-upload input:not([type="color"])' , this . changeHandler . bind ( this ) ) ;
$ ( '#modal' ) . addDelegatedEventListener ( 'input' , '#image-upload input#color' , this . changeHandler . bind ( this ) ) ;
}
async renderModal ( ) {
await template . loadTemplate ( "image" ) ;
gui . modal . resetModal ( ) ;
gui . modal . renderModal ( "Background-Image" ,
template . parseTemplate ( "image" , {
value : this . image ,
bgValue : this . color ,
alphaValue : this . alpha
} ) , "" ) ;
gui . modal . showModal ( ) ;
}
changeHandler ( e , el ) {
if ( el . id === 'color' ) {
this . color = el . value ;
} else if ( el . id === "alphaValue" ) {
this . alpha = el . value ;
} else {
pConf . set ( 'bgMode' , el . id ) ;
if ( el . id === 'image' ) {
el . files [ 0 ] . toBase64 ( ( e , b ) => {
if ( b ) {
alert ( "Error converting image!" ) ;
return ;
}
pConf . set ( 'bgURL' , e . currentTarget . result ) ;
pConf . save ( ) ;
} )
this . image = URL . createObjectURL ( el . files [ 0 ] ) ;
} else {
this . image = el . value ;
pConf . set ( 'bgURL' , this . image ) ;
}
}
pConf . set ( 'bgColour' , this . color ) ;
pConf . set ( 'alphaValue' , this . alpha ) ;
this . applyValues ( ) ;
pConf . save ( ) ;
}
applyValues ( ) {
let body = $ ( 'body' ) ;
body . style . backgroundImage = 'url(' + this . image + ')' ;
body . style . backgroundColor = this . color ;
}
getRealImage ( ) {
let mode = pConf . get ( 'bgMode' ) ,
value = pConf . get ( "bgURL" , "" ) ;
if ( mode === 'image' ) {
if ( value !== '' && value . startsWith ( 'data:image' ) ) {
let split = value . split ( ";" ) ,
type = split . shift ( ) ,
message = split . join ( ";" ) . replace ( "base64," , "" ) ;
this . image = URL . createObjectURL ( b64toBlob ( message , type ) ) ;
}
} else {
this . image = value ;
}
}
}
const imageUploader = new ImageUploader ( ) ;
2020-08-06 23:44:37 +02:00
class NotificationHandler {
static instance = new NotificationHandler ( ) ;
2020-08-05 11:24:59 +02:00
constructor ( ) {
2020-08-06 23:44:37 +02:00
this . outer = $ ( '.notification' ) ;
this . notifications = [ ] ;
}
async init ( ) {
await template . loadTemplate ( 'notification' ) ;
}
2020-08-05 11:24:59 +02:00
2020-08-06 23:44:37 +02:00
static createNotification ( message , type , time ) {
time = parseInt ( time || "3000" ) ;
let handler = NotificationHandler . instance ,
not = new Notification ( message , type , time ) ;
handler . notifications . push ( not ) ;
not . show ( ) ;
return not ;
}
}
class Notification {
constructor ( message , type , time ) {
this . outer = NotificationHandler . instance . outer ;
this . message = message ;
this . type = type ;
this . time = time ;
this . isRemoved = false ;
}
async show ( ) {
let self = this ,
endless = self . time === - 1 ;
self . item = create ( 'div' ) ;
self . item . addClass ( 'notification-item, ' + self . type ) ;
if ( endless ) {
self . type += ' endless' ;
}
self . updateContent ( self . message ) ;
2020-08-07 19:31:30 +02:00
this . outer . prepend ( self . item ) ;
2020-08-06 23:44:37 +02:00
if ( ! endless ) {
setTimeout ( this . remove . bind ( this ) , self . time )
}
}
async remove ( ) {
if ( this . isRemoved ) {
return ;
}
this . isRemoved = true ;
this . outer . removeChild ( this . item ) ;
let not = NotificationHandler . instance . notifications ,
index = not . indexOf ( this ) ;
not . splice ( index , 1 ) ;
delete this ;
}
updateContent ( message ) {
let self = this ,
isEndless = self . time === - 1 ,
data = {
message : message ,
time : isEndless ? 1000 : self . time + 1 ,
type : self . type ,
}
this . item . innerHTML = template . parseTemplate ( 'notification' , data ) ;
}
updateMessageOnly ( message ) {
let item = $ ( '.message' , this . item ) ;
if ( item ) {
item . innerHTML = message ;
}
2020-08-05 11:24:59 +02:00
}
2020-08-01 21:51:54 +02:00
}
class Config {
2020-08-06 23:44:37 +02:00
static allConfigs = { } ;
constructor ( type ) {
2020-08-01 21:51:54 +02:00
this . config = { } ;
this . name = ''
2020-08-06 23:44:37 +02:00
this . type = type ;
Config . allConfigs [ type ] = this ;
2020-08-01 21:51:54 +02:00
}
loadConfigByName ( name ) {
2020-08-05 11:24:59 +02:00
this . save ( ) ;
2020-08-01 21:51:54 +02:00
this . name = 'config-' + name ;
2020-08-05 11:24:59 +02:00
let item = localStorage . getItem ( this . name ) ;
if ( item ) {
this . config = JSON . parse ( item ) ;
}
2020-08-01 21:51:54 +02:00
}
2020-08-05 11:24:59 +02:00
save ( ) {
2020-08-01 21:51:54 +02:00
if ( this . name !== '' ) {
localStorage . setItem ( this . name , JSON . stringify ( this . config ) ) ;
}
}
2020-08-05 11:24:59 +02:00
set ( name , value ) {
2020-08-01 21:51:54 +02:00
this . config [ name ] = value ;
}
2020-08-05 11:24:59 +02:00
remove ( name ) {
2020-08-01 21:51:54 +02:00
delete this . config [ name ] ;
}
2020-08-05 11:24:59 +02:00
get ( name , def ) {
2020-08-01 21:51:54 +02:00
let value = this . config [ name ] ;
if ( value === undefined || value === null ) {
this . config [ name ] = def ;
value = def ;
}
return value ;
}
2020-08-06 23:44:37 +02:00
reset ( ) {
NotificationHandler . createNotification ( ` CONFIG REQUEST SUCCESS FOR ${ this . type } ` , "success" , 2000 ) ;
this . config = { } ;
this . save ( ) ;
}
2020-04-07 21:44:46 +02:00
}
class Sphere extends Visual {
2020-08-06 23:44:37 +02:00
constructor ( ) {
super ( ) ;
this . name = "Sphere" ;
}
2020-04-07 21:44:46 +02:00
draw ( ) {
}
setup ( ) {
}
}
2020-08-01 21:51:54 +02:00
// 3D Audio-Waves -> maybe also 2D?
class Wave extends Visual {
2020-08-06 23:44:37 +02:00
constructor ( ) {
super ( ) ;
this . name = "3D Wave" ;
}
2020-08-01 21:51:54 +02:00
updateData ( ) {
let data = audioHandler . getFloatArray ( ) ;
let add = 2 / data . length ,
x = - 1 ;
2020-08-05 11:24:59 +02:00
let outerLoop = 0 ;
2020-08-01 21:51:54 +02:00
for ( let i = 0 ; i < data . length ; i ++ ) {
2020-08-05 11:24:59 +02:00
//first
this . data [ outerLoop ] = x ;
this . data [ outerLoop + 1 ] = data [ i ] ;
this . data [ outerLoop + 2 ] = 0 ;
//second
this . data [ outerLoop + 3 ] = x ;
//third
this . data [ outerLoop + 6 ] = x ;
this . data [ outerLoop + 8 ] = data [ i + 1 ] || 0 ;
outerLoop += 9 ;
2020-08-01 21:51:54 +02:00
x += add ;
}
}
draw ( program ) {
this . prepare ( program ) ;
let position = this . position ,
positionBuffer = gl . createBuffer ( ) ;
this . rotate ( program ) ;
gl . bindBuffer ( gl . ARRAY _BUFFER , positionBuffer ) ;
gl . bufferData ( gl . ARRAY _BUFFER , new Float32Array ( this . data ) , gl . DYNAMIC _DRAW ) ;
let vao = gl . createVertexArray ( ) ;
gl . bindVertexArray ( vao ) ;
gl . enableVertexAttribArray ( position ) ;
gl . vertexAttribPointer ( position , 3 , gl . FLOAT , true , 0 , 0 ) ;
2020-08-05 11:24:59 +02:00
gl . drawArrays ( vConf . get ( "waveForm" , gl . TRIANGLES ) , 0 , this . data . length / 3 ) ;
2020-08-06 23:44:37 +02:00
this . afterDraw ( ) ;
2020-08-01 21:51:54 +02:00
}
rotate ( program ) {
2020-08-05 11:24:59 +02:00
let aspect = c . width / c . height ,
2020-08-01 21:51:54 +02:00
matrix = [
2020-08-05 11:24:59 +02:00
1 / aspect , 0 , 0 , 0 ,
0 , 0.6 , 0 , 0 ,
0 , 0 , 1 , 0 ,
0 , 0 , 0 , 1
]
2020-08-06 23:44:37 +02:00
matrix = TDUtils . multiply ( matrix , TDUtils . xRotation ( vConf . get ( "rotation-x" , 10 ) ) ) ;
matrix = TDUtils . multiply ( matrix , TDUtils . yRotation ( vConf . get ( "rotation-y" , 50 ) ) ) ;
matrix = TDUtils . multiply ( matrix , TDUtils . zRotation ( vConf . get ( "rotation-z" , - 30 ) ) ) ;
let rotate = gl . getUniformLocation ( program , "u_matrix" ) ;
gl . uniformMatrix4fv ( rotate , false , matrix ) ;
}
setup ( ) {
this . updateFFT ( vConf . get ( 'fftSize' , 4096 ) ) ;
vConf . get ( "rotation-z" , - 30 ) ;
vConf . get ( "rotation-y" , 50 ) ;
vConf . get ( "rotation-x" , 10 ) ;
}
updateFFT ( fftSize ) {
audioHandler . fftSize ( fftSize ) ;
this . data = new Float32Array ( fftSize * 9 ) ;
}
prepare ( program ) {
this . position = gl . getAttribLocation ( program , "a_position" ) ;
this . color = gl . getUniformLocation ( program , "u_color" ) ;
2020-08-07 19:31:30 +02:00
let lightPos = gl . getUniformLocation ( program , "u_lightPos" ) ,
light = gl . getUniformLocation ( program , "u_light" ) ;
gl . uniform3fv ( lightPos , [
vConf . get ( "light-x" , 0 ) ,
vConf . get ( "light-y" , 5 ) ,
vConf . get ( "light-z" , - 56 )
] ) ;
gl . uniform1f ( light , parseFloat ( vConf . get ( "light-strength" , 0.3 ) ) ) ;
2020-08-06 23:44:37 +02:00
}
afterDraw ( ) {
TDUtils . updateRotate ( 'rotation-x' , 10 ) ;
TDUtils . updateRotate ( 'rotation-y' , 50 ) ;
TDUtils . updateRotate ( 'rotation-z' , - 30 ) ;
vConf . save ( ) ;
}
}
class Wave2D extends Visual {
constructor ( ) {
super ( ) ;
this . name = "2D Wave" ;
}
updateData ( ) {
let data = audioHandler . getFloatArray ( ) ;
let add = 2 / data . length ,
x = - 1 ;
let outerLoop = 0 ;
for ( let i = 0 ; i < data . length ; i ++ ) {
//first
this . data [ outerLoop ] = x ;
this . data [ outerLoop + 1 ] = data [ i ] ;
this . data [ outerLoop + 2 ] = data [ i ] ;
outerLoop += 3 ;
x += add ;
}
}
draw ( program ) {
this . prepare ( program ) ;
let position = this . position ,
positionBuffer = gl . createBuffer ( ) ;
this . rotate ( program ) ;
gl . bindBuffer ( gl . ARRAY _BUFFER , positionBuffer ) ;
gl . bufferData ( gl . ARRAY _BUFFER , new Float32Array ( this . data ) , gl . DYNAMIC _DRAW ) ;
let vao = gl . createVertexArray ( ) ;
gl . bindVertexArray ( vao ) ;
gl . enableVertexAttribArray ( position ) ;
gl . vertexAttribPointer ( position , 3 , gl . FLOAT , true , 0 , 0 ) ;
gl . drawArrays ( vConf . get ( "waveForm" , gl . LINE _STRIP ) , 0 , this . data . length / 3 ) ;
this . afterDraw ( ) ;
}
rotate ( program ) {
let matrix = [
1 , 0 , 0 , 0 ,
0 , 0.6 , 0 , 0 ,
0 , 0 , 1 , 0 ,
0 , 0 , 0 , 1
]
matrix = TDUtils . multiply ( matrix , TDUtils . xRotation ( vConf . get ( "rotation-x" , 0 ) ) ) ;
matrix = TDUtils . multiply ( matrix , TDUtils . yRotation ( vConf . get ( "rotation-y" , 0 ) ) ) ;
matrix = TDUtils . multiply ( matrix , TDUtils . zRotation ( vConf . get ( "rotation-z" , 0 ) ) ) ;
2020-08-01 21:51:54 +02:00
let rotate = gl . getUniformLocation ( program , "u_matrix" ) ;
gl . uniformMatrix4fv ( rotate , false , matrix ) ;
}
setup ( ) {
2020-08-06 23:44:37 +02:00
this . updateFFT ( vConf . get ( 'fftSize' , 16384 ) ) ;
}
updateFFT ( fftSize ) {
audioHandler . fftSize ( fftSize ) ;
this . data = new Float32Array ( fftSize * 3 ) ;
2020-08-01 21:51:54 +02:00
}
prepare ( program ) {
this . position = gl . getAttribLocation ( program , "a_position" ) ;
this . color = gl . getUniformLocation ( program , "u_color" ) ;
2020-08-06 23:44:37 +02:00
let lightPos = gl . getUniformLocation ( program , "u_lightPos" ) ;
2020-08-05 11:24:59 +02:00
gl . uniform3fv ( lightPos , vConf . get ( "light" , [ 0 , 5 , - 56 ] ) ) ;
2020-08-06 23:44:37 +02:00
}
afterDraw ( ) {
TDUtils . updateRotate ( 'rotation-x' , 0 ) ;
TDUtils . updateRotate ( 'rotation-y' , 0 ) ;
TDUtils . updateRotate ( 'rotation-z' , 0 ) ;
vConf . save ( ) ;
2020-08-01 21:51:54 +02:00
}
}
//animate Water the way like the Audio is Coming... 256FFT-Size max!
class Water extends Visual {
2020-08-06 23:44:37 +02:00
constructor ( ) {
super ( ) ;
this . name = "Water" ;
}
2020-08-01 21:51:54 +02:00
draw ( ) {
}
setup ( ) {
audioHandler . fftSize ( 256 )
}
}
2020-08-06 23:44:37 +02:00
class EventHandler {
constructor ( ) {
this . events = { } ;
}
2020-08-07 19:31:30 +02:00
addEvent ( events , cb ) {
let names = events . split ( "," ) ;
for ( let name of names ) {
this . events [ name . trim ( ) ] = cb ;
}
2020-08-06 23:44:37 +02:00
}
sendData ( name , data ) {
worker . postMessage ( {
cmd : name ,
data : data
} ) ;
}
handleEvent ( event ) {
let data = event . data ;
if ( ! data . cmd ) {
2020-08-07 19:31:30 +02:00
return false ;
2020-08-06 23:44:37 +02:00
}
if ( this . events [ data . cmd ] ) {
2020-08-07 19:31:30 +02:00
try {
this . events [ data . cmd ] ( data . data ) ;
} catch ( e ) {
console . error ( '[EventHandler] > ' + e . message ) ;
}
return true ;
2020-08-06 23:44:37 +02:00
}
2020-08-07 19:31:30 +02:00
return false ;
2020-08-06 23:44:37 +02:00
}
}
2020-08-01 21:51:54 +02:00
async function initHandler ( ) {
let body = $ ( 'body' ) ;
$ ( '.playlist.menu-icon' ) . addEventListener ( 'click' , e => {
player . playlist . renderPagination ( player . playlist . page ) ;
2020-08-05 11:24:59 +02:00
gui . modal . showModal ( ) ;
2020-08-01 21:51:54 +02:00
} ) ;
body . addDelegatedEventListener ( 'click' , '.playlist-item' , ( e , el ) => {
let number = el . dataset . index ;
player . playByID ( parseInt ( number ) ) ;
togglePlayButton ( 'pause' ) ;
} ) ;
body . addDelegatedEventListener ( 'click' , '.controls button' , ( e , el ) => {
switch ( el . id ) {
case 'previous' :
player . prevSong ( ) ;
break ;
case 'next' :
player . nextSong ( )
break ;
case 'play' :
player . playStop ( ) ;
break ;
2020-08-05 11:24:59 +02:00
case 'shuffle' :
toggleShuffle ( ) ;
break ;
2020-08-01 21:51:54 +02:00
}
} ) ;
2020-08-05 11:24:59 +02:00
window . addEventListener ( 'playSong' , setActiveOnPlaylist ) ;
2020-08-07 19:31:30 +02:00
window . addEventListener ( 'playSong' , e => {
togglePlayButton ( audioHandler . audioFile . paused ? 'play' : 'pause' ) ;
} ) ;
2020-08-05 11:24:59 +02:00
$ ( '.upload-image' ) . addEventListener ( 'click' , imageUploader . renderModal . bind ( imageUploader ) ) ;
2020-08-07 19:31:30 +02:00
body . addDelegatedEventListener ( 'click' , '.readAll' , forceAllRead ) ;
2020-08-06 23:44:37 +02:00
body . addDelegatedEventListener ( 'input' , '.input-range input[type="range"]' , ( e , el ) => {
let current = $ ( '.current' , el . parentNode ) ;
current . innerText = el . value ;
} ) ;
body . addDelegatedEventListener ( 'input' , 'input[type="color"]' , ( e , el ) => {
let parent = el . parentNode ;
$ ( '.colorBlob' , parent ) . style . backgroundColor = el . value ;
} )
body . addDelegatedEventListener ( 'click' , '.visual-item' , ( e , el ) => {
visual . switch ( el . dataset . id || 'wave' ) ;
$ ( 'modal-content .visuals .active' ) . removeClass ( 'active' ) ;
el . addClass ( 'active' ) ;
} )
body . addDelegatedEventListener ( 'input' , 'section.base input' , ( e , el ) => {
if ( el . type === 'checkbox' ) {
pConf . set ( el . name , el . checked ) ;
} else {
setValue ( el . name , el . value , pConf , el . dataset . type ) ;
}
pConf . save ( ) ;
} )
body . addDelegatedEventListener ( 'input' , 'section.visual input' , ( e , el ) => {
if ( el . type === 'checkbox' ) {
vConf . set ( el . name , el . checked ) ;
} else {
setValue ( el . name , el . value , vConf , el . dataset . type ) ;
}
vConf . save ( ) ;
} )
body . addDelegatedEventListener ( 'click' , '.button[data-action]' , ( e , el ) => {
switch ( el . dataset . action ) {
case 'resetVConf' :
vConf . reset ( ) ;
setTimeout ( e => {
playerConf . handleById ( ) ;
} , 30 ) ;
break ;
case 'makeModalTransparent' :
$ ( '#modal' ) . toggleClass ( 'lightMode' )
break ;
}
} )
2020-08-07 19:31:30 +02:00
$ ( '.help.menu-icon' ) . addEventListener ( 'click' , gui . openHelp ) ;
document . onfullscreenchange = e => {
if ( body . hasClass ( 'fullscreen' ) ) {
body . removeClass ( 'fullscreen' )
} else {
body . addClass ( 'fullscreen' )
}
}
}
function forceAllRead ( ) {
let playlist = player . playlist . list ;
for ( let i = 0 ; i < playlist . length ; i ++ ) {
playlist [ i ] . getID3Tag ( true ) ;
}
2020-08-06 23:44:37 +02:00
}
function setValue ( name , value , conf , type ) {
switch ( type ) {
case 'float' :
value = parseFloat ( value ) ;
break ;
case 'int' :
value = parseInt ( value ) ;
break ;
}
conf . set ( name , value ) ;
2020-08-01 21:51:54 +02:00
}
2020-08-05 11:24:59 +02:00
function setActiveOnPlaylist ( e ) {
let item = $ ( '.playlist-item[data-index="' + player . playlist . index + '"]' ) ,
active = $ ( '.playlist-item.active' ) ;
if ( active ) {
active . removeClass ( 'active' ) ;
}
if ( item ) {
item . addClass ( 'active' ) ;
}
}
2020-08-07 19:31:30 +02:00
function toggleShuffle ( updateGUI ) {
2020-08-05 11:24:59 +02:00
let active = player . playlist . isShuffle ;
2020-08-07 19:31:30 +02:00
if ( updateGUI !== false ) {
active = ! active ;
let status = active ? 'enabled' : 'disabled' ;
NotificationHandler . createNotification ( "Shuffle: " + status , "info" , 500 ) ;
pConf . set ( "shuffle" , active ) ;
pConf . save ( ) ;
player . playlist . isShuffle = active ;
}
2020-08-05 11:24:59 +02:00
$ ( '#shuffle' ) . toggleCheck ( 'active' , active ) ;
}
2020-08-01 21:51:54 +02:00
function togglePlayButton ( status ) {
let icons = $$ ( '#play .icon' ) ;
icons . forEach ( el => {
2020-08-05 11:24:59 +02:00
if ( el . dataset . name === status ) {
2020-08-01 21:51:54 +02:00
el . removeClass ( 'hide' ) ;
} else {
el . addClass ( 'hide' ) ;
}
} )
}
2020-08-06 23:44:37 +02:00
( function ( ) {
const body = $ ( 'body' ) ;
body . addDelegatedEventListener ( 'click' , 'custom-select .label' , ( e , el ) => {
let parent = el . parentNode ;
let options = $$ ( 'custom-option' , parent ) ,
optionsDiv = $ ( 'custom-options' , parent ) ;
if ( parent . hasClass ( 'open' ) ) {
optionsDiv . style . maxHeight = '' ;
parent . removeClass ( 'open' ) ;
} else {
let sum = 0 ;
options . forEach ( function ( element ) {
sum += element . offsetHeight ;
} ) ;
optionsDiv . style . maxHeight = sum + 'px' ;
parent . addClass ( 'open' ) ;
}
} )
body . addDelegatedEventListener ( 'click' , 'custom-select custom-option' , ( e , el ) => {
let select = el . closest ( 'custom-select' ) ,
input = $ ( 'input' , select ) ;
$$ ( 'custom-option.active' ) . forEach ( activeEl => {
activeEl . removeClass ( 'active' ) ;
} )
el . addClass ( 'active' ) ;
if ( input ) {
input . value = el . dataset . value || el . innerText ;
$ ( '.label' , select ) . innerText = el . innerText ;
select . removeClass ( 'open' ) ;
el . parentNode . style . maxHeight = '' ;
window . dispatchEvent ( new CustomEvent ( 'selectChanged' , {
detail : {
select : select ,
event : select . dataset . event ,
value : input . value ,
name : input . name
}
} ) ) ;
}
} )
window . addEventListener ( 'selectChanged' , ( e ) => {
if ( e . detail . event === 'visualConf' ) {
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
handleVisualConf ( e . detail ) ;
}
} )
function handleVisualConf ( e ) {
try {
let value = e . value ,
config = Config . allConfigs [ e . select . dataset . conf ] ;
if ( e . name === 'fftSize' ) {
value = parseInt ( e . value ) ;
visual . visuals [ visual . c ] . updateFFT ( value ) ;
}
config . set ( e . name , value ) ;
config . save ( ) ;
} catch ( e ) {
console . error ( e ) ;
}
}
} ) ( )
2020-08-07 19:31:30 +02:00
class KeyHandler {
async init ( ) {
await this . mediaKeys ( ) ;
await this . addKeyHandler ( ) ;
window . addEventListener ( 'keydown' , this . keyHandler . bind ( this ) ) ;
}
async mediaKeys ( ) {
if ( 'mediaSession' in navigator ) {
let media = navigator . mediaSession ;
media . setActionHandler ( 'play' , player . playStop . bind ( player ) ) ;
media . setActionHandler ( 'pause' , player . playStop . bind ( player ) ) ;
media . setActionHandler ( 'previoustrack' , player . prevSong . bind ( player ) ) ;
media . setActionHandler ( 'nexttrack' , player . prevSong . bind ( player ) ) ;
}
}
async addKeyHandler ( ) {
eventHandler . addEvent ( 'keys-Space' , player . playStop . bind ( player ) ) ;
eventHandler . addEvent ( 'keys-KeyN' , player . nextSong . bind ( player ) ) ;
eventHandler . addEvent ( 'keys-KeyV' , player . prevSong . bind ( player ) ) ;
eventHandler . addEvent ( 'keys-KeyS' , playerConf . open . bind ( playerConf ) ) ;
eventHandler . addEvent ( 'keys-KeyS-shift' , toggleShuffle ) ;
eventHandler . addEvent ( 'keys-KeyB' , imageUploader . renderModal . bind ( imageUploader ) ) ;
eventHandler . addEvent ( 'keys-KeyF-shift' , forceAllRead ) ;
eventHandler . addEvent ( 'keys-KeyH' , gui . openHelp ) ;
eventHandler . addEvent ( 'keys-KeyP' , e => {
player . playlist . renderPagination ( player . playlist . page ) ;
gui . modal . showModal ( ) ;
} ) ;
eventHandler . addEvent ( 'keys-Escape, keys-KeyC-shift' , e => {
gui . modal . resetModal ( ) ;
gui . modal . closeModal ( ) ;
} )
eventHandler . addEvent ( 'keys-F11' , e => {
if ( document . fullscreenElement ) {
document . exitFullscreen ( ) . catch ( console . error ) ;
} else {
document . body . requestFullscreen ( ) . catch ( console . error ) ;
}
} ) ;
}
async keyHandler ( event ) {
let key = event . code ,
shift = event . shiftKey ? '-shift' : '' ,
ctrl = event . ctrlKey ? '-ctrl' : '' ,
name = 'keys-' + key + shift + ctrl ;
if ( eventHandler . handleEvent ( {
data : {
cmd : name
}
} ) ) {
event . preventDefault ( ) ;
event . stopPropagation ( ) ;
}
}
}
2020-08-06 23:44:37 +02:00
class Startup {
constructor ( ) {
this . modules = {
'startup' : false ,
'id3-ready' : false
} ;
}
moduleLoaded ( name ) {
this . modules [ name ] = true
this . allModulesLoaded ( ) ;
}
allModulesLoaded ( ) {
for ( let module in this . modules ) {
if ( ! this . modules [ module ] ) {
return false ;
}
}
window . dispatchEvent ( new CustomEvent ( 'startupFin' ) ) ;
return true ;
}
}
2020-04-07 21:44:46 +02:00
const shaderHandler = new ShaderHandler ( null ) ,
audioHandler = new AudioHandler ( ) ,
gui = new GUI ( ) ,
2020-08-01 21:51:54 +02:00
visual = new VisualDrawer ( ) ,
template = new Template ( ) ,
player = new Player ( ) ,
2020-08-06 23:44:37 +02:00
vConf = new Config ( "visual" ) ,
pConf = new Config ( "player" ) ,
worker = new Worker ( '/out/js/worker.min.js' ) ,
startup = new Startup ( ) ,
eventHandler = new EventHandler ( ) ,
2020-08-07 19:31:30 +02:00
playerConf = new PlayerConfigHandler ( ) ,
keyHandler = new KeyHandler ( ) ;
2020-08-01 21:51:54 +02:00
2020-08-06 23:44:37 +02:00
let c , gl , cInfo , ctx ;
worker . addEventListener ( 'message' , e => {
if ( e . data . status === 'startup' ) {
startup . moduleLoaded ( e . data . cmd ) ;
return ;
}
eventHandler . handleEvent ( e ) ;
} ) ;
window . addEventListener ( 'startupFin' , e => {
setTimeout ( e => {
$ ( '.loading-screen' ) . remove ( ) ;
} , 100 )
} )
2020-04-07 21:44:46 +02:00
async function startUP ( ) {
2020-08-05 11:24:59 +02:00
pConf . loadConfigByName ( 'default' ) ;
2020-08-06 23:44:37 +02:00
c = $ ( '#c' ) ,
gl = c . getContext ( "webgl2" ) ,
cInfo = $ ( '#cInfo' ) ,
ctx = cInfo . getContext ( '2d' ) ;
2020-04-07 21:44:46 +02:00
if ( ! gl ) {
alert ( "SORRY THE BROWSER DOESN'T SUPPORT WEBGL2" ) ;
return false ;
}
shaderHandler . setGL ( gl )
2020-08-06 23:44:37 +02:00
await shaderHandler . loadArray ( [ "wave" , "sphere" , "water" , "wave2d" ] , 'shaders/' ) ;
await NotificationHandler . instance . init ( ) ;
2020-04-07 21:44:46 +02:00
await audioHandler . init ( ) ;
await player . init ( ) ;
2020-08-01 21:51:54 +02:00
await visual . init ( ) ;
await gui . init ( ) ;
2020-08-05 11:24:59 +02:00
await imageUploader . init ( ) ;
2020-08-06 23:44:37 +02:00
await playerConf . init ( ) ;
2020-08-07 19:31:30 +02:00
await keyHandler . init ( ) ;
2020-08-01 21:51:54 +02:00
await initHandler ( ) ;
2020-08-07 19:31:30 +02:00
toggleShuffle ( false ) ;
2020-04-07 21:44:46 +02:00
}
startUP ( ) . then ( r => {
2020-08-06 23:44:37 +02:00
startup . moduleLoaded ( 'startup' ) ;
2020-04-07 21:44:46 +02:00
} ) ;