aotrautils 0.0.44 → 0.0.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
1
 
2
- /*utils CLIENT library associated with aotra version : «1.0.0.000 (11/01/2022-00:18:33)»*/
2
+
3
+ /*utils CLIENT library associated with aotra version : «1.0.0.000 (11/07/2022-20:58:07)»*/
3
4
  /*-----------------------------------------------------------------------------*/
4
5
  /* ## Utility global methods in a browser (htmljs) client environment.
5
6
  *
@@ -9,7 +10,7 @@
9
10
  *
10
11
  * # Library name : «aotrautils»
11
12
  * # Library license : HGPL(Help Burma) (see aotra README information for details : https://alqemia.com/aotra.js )
12
- * # Author name : Jérémie Ratomposon (massively helped by its programming egos legions)
13
+ * # Author name : Jérémie Ratomposon (massively helped by his native country free education system)
13
14
  * # Author email : info@alqemia.com
14
15
  * # Organization name : Alqemia
15
16
  * # Organization email : admin@alqemia.com
@@ -35,6 +36,9 @@ if(typeof(window)==="undefined") window=global;
35
36
  const APOSTROPHE="’";
36
37
  PERFORM_TESTS_ON_LIBRARY=false;
37
38
 
39
+ const isUserMediaAvailable=!!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia && navigator.mediaDevices.enumerateDevices);
40
+
41
+
38
42
  // Dependances-optionalizing mechanisms :
39
43
  if(typeof aotest === "undefined"){
40
44
  aotest=function(testcase,testedFunction){ return testedFunction; };
@@ -1960,11 +1964,11 @@ if(typeof(window)!=="undefined") window.URL=window.URL || window.webkitURL;
1960
1964
  //navigator.getUserMedia=navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
1961
1965
  //window.AudioContext=window.AudioContext || window.webkitAudioContext;
1962
1966
 
1963
- function hasGetUserMedia(){
1964
- // OLD : (DEPRECATED)
1965
- // return !!/*<-=to force a boolean result*/(navigator.getUserMedia);
1966
- return !!/*<-=to force a boolean result*/(navigator.mediaDevices && navigator.mediaDevices.getUserMedia);
1967
- }
1967
+ //function hasGetUserMedia(){
1968
+ // // OLD : (DEPRECATED)
1969
+ //// return !!/*<-=to force a boolean result*/(navigator.getUserMedia);
1970
+ // return !!/*<-=to force a boolean result*/(navigator.mediaDevices && navigator.mediaDevices.getUserMedia);
1971
+ //}
1968
1972
 
1969
1973
 
1970
1974
 
@@ -3082,6 +3086,7 @@ function filterPoints(allPoints ,ctx/*DBG*/){
3082
3086
 
3083
3087
 
3084
3088
 
3089
+
3085
3090
  //Deprecated : use getMediaHandler directly
3086
3091
  /*public*/getWebcamHandler=function(
3087
3092
  // videoTagId
@@ -3101,6 +3106,12 @@ function filterPoints(allPoints ,ctx/*DBG*/){
3101
3106
  /*OPTIONAL*/doFinallyWhenMultipleDevices=null
3102
3107
  ){
3103
3108
 
3109
+ if(!isUserMediaAvailable){
3110
+ // TRACE
3111
+ log("ERROR : User medias is not supported in your browser (or are you running in a non-https context ?). Aborting");
3112
+ return null;
3113
+ }
3114
+
3104
3115
  const cameras=[];
3105
3116
  const microphones=[];
3106
3117
  navigator.mediaDevices.enumerateDevices().then(function(devices){
@@ -3110,14 +3121,10 @@ function filterPoints(allPoints ,ctx/*DBG*/){
3110
3121
  else if(d.kind==="audioinput") microphones.push(d);
3111
3122
  });
3112
3123
 
3113
-
3114
- // DBG
3115
- //alert(JSON.stringify(cameras));
3116
-
3117
3124
  // CAUTION : On today this method only handles media handlers with a single track :
3118
3125
 
3119
3126
  let constraints={video:(videoConfigParam!=null), audio:(audioConfigParam!=null)}; // Default constraints
3120
- if(videoConfigParam){
3127
+ if(!empty(cameras) && videoConfigParam){
3121
3128
  let webcamIndex=videoConfigParam.webcamIndex;
3122
3129
  if(webcamIndex!=null){
3123
3130
  const deviceIndex=Math.min(cameras.length-1, webcamIndex);
@@ -3127,7 +3134,7 @@ function filterPoints(allPoints ,ctx/*DBG*/){
3127
3134
  }
3128
3135
  };
3129
3136
  }
3130
- }else if(audioConfigParam){
3137
+ }else if(!empty(microphones) && audioConfigParam){
3131
3138
  let microphoneIndex=audioConfigParam.microphoneIndex;
3132
3139
  if(microphoneIndex!=null){
3133
3140
  const deviceIndex=Math.min(microphones.length-1, microphoneIndex);
@@ -3195,9 +3202,10 @@ function filterPoints(allPoints ,ctx/*DBG*/){
3195
3202
  return null;
3196
3203
  }
3197
3204
 
3198
- if (!hasGetUserMedia()){
3205
+
3206
+ if(!isUserMediaAvailable){
3199
3207
  // TRACE
3200
- log("ERROR : Method getUserMedia() is not supported in your browser, your browser doesn't have a getUserMedia() usable method.");
3208
+ log("ERROR : User medias is not supported in your browser (or are you running in a non-https context ?). Aborting");
3201
3209
  return null;
3202
3210
  }
3203
3211
 
@@ -3747,34 +3755,51 @@ getColorFingersPointer=(mediaHandler,colorsPattern="rgb")=>{
3747
3755
  //Sound handling :
3748
3756
 
3749
3757
 
3750
- function playAudioData(audioData,/*OPTIONAL*/audioCtxParam,/*OPTIONAL*/audioBufferSizeParam,/*OPTIONAL*/numberOfAudioChannelsParam){
3758
+ function playAudioData(audioData,audioCtxParam=null,audioBufferSizeParam=null,numberOfAudioChannelsParam=null,sampleRateParam=null,
3759
+ //sourceParam=null, bufferParam=null,
3760
+ doOnEndPlayingSample=null,
3761
+ playbackRateParam=2
3762
+ ){
3751
3763
 
3752
3764
  const DEFAULT_AUDIO_BUFFER_SIZE=4096;
3753
- let audioBufferSize=audioBufferSizeParam?audioBufferSizeParam:DEFAULT_AUDIO_BUFFER_SIZE;
3754
- let numberOfAudioChannelsOUT=numberOfAudioChannelsParam?numberOfAudioChannelsParam:1;//default is «mono»
3755
3765
 
3756
3766
  // To read the audio data :
3757
3767
  // simply perform a reading of mediaHandler.audioData
3758
3768
  // To play the sound :
3759
3769
 
3760
3770
  let audioCtx=nonull(audioCtxParam,new AudioContext());
3761
-
3762
- let sampleRate=audioCtx.sampleRate;
3763
-
3764
- let source=audioCtx.createBufferSource();
3765
-
3766
- let buffer=audioCtx.createBuffer(numberOfAudioChannelsOUT, audioBufferSize, sampleRate); // Only one channel for «mono»
3767
- let data=buffer.getChannelData(0);
3768
- for(let i=0; i<audioBufferSize; i++){
3769
- data[i]=audioData[i];
3770
- }
3771
- source.buffer=buffer;
3772
- source.connect(audioCtx.destination);
3771
+ let audioBufferSize=nonull(audioBufferSizeParam,DEFAULT_AUDIO_BUFFER_SIZE);
3772
+ let numberOfAudioChannels=nonull(numberOfAudioChannelsParam,1);//default is «mono»
3773
+ let sampleRate=nonull(sampleRateParam,audioCtx.sampleRate);
3774
+
3775
+ // We have to recreate them each time ! :
3776
+ let source=audioCtx.createBufferSource();
3777
+ let buffer=audioCtx.createBuffer(numberOfAudioChannels, audioBufferSize, sampleRate); // Only one channel for «mono»
3778
+
3779
+ let data=buffer.getChannelData(0);
3780
+ for(let i=0; i<audioBufferSize; i++){
3781
+ data[i]=audioData[i];
3782
+ }
3783
+ if(!source.buffer){
3784
+ source.playbackRate.value=playbackRateParam;
3785
+ source.buffer=buffer;
3786
+ source.connect(audioCtx.destination);
3787
+ // TODO : FIXME : DOES NOT CURRENTLY WORK :
3788
+ // BECAUSE OF INTERACTION-FIRST RESTRICTIONS WILL MAKE IT NOT WORK !
3789
+ // OLD : source.start(0);
3790
+ source.start();
3791
+
3792
+ // // DBG
3793
+ // lognow("audioSource STARTED : ",source);
3794
+ }
3773
3795
 
3774
- // TODO : FIXME : DOES NOT CURRENTLY WORK :
3775
- // BECAUSE OF INTERACTION-FIRST RESTRICTIONS WILL MAKE IT NOT WORK !
3776
- source.start(0);
3777
-
3796
+
3797
+ if(doOnEndPlayingSample){
3798
+ const playingDurationMillis=(audioBufferSize/sampleRate);
3799
+ // DBG
3800
+ // setTimeout(doOnEndPlayingSample,playingDurationMillis*1000); (because Hertz)
3801
+ doOnEndPlayingSample();
3802
+ }
3778
3803
 
3779
3804
  // OLD :
3780
3805
  // // Fill the buffer with data from array;
@@ -3934,6 +3959,7 @@ function loadSound(filePath,doOnLoaded=null,doOnEnded=null){
3934
3959
  isReady:false,
3935
3960
  speed:1,
3936
3961
  volume:1,
3962
+ isPlaying:false,
3937
3963
  mainLoopSound:null,
3938
3964
  setVolume:function(newVolume=null){
3939
3965
  if(newVolume) self.volume=newVolume;
@@ -3947,12 +3973,15 @@ function loadSound(filePath,doOnLoaded=null,doOnEnded=null){
3947
3973
  },
3948
3974
  play:function(){
3949
3975
  if(!self.nativeAudioElement.paused) return self;
3950
- self.nativeAudioElement.play();
3976
+ self.nativeAudioElement.play().then(()=>{
3977
+ self.isPlaying=true;
3978
+ });
3951
3979
  return self;
3952
3980
  },
3953
3981
  pause:function(){
3954
- if(self.nativeAudioElement.paused) return self;
3982
+ if(self.nativeAudioElement.paused || !self.isPlaying) return self;
3955
3983
  self.nativeAudioElement.pause();
3984
+ self.isPlaying=false;
3956
3985
  return self;
3957
3986
  },
3958
3987
  resumeLoop:function(){
@@ -3962,7 +3991,7 @@ function loadSound(filePath,doOnLoaded=null,doOnEnded=null){
3962
3991
  return self;
3963
3992
  },
3964
3993
  pauseLoop:function(){
3965
- if(!self.isLoop || !self.mainLoopSound) return self;
3994
+ if(!self.isLoop || !self.mainLoopSound || !self.isPlaying) return self;
3966
3995
  self.mainLoopSound.pause();
3967
3996
  self.pause();
3968
3997
  return self;
@@ -4170,7 +4199,7 @@ function getCenteredZoneCoords(xParam,yParam,width,height,center={x:"left",y:"to
4170
4199
  }
4171
4200
 
4172
4201
 
4173
- function drawImageAndCenter(ctx,image,xParam,yParam,center={x:"left",y:"top"},
4202
+ function drawImageAndCenter(ctx,image,xParam=0,yParam=0,zooms={zx:1,zy:1},center={x:"left",y:"top"},
4174
4203
  scaleFactorW=1,scaleFactorH=1,
4175
4204
  sizeW=null,sizeH=null,
4176
4205
  opacity=1,
@@ -4193,6 +4222,8 @@ function drawImageAndCenter(ctx,image,xParam,yParam,center={x:"left",y:"top"},
4193
4222
  ctx.globalAlpha=opacity;
4194
4223
  }
4195
4224
 
4225
+ // TODO : FIXME : use zooms parameter !
4226
+
4196
4227
  if(opacity!==0){
4197
4228
 
4198
4229
  if(alphaAngleRadians==null){
@@ -4353,8 +4384,12 @@ function getSpriteMonoThreaded(imagesPool,/*OPTIONAL*/refreshMillis=null,
4353
4384
  // drawingWidth, drawingHeight);
4354
4385
 
4355
4386
 
4356
- drawImageAndCenter(ctx,nonull(img, self.imagesPool),
4357
- xParam+self.xOffset, yParam+self.yOffset,
4387
+ const xImage=xParam+self.xOffset;
4388
+ const yImage=yParam+self.yOffset;
4389
+
4390
+ drawImageAndCenter(ctx, nonull(img,self.imagesPool),
4391
+ xImage, yImage,
4392
+ {zx:1,zy:1},
4358
4393
  self.center,
4359
4394
  (self.scaleFactorW*self.zoom),
4360
4395
  (self.scaleFactorH*self.zoom),
@@ -4387,9 +4422,11 @@ function getSpriteMonoThreaded(imagesPool,/*OPTIONAL*/refreshMillis=null,
4387
4422
  }
4388
4423
  }
4389
4424
 
4425
+ // DOES NOT WORK :
4426
+ // return ctx.getImageData(clipX,clipY,drawingWidth,drawingHeight);
4427
+ // }else{
4428
+ // return ctx.getImageData(0,0,drawingWidth,drawingHeight);
4390
4429
  }
4391
-
4392
-
4393
4430
  },
4394
4431
 
4395
4432
  };
@@ -4451,20 +4488,22 @@ function getMonoThreadedTimeout(doOnStop=null, delayMillis=null){
4451
4488
 
4452
4489
 
4453
4490
 
4454
- function getMonoThreadedRoutine(doOnEachStepCallback,terminateFunction=null,doOnStop=null,refreshMillis=null,startDependsOnParentOnly=false){
4491
+ function getMonoThreadedRoutine(doOnEachStepCallback,terminateFunction=null,doOnStop=null,refreshMillis=null,startDependsOnParentOnly=false,doOnSuspend=null,doOnResume=null){
4455
4492
 
4456
- // Three-states : started, suspended, stopped.
4493
+ // Three-states : started, paused, stopped.
4457
4494
 
4458
4495
  let self={
4459
4496
 
4460
4497
  startDependsOnParentOnly:startDependsOnParentOnly,
4461
4498
  started:false,
4462
- suspended:false,
4499
+ paused:false,
4463
4500
  time:getNow(),
4464
4501
  refreshMillis:refreshMillis,
4465
4502
  doOnEachStepCallback:doOnEachStepCallback,
4466
4503
  terminateFunction:terminateFunction,
4467
4504
  doOnStop:doOnStop,
4505
+ doOnSuspend:doOnSuspend,
4506
+ doOnResume:doOnResume,
4468
4507
  durationTimeFactorHolder:{durationTimeFactor:1},
4469
4508
  setDurationTimeFactorHolder:function(durationTimeFactorHolder){
4470
4509
  self.durationTimeFactorHolder=durationTimeFactorHolder;
@@ -4473,44 +4512,50 @@ function getMonoThreadedRoutine(doOnEachStepCallback,terminateFunction=null,doOn
4473
4512
  isStarted:function(){
4474
4513
  return self.started || self.startDependsOnParentOnly;
4475
4514
  },
4476
- start:function(callingObject=null){
4515
+ start:function(callingObject=null,args=null){
4477
4516
 
4478
4517
  // if(self.isStarted()) return;
4479
- if(self.terminateFunction && self.terminateFunction(callingObject)){
4518
+ if(self.terminateFunction && self.terminateFunction(callingObject,args)){
4480
4519
 
4481
4520
  // CAUTION : Even if the routine is «pre-terminated» before even starting
4482
4521
  // (ie. its stop conditions are fullfilled even before it has started)
4483
4522
  // we all the same have to trigger its stop treatments :
4484
- if(self.doOnStop) self.doOnStop(callingObject);
4523
+ if(self.doOnStop) self.doOnStop(callingObject,args);
4485
4524
 
4486
4525
  return;
4487
4526
  }
4488
4527
 
4489
4528
  self.started=true;
4490
4529
 
4491
- self.suspended=false;
4530
+ self.paused=false;
4492
4531
 
4493
4532
  return self;
4494
4533
  },
4495
- stop:function(callingObject=null){
4534
+ stop:function(callingObject=null,args=null){
4496
4535
 
4497
4536
  if(!self.isStarted()) return;
4498
4537
  self.started=false;
4499
4538
 
4500
- if(self.doOnStop) self.doOnStop(callingObject);
4539
+ if(self.doOnStop) self.doOnStop(callingObject,args);
4540
+
4541
+ },
4542
+ pause:function(callingObject=null,args=null){
4543
+ self.paused=true;
4501
4544
 
4545
+ if(self.doOnSuspend) self.doOnSuspend(callingObject,args);
4502
4546
  },
4503
- suspend:function(){
4504
- self.suspended=true;
4547
+ resume:function(callingObject=null,args=null){
4548
+ self.paused=false;
4549
+
4550
+ if(self.doOnResume) self.doOnResume(callingObject,args);
4505
4551
  },
4506
- resume:function(){
4507
- self.suspended=false;
4552
+ isPaused:function(){
4553
+ return self.paused;
4508
4554
  },
4509
-
4510
4555
  doStep:function(callingObject=null,args=null){
4511
4556
 
4512
4557
 
4513
- if(!self.isStarted() || self.suspended) return;
4558
+ if(!self.isStarted() || self.paused) return;
4514
4559
 
4515
4560
  // Looping index with a delay :
4516
4561
 
@@ -4521,8 +4566,8 @@ function getMonoThreadedRoutine(doOnEachStepCallback,terminateFunction=null,doOn
4521
4566
  }
4522
4567
 
4523
4568
  // We perform the step :
4524
- if(self.terminateFunction && self.terminateFunction(callingObject)){
4525
- self.stop(callingObject);
4569
+ if(self.terminateFunction && self.terminateFunction(callingObject,args)){
4570
+ self.stop(callingObject,args);
4526
4571
  return;
4527
4572
  }
4528
4573
 
@@ -4545,7 +4590,7 @@ function getMonoThreadedRoutine(doOnEachStepCallback,terminateFunction=null,doOn
4545
4590
  function getMonoThreadedGoToGoal(actualValue,goalValue,refreshMillis=null,totalTimeMillis=1000,mode="linear",doOnEachStepCallback=null,doOnStop=null){
4546
4591
 
4547
4592
  // const STEP_PRECISION=4;
4548
- // Three-states : started, suspended, stopped.
4593
+ // Three-states : started, paused, stopped.
4549
4594
 
4550
4595
  let self={
4551
4596
 
@@ -4555,7 +4600,7 @@ function getMonoThreadedGoToGoal(actualValue,goalValue,refreshMillis=null,totalT
4555
4600
  value:actualValue,
4556
4601
  goalValue:goalValue,
4557
4602
  started:false,
4558
- suspended:false,
4603
+ paused:false,
4559
4604
  time:getNow(),
4560
4605
  refreshMillis:refreshMillis,
4561
4606
  doOnEachStepCallback:doOnEachStepCallback,
@@ -4591,39 +4636,39 @@ function getMonoThreadedGoToGoal(actualValue,goalValue,refreshMillis=null,totalT
4591
4636
 
4592
4637
  return self;
4593
4638
  },
4594
- start:function(callingObject){
4639
+ start:function(callingObject=null,args=null){
4595
4640
 
4596
4641
  if( self.value===self.goalValue
4597
4642
  // || self.isStarted();
4598
- || self.terminateFunction(callingObject)){
4643
+ || self.terminateFunction(callingObject,args)){
4599
4644
 
4600
4645
  // CAUTION : Even if the routine is «pre-terminated» before even starting
4601
4646
  // (ie. its stop conditions are fullfilled even before it has started)
4602
4647
  // we all the same have to trigger its stop treatments :
4603
- if(self.doOnStop) self.doOnStop(callingObject);
4648
+ if(self.doOnStop) self.doOnStop(callingObject,args);
4604
4649
 
4605
4650
  return;
4606
4651
  }
4607
4652
 
4608
4653
  self.started=true;
4609
4654
 
4610
- self.suspended=false;
4655
+ self.paused=false;
4611
4656
 
4612
4657
  return self;
4613
4658
  },
4614
- stop:function(callingObject=null){
4659
+ stop:function(callingObject=null,args=null){
4615
4660
 
4616
4661
  if(!self.isStarted()) return;
4617
4662
  self.started=false;
4618
4663
 
4619
- if(self.doOnStop) self.doOnStop(callingObject);
4664
+ if(self.doOnStop) self.doOnStop(callingObject,args);
4620
4665
 
4621
4666
  },
4622
- suspend:function(){
4623
- self.suspended=true;
4667
+ pause:function(){
4668
+ self.paused=true;
4624
4669
  },
4625
4670
  resume:function(){
4626
- self.suspended=false;
4671
+ self.paused=false;
4627
4672
  },
4628
4673
  terminateFunction:function(){
4629
4674
 
@@ -4643,7 +4688,7 @@ function getMonoThreadedGoToGoal(actualValue,goalValue,refreshMillis=null,totalT
4643
4688
 
4644
4689
  doStep:function(callingObject=null,args=null){
4645
4690
 
4646
- if(!self.isStarted() || self.suspended) return self.value;
4691
+ if(!self.isStarted() || self.paused) return self.value;
4647
4692
 
4648
4693
  // Looping index with a delay :
4649
4694
 
@@ -5951,15 +5996,26 @@ function normalizeDisplayQuotesForJSONParsing(str){
5951
5996
 
5952
5997
  //A polyvalent popup :
5953
5998
  //CAUTION : Non-blocking of execution ! (uses callbacks)
5954
- function promptWindow(label,type=null,defaultValue=null,doOnOK=null,doOnCancel=null,config={displayMode:"popup",backgroundColor1:"#B3DCED",font:"helvetica"}){
5999
+ function promptWindow(label,type=null,defaultValue=null,
6000
+ doOnOK=null,doOnCancel=null,
6001
+ config={displayMode:"popup",backgroundColor1:"#B3DCED",font:"helvetica",width:"80%",height:"80%"}){
5955
6002
 
6003
+ const LABEL_AND_DEFAULT_VALUE_SEPARATOR=":";
5956
6004
  const MAX_Z_INDEX_OVERLAY_LOCAL=9999;// (Repeated because must be stand-alone constant...)
5957
- const WIDTH="90%";
5958
- const HEIGHT="90%";
6005
+ const promptWindowWidth=(config && config.width?config.width:"80%");
6006
+ const promptWindowHeight=(config && config.height?config.height:"80%");
6007
+ // DOES NOT WORK : const promptWindowWidth="auto", promptWindowHeight="auto";
6008
+
6009
+
5959
6010
  // const POSITION_X="7.5%";
5960
6011
  // const POSITION_Y="5%";
5961
6012
  const SPECIAL_SEPARATOR="@@@";
5962
6013
 
6014
+
6015
+ let resultPromiseOK=null;
6016
+ let resultPromiseCancel=null;
6017
+
6018
+
5963
6019
  let displayMode=config && config.displayMode ? config.displayMode:"popup";
5964
6020
  let backgroundColor1=config && config.backgroundColor1 ? config.backgroundColor1:"#B3DCED";
5965
6021
  let backgroundColor2=config && config.backgroundColor2 ? config.backgroundColor2:"#29B8E5";
@@ -5975,12 +6031,11 @@ function promptWindow(label,type=null,defaultValue=null,doOnOK=null,doOnCancel=n
5975
6031
  };
5976
6032
 
5977
6033
  //Each field rendering :
5978
- /* private */let getRenderedInputElement=function(inputType, defaultValueParam=null,/* OPTIONAL */labelParam=null){
6034
+ /* private */const getRenderedInputElement=function(inputType, defaultValueParam=null,/* OPTIONAL */labelParam=null){
5979
6035
 
5980
- const LABEL_AND_DEFAULT_VALUE_SEPARATOR=":";
5981
6036
  const DISABLED_OPACITY=0.6;
5982
6037
 
5983
- var inputElement=null;
6038
+ let inputElement=null;
5984
6039
  if(contains(inputType, "password")){
5985
6040
  inputElement=document.createElement("input");
5986
6041
  if(defaultValueParam) inputElement.value=defaultValueParam;
@@ -6000,7 +6055,7 @@ function promptWindow(label,type=null,defaultValue=null,doOnOK=null,doOnCancel=n
6000
6055
 
6001
6056
  } else if(contains(inputType, "uniqueChoice") || contains(inputType, "multipleChoice")){ // If we have a group of inputs for (a) value(s) outcome :
6002
6057
 
6003
- var choicesLabelsStrs=defaultValueParam;
6058
+ let choicesLabelsStrs=defaultValueParam;
6004
6059
  if(typeof(defaultValueParam) === "string"){
6005
6060
  choicesLabelsStrs=parseJSON(defaultValueParam);
6006
6061
  }
@@ -6008,97 +6063,99 @@ function promptWindow(label,type=null,defaultValue=null,doOnOK=null,doOnCancel=n
6008
6063
  inputElement=document.createElement("p");
6009
6064
  inputElement.style.display="flex";
6010
6065
 
6011
-
6012
6066
 
6013
6067
  // Represents a list with a unique or a multiple outcome value(s) :
6014
- foreach(choicesLabelsStrs,(choiceLabelStr,i)=>{
6015
-
6016
- let radioButtonContainer=document.createElement("div");
6017
- radioButtonContainer.style.width="200px";
6018
- // radioButtonContainer.style.height="400px";
6019
- radioButtonContainer.style["padding-right"]="10px";
6020
- radioButtonContainer.style["text-align"]="center";
6021
- radioButtonContainer.style["font-size"]="10px";
6022
- radioButtonContainer.style["line-height"]="10px";
6023
-
6024
-
6025
- let radio=document.createElement("input");
6068
+
6069
+ resultPromiseOK=new Promise((resolve, reject)=>{
6026
6070
 
6027
- if(contains(inputType, "multipleChoice")){ // multipleChoice :
6028
- radio.type="checkbox";
6029
- radio.id="checkboxMultipleChoice_" + i;
6030
- } else { // uniqueChoice :
6031
-
6032
-
6033
- if(contains(inputType, "icons")){
6034
-
6035
- radio.type="image";
6036
- if(typeof(choiceLabelStr) === "string"){
6037
- radio.src=choiceLabelStr.trim();
6038
- }else{
6071
+ foreach(choicesLabelsStrs,(choiceLabelStr,i)=>{
6072
+
6073
+ // Radio image card :
6074
+ const radioButtonContainer=document.createElement("div");
6075
+ radioButtonContainer.style.width="200px";
6076
+ // radioButtonContainer.style.height="400px";
6077
+ radioButtonContainer.style["padding-right"]="10px";
6078
+ radioButtonContainer.style["text-align"]="center";
6079
+ radioButtonContainer.style["font-size"]="10px";
6080
+ radioButtonContainer.style["line-height"]="10px";
6081
+ inputElement.appendChild(radioButtonContainer);
6082
+
6083
+
6084
+
6085
+ let radio=document.createElement("input");
6086
+ if(contains(inputType, "multipleChoice")){ // multipleChoice :
6087
+ radio.type="checkbox";
6088
+ radio.id="checkboxMultipleChoice_" + i;
6039
6089
 
6040
- if(choiceLabelStr.width)
6041
- radioButtonContainer.style.width=choiceLabelStr.width;
6042
- if(choiceLabelStr.height)
6043
- radioButtonContainer.style.height=choiceLabelStr.height;
6090
+ } else { // uniqueChoice :
6044
6091
 
6092
+ if(contains(inputType, "icons")){
6093
+
6094
+ radio.type="image";
6095
+ if(typeof(choiceLabelStr) === "string"){
6096
+ radio.src=choiceLabelStr.trim();
6097
+ }else{
6098
+
6099
+ if(choiceLabelStr.width) radioButtonContainer.style.width=choiceLabelStr.width;
6100
+ if(choiceLabelStr.height) radioButtonContainer.style.height=choiceLabelStr.height;
6101
+
6102
+ radio.src=choiceLabelStr.src.trim();
6103
+
6104
+ if(choiceLabelStr.inactive){
6105
+ radio.disabled=true;
6106
+ radio.style.opacity=DISABLED_OPACITY;
6107
+ }
6108
+ }
6109
+
6110
+ // Icon substitutes itself to the OK button :
6111
+
6112
+ radio.onclick=function(event){
6113
+ let promptedValue=event.target.indexValue;
6114
+
6115
+ if(!validationFunction(promptedValue)){
6116
+ // TRACE
6117
+ alert(validationFunction.errorMessage);
6118
+ return;
6119
+ }
6120
+
6121
+ if(doOnOK){
6122
+ const doOnOKResult=doOnOK(promptedValue);
6123
+ resolve({value:promptedValue,result:doOnOKResult});
6124
+ }
6125
+ closeWindow();
6126
+
6127
+ };
6128
+
6129
+
6130
+ }else{
6131
+ radio.type="radio";
6132
+ }
6045
6133
 
6046
- radio.src=choiceLabelStr.src.trim();
6134
+ radio.id="radioButtonUniqueChoice_" + i;
6135
+ radio.name="radioButtonsUniqueChoice";
6047
6136
 
6048
- if(choiceLabelStr.inactive){
6049
- radio.disabled=true;
6050
- radio.style.opacity=DISABLED_OPACITY;
6051
- }
6052
6137
  }
6053
6138
 
6054
- // Icon substitutes itself to the OK button :
6055
- radio.onclick=function(event){
6056
- var promptedValue=event.target.indexValue;
6057
-
6058
- if(!validationFunction(promptedValue)){
6059
- // TRACE
6060
- alert(validationFunction.errorMessage);
6061
- return;
6062
- }
6063
-
6064
- if(doOnOK){
6065
- doOnOK(promptedValue);
6066
- }
6067
- closeWindow();
6068
- };
6069
-
6070
-
6071
6139
 
6072
-
6073
- }else{
6074
-
6075
- radio.type="radio";
6076
- }
6077
-
6078
- radio.id="radioButtonUniqueChoice_" + i;
6079
- radio.name="radioButtonsUniqueChoice";
6080
-
6081
-
6082
- }
6083
-
6084
-
6085
- radio.indexValue=i;
6086
- radioButtonContainer.appendChild(radio);
6087
-
6088
- var choiceLabel=document.createElement("label");
6089
- if(typeof(choiceLabelStr) === "string"){
6090
- choiceLabel.innerHTML=choiceLabelStr.trim();
6091
- }else{
6092
- choiceLabel.innerHTML=choiceLabelStr.html.trim();
6093
- }
6094
- choiceLabel.htmlFor=radio.id;
6095
- radioButtonContainer.appendChild(choiceLabel);
6096
-
6097
-
6098
- inputElement.appendChild(radioButtonContainer);
6140
+ radio.indexValue=i;
6141
+ radioButtonContainer.appendChild(radio);
6142
+
6143
+ let choiceLabel=document.createElement("label");
6144
+ if(typeof(choiceLabelStr)==="string"){
6145
+ choiceLabel.innerHTML=choiceLabelStr.trim();
6146
+ }else{
6147
+ choiceLabel.innerHTML=choiceLabelStr.html.trim();
6148
+ }
6149
+ choiceLabel.htmlFor=radio.id;
6150
+ radioButtonContainer.appendChild(choiceLabel);
6099
6151
 
6100
-
6152
+
6153
+
6154
+ });
6155
+
6101
6156
  });
6157
+
6158
+
6102
6159
 
6103
6160
  // We redefine the function used to retrieve entered value :
6104
6161
  if(contains(inputType, "icons")){
@@ -6108,11 +6165,11 @@ function promptWindow(label,type=null,defaultValue=null,doOnOK=null,doOnCancel=n
6108
6165
  }else{
6109
6166
 
6110
6167
  inputElement.getResultValue=function(){
6111
- var result="";
6168
+ let result="";
6112
6169
 
6113
6170
  // OLD :
6114
6171
  // jQuery(inputElement).find("input:checked").each(function(){
6115
- // var radioSlct=jQuery(this);
6172
+ // let radioSlct=jQuery(this);
6116
6173
  // result+=(nothing(result) ? "" : ",") + radioSlct.get(0).indexValue;
6117
6174
  // });
6118
6175
 
@@ -6127,9 +6184,7 @@ function promptWindow(label,type=null,defaultValue=null,doOnOK=null,doOnCancel=n
6127
6184
  }
6128
6185
 
6129
6186
 
6130
-
6131
-
6132
- } else { // DEFAULT TYPE IS TEXT AREA :
6187
+ } else if(contains(inputType, "textarea")){
6133
6188
 
6134
6189
  inputElement=document.createElement("textarea");
6135
6190
  if(defaultValueParam)
@@ -6138,14 +6193,16 @@ function promptWindow(label,type=null,defaultValue=null,doOnOK=null,doOnCancel=n
6138
6193
  inputElement.rows="20";
6139
6194
 
6140
6195
  }
6196
+ // DEFAULT TYPE IS "yesno"
6141
6197
 
6198
+
6142
6199
  inputElement.style.padding="4px";
6143
6200
  inputElement.style.margin="0";
6144
6201
 
6145
6202
  // The default function used to retrieve entered value :
6146
6203
  if(!inputElement.getResultValue){
6147
6204
  inputElement.getResultValue=function(){
6148
- var result=inputElement.value;
6205
+ let result=inputElement.value;
6149
6206
  return result;
6150
6207
  };
6151
6208
  }
@@ -6180,10 +6237,12 @@ function promptWindow(label,type=null,defaultValue=null,doOnOK=null,doOnCancel=n
6180
6237
  if(contains(displayMode,":")){
6181
6238
  let s=displayMode.split(":");
6182
6239
  divModal.style.opacity=s[1];
6240
+ }else{
6241
+ divModal.style.opacity="0.85";// Default modal transparency value
6183
6242
  }
6184
6243
 
6185
6244
  divModal.appendChild(divWindow);
6186
- // parent.appendChild(divModal);
6245
+ // parent.appendChild(divModal);
6187
6246
 
6188
6247
  appendAsFirstChildOf(divModal, parent);
6189
6248
  }else{
@@ -6198,12 +6257,12 @@ function promptWindow(label,type=null,defaultValue=null,doOnOK=null,doOnCancel=n
6198
6257
 
6199
6258
  divWindow.className="promptWindowClass";
6200
6259
 
6201
- var style="";
6202
- style+="position:sticky;";
6203
- //style+="left:" + POSITION_X + ";top:" + POSITION_Y + ";z-index:" + (MAX_Z_INDEX_OVERLAY_LOCAL) + ";";
6204
- style+="margin:auto;";
6260
+ let style="";
6261
+ style+="position:absolute;";
6262
+ style+="left:50%;top:50%;";
6263
+ style+="z-index:" + (MAX_Z_INDEX_OVERLAY_LOCAL) + ";";
6205
6264
  style+="z-index:" + (MAX_Z_INDEX_OVERLAY_LOCAL) + ";";
6206
- style+="width:"+WIDTH+";height:"+HEIGHT+"; ";
6265
+ style+="width:"+promptWindowWidth+";height:"+promptWindowHeight+"; ";
6207
6266
  style+="border:#888888 8px dotted;padding:10px;opacity:1;";
6208
6267
  style+="font-family:"+font+";";
6209
6268
  style+="color:"+textColor+";";
@@ -6219,10 +6278,12 @@ function promptWindow(label,type=null,defaultValue=null,doOnOK=null,doOnCancel=n
6219
6278
 
6220
6279
  divWindow.style=style;
6221
6280
 
6222
-
6281
+ divWindow.style["margin-left"]=(-divWindow.offsetWidth/2)+"px";
6282
+ divWindow.style["margin-top"]= (-divWindow.offsetHeight/2)+"px";
6283
+
6223
6284
 
6224
6285
  // The label for the title of the window :
6225
- var labelElement=document.createElement("div");
6286
+ let labelElement=document.createElement("div");
6226
6287
  labelElement.innerHTML=label;
6227
6288
  labelElement.style="padding-top:0;padding-bottom:10px;";
6228
6289
  // Positioning :
@@ -6232,21 +6293,21 @@ function promptWindow(label,type=null,defaultValue=null,doOnOK=null,doOnCancel=n
6232
6293
  // The container containing all the elements :
6233
6294
  let divContainer=document.createElement("div");
6234
6295
  divContainer.id="divPromptContainer";
6235
- divContainer.style="overflow-y:auto;width:100%;height:80%;";
6296
+ divContainer.style="overflow-y:auto;width:100%;max-height:83%;";
6236
6297
  divWindow.appendChild(divContainer);
6237
6298
 
6238
6299
 
6239
6300
 
6240
6301
 
6241
6302
  // Eventual validation :
6242
- var validationFunction=function(value){
6303
+ let validationFunction=function(value){
6243
6304
  return true;/* DO NOTHING */
6244
6305
  };
6245
6306
  validationFunction.errorMessage="";
6246
6307
  if(!nothing(type) && contains(type, ":")){
6247
- var typeParams=type.split(":");
6308
+ let typeParams=type.split(":");
6248
6309
  if(typeParams.length > 1){
6249
- var validationType=typeParams[1];
6310
+ let validationType=typeParams[1];
6250
6311
 
6251
6312
  if(validationType === "email"){
6252
6313
  validationFunction=function(value){
@@ -6259,19 +6320,20 @@ function promptWindow(label,type=null,defaultValue=null,doOnOK=null,doOnCancel=n
6259
6320
 
6260
6321
 
6261
6322
  // We define the global function used to retrieve entered value :
6262
- var getGlobalResultValue=null;
6263
- var inputElements=new Array();
6323
+ let getGlobalResultValue=null;
6324
+ const inputElements=[];
6264
6325
 
6265
6326
  // Case the window is actually a form of several fields :
6266
6327
  if(!nothing(type) && containsIgnoreCase(type, "form{")){
6267
- var namesWithTypesAndDefaultValuesStr=type.replace("form{", "{");
6328
+
6329
+ let namesWithTypesAndDefaultValuesStr=type.replace("form{", "{");
6268
6330
  try {
6269
6331
 
6270
- var defaultValues=new Array();
6332
+ let defaultValues=new Array();
6271
6333
  if(!nothing(defaultValue)) defaultValues=defaultValue.split(SPECIAL_SEPARATOR);
6272
6334
 
6273
- var namesWithTypesAndDefaultValues=parseJSON(namesWithTypesAndDefaultValuesStr);
6274
- var j=0;
6335
+ let namesWithTypesAndDefaultValues=parseJSON(namesWithTypesAndDefaultValuesStr);
6336
+ let j=0;
6275
6337
  foreach(namesWithTypesAndDefaultValues,(inputItemDescriptionString,key)=>{
6276
6338
 
6277
6339
  let typeInputItem=inputItemDescriptionString;
@@ -6293,9 +6355,9 @@ function promptWindow(label,type=null,defaultValue=null,doOnOK=null,doOnCancel=n
6293
6355
 
6294
6356
  // We redefine the global function used to retrieve entered value :
6295
6357
  getGlobalResultValue=function(){
6296
- var result="";
6358
+ let result="";
6297
6359
 
6298
- for(var k=0; k<inputElements.length; k++){
6360
+ for(let k=0; k<inputElements.length; k++){
6299
6361
  result+=(nothing(result) ? "" : SPECIAL_SEPARATOR) + inputElements[k].getResultValue();
6300
6362
  }
6301
6363
 
@@ -6303,22 +6365,22 @@ function promptWindow(label,type=null,defaultValue=null,doOnOK=null,doOnCancel=n
6303
6365
  };
6304
6366
 
6305
6367
  } catch (e1){
6306
- var errorDiv=document.createElement("div");
6368
+ let errorDiv=document.createElement("div");
6307
6369
  errorDiv.innerHTML="Error parsing JSON «" + namesWithTypesAndDefaultValuesStr + "» : ERROR «:" + e1 + "»";
6308
6370
  inputElements.push(errorDiv);
6309
6371
  }
6310
6372
 
6311
6373
 
6312
- // UNUSED, AND DOES NOT WORK :
6313
- // }else if(type==="yesno"){ // Case yes/no
6314
- //
6315
- // // We redefine the global function used to retrieve entered value :
6316
- // getGlobalResultValue=function(){
6317
- // return null;
6318
- // };
6319
- }else { // Case of a single field :
6374
+ } else if(type==="yesno"){ // Case yes/no
6375
+
6376
+ // We redefine the global function used to retrieve entered value :
6377
+ getGlobalResultValue=function(){
6378
+ return null;
6379
+ };
6380
+
6381
+ } else { // Case of a single field :
6320
6382
 
6321
- var singleInputElement=getRenderedInputElement(type, defaultValue);
6383
+ let singleInputElement=getRenderedInputElement(type, defaultValue);
6322
6384
  inputElements.push(singleInputElement);
6323
6385
 
6324
6386
  // Focus on password field :
@@ -6335,50 +6397,65 @@ function promptWindow(label,type=null,defaultValue=null,doOnOK=null,doOnCancel=n
6335
6397
  // TODO : FIXME : Focus on text field is not working.... :,-(
6336
6398
  // inputElements[inputElements.length - 1].focus();
6337
6399
 
6338
- var buttonCancel=document.createElement("button");
6400
+ // This button is displayed in all cases :
6401
+ let buttonCancel=document.createElement("button");
6339
6402
  buttonCancel.innerHTML="Cancel";
6340
- buttonCancel.style="display:block;float:right;margin-top:8px;";
6341
- buttonCancel.onclick=function(){
6342
- var promptedValue=getGlobalResultValue();
6343
- if(doOnCancel){
6344
- doOnCancel(promptedValue);
6345
- }
6346
- closeWindow();
6347
- };
6348
-
6349
- // TODO : FISME : There should be a way to hide this OK button,
6350
- // when we are in a "radio-image" unique selection case,
6351
- // since the doOnOK() is triggered when we click on the "radio-image" icon,
6352
- // thus rendering this OK button unuseful !! :
6353
-
6354
- var buttonOK=document.createElement("button");
6355
- buttonOK.innerHTML="OK";
6356
- buttonOK.style="display:block;margin-top:8px;margin-bottom:10px;";
6357
- buttonOK.onclick=function(){
6358
- var promptedValue=getGlobalResultValue();
6359
-
6360
- if(promptedValue!=null && !validationFunction(promptedValue)){
6361
- // TRACE
6362
- alert(validationFunction.errorMessage);
6363
- return;
6364
- }
6403
+ buttonCancel.style="display:block;float:right;";
6404
+ resultPromiseCancel=new Promise((resolve, reject)=>{
6405
+ buttonCancel.onclick=function(){
6406
+ let promptedValue=getGlobalResultValue();
6407
+ if(doOnCancel){
6408
+ const doOnCancelResult=doOnCancel(promptedValue);
6409
+ resolve({value:promptedValue,result:doOnCancelResult});
6410
+ }
6411
+ closeWindow();
6412
+ };
6413
+ });
6414
+ divWindow.appendChild(buttonCancel);
6415
+
6365
6416
 
6366
- if(doOnOK){
6367
- doOnOK(promptedValue);
6368
- }
6369
- closeWindow();
6370
- };
6371
6417
 
6372
- for(var i=0; i<inputElements.length; i++){
6373
-
6418
+ // We hide this OK button, when we are in a "radio-image" unique selection case,
6419
+ // since the doOnOK() is triggered when we click on the "radio-image" icon,
6420
+ // thus rendering this OK button unuseful ! :
6421
+ if(nothing(type) || !contains(type, "uniqueChoice")){
6422
+
6423
+ let buttonOK=document.createElement("button");
6424
+ buttonOK.innerHTML="OK";
6425
+ buttonOK.style="display:block;margin-bottom:10px;";
6426
+
6427
+ resultPromiseOK=new Promise((resolve, reject)=>{
6428
+
6429
+ buttonOK.onclick=function(){
6430
+ let promptedValue=getGlobalResultValue();
6431
+
6432
+ if(promptedValue!=null && !validationFunction(promptedValue)){
6433
+ // TRACE
6434
+ alert(validationFunction.errorMessage);
6435
+ return;
6436
+ }
6437
+
6438
+ if(doOnOK){
6439
+ const doOnOKResult=doOnOK(promptedValue);
6440
+ resolve({value:promptedValue,result:doOnOKResult});
6441
+ }
6442
+ closeWindow();
6443
+ };
6444
+
6445
+ });
6446
+ divWindow.appendChild(buttonOK);
6447
+ }
6448
+
6449
+ // Final integration of the generated input elements :
6450
+ for(let i=0; i<inputElements.length; i++){
6374
6451
  if(0<i){
6375
- var br=document.createElement("br");
6452
+ const br=document.createElement("br");
6376
6453
  divContainer.appendChild(br);
6377
6454
  }
6378
6455
 
6379
6456
  // The label :
6380
6457
  if(inputElements[i].label){
6381
- var divLabel=document.createElement("div");
6458
+ const divLabel=document.createElement("div");
6382
6459
  divLabel.innerHTML=inputElements[i].label;
6383
6460
  divContainer.appendChild(divLabel);
6384
6461
  }
@@ -6386,14 +6463,16 @@ function promptWindow(label,type=null,defaultValue=null,doOnOK=null,doOnCancel=n
6386
6463
  divContainer.appendChild(inputElements[i]);
6387
6464
  }
6388
6465
 
6389
- // Positioning :
6390
- divWindow.appendChild(buttonCancel);
6391
- divWindow.appendChild(buttonOK);
6392
-
6393
6466
 
6467
+ return {
6468
+ toPromise:(issue="OK")=>(issue=="OK"?resultPromiseOK:resultPromiseCancel),
6469
+ toPromises:()=>{ return {OK:resultPromiseOK,cancel:resultPromiseCancel}; }
6470
+ };
6394
6471
  }
6395
6472
 
6396
6473
 
6474
+
6475
+
6397
6476
  getInputCoords=function(event){
6398
6477
  return (((event.clientX && event.clientY) || !event.touches) ? event : event.touches.item(0) );
6399
6478
  }
@@ -6689,63 +6768,416 @@ function getPointsFromSVGDString(str){
6689
6768
 
6690
6769
 
6691
6770
 
6692
- /*utils GEOMETRY library associated with aotra version : «1.0.0.000 (11/01/2022-00:18:33)»*/
6693
- /*-----------------------------------------------------------------------------*/
6694
6771
 
6695
6772
 
6696
- /* ## Utility global methods in a javascript, at least console (nodejs) server, or vanilla javascript with no browser environment.
6697
- *
6698
- * This set of methods gathers utility generic-purpose methods usable in any JS project.
6699
- * Several authors of snippets published freely on the Internet contributed to this library.
6700
- * Feel free to use/modify-enhance/publish them under the terms of its license.
6701
- *
6702
- * # Library name : «aotrautils»
6703
- * # Library license : HGPL(Help Burma) (see aotra README information for details : https://alqemia.com/aotra.js )
6704
- * # Author name : Jérémie Ratomposon (massively helped by its programming egos legions)
6705
- * # Author email : info@alqemia.com
6706
- * # Organization name : Alqemia
6707
- * # Organization email : admin@alqemia.com
6708
- * # Organization website : https://alqemia.com
6709
- *
6710
- *
6711
- */
6712
6773
 
6713
6774
 
6714
- // COMPATIBILITY browser javascript / nodejs environment :
6715
6775
 
6716
- if(typeof(window)==="undefined") window=global;
6776
+ // -Client :
6717
6777
 
6778
+ // TODO : Use everywhere it's applicable !
6779
+ initClient=function(isNodeContext=true, useSocketIOImplementation=false, doOnServerConnection=null, url, /*OPTIONAL*/port=25000, isSecure=true, /*OPTIONAL*/timeout=10000, /*OPTIONAL*/selfParam=null){
6780
+ let aotraClient={};
6781
+
6782
+ aotraClient.client={};
6783
+ aotraClient.client.start=function(){
6784
+
6785
+
6786
+ let socketToServer=WebsocketImplementation.getStatic(isNodeContext,useSocketIOImplementation).connectToServer(url, port, isSecure, timeout);
6787
+ if(!socketToServer){
6788
+ // TRACE
6789
+ lognow("ERROR : CLIENT : Could not get socketToServer, aborting client connection.");
6790
+ return null;
6791
+ }
6792
+
6793
+ // When client is connected, we execute the callback :
6794
+ // CAUTION : MUST BE CALLED ONLY ONCE !
6795
+ socketToServer.onConnectionToServer(()=>{
6796
+ if(doOnServerConnection){
6797
+ if(selfParam) doOnServerConnection.apply(selfParam,[socketToServer]);
6798
+ else doOnServerConnection(socketToServer);
6799
+ }
6800
+ });
6718
6801
 
6802
+ // // Errors handling :
6803
+ // if(doOnError && useSocketIOImplementation /*TODO : FIXME: FOR NOW, ONLY WORKS FOR SOCKET.IO IMPLEMENTATION !'*/){
6804
+ //
6805
+ //// // DBG
6806
+ //// lognow(">>> serverSocket.connected:"+serverSocket.connected);
6807
+ //// lognow(">>> serverSocket:",serverSocket);
6808
+ //
6809
+ //
6810
+ // const errorMethod=function(){
6811
+ // // TRACE
6812
+ // lognow("ERROR : CLIENT : Client encountered an error while trying to connect to server.");
6813
+ //
6814
+ // if(selfParam) doOnError.apply(selfParam);
6815
+ // else doOnError();
6816
+ // };
6817
+ //
6818
+ // socketToServer.clientSocket.on("connect_error", errorMethod);
6819
+ //
6820
+ //// // DOES NOT SEEM TO WORK :
6821
+ //// socketToServer.on("connect_failed", errorMethod);
6822
+ //// // DOES NOT SEEM TO WORK :
6823
+ //// socketToServer.on("error", errorMethod);
6824
+ //
6825
+ // }
6826
+ //
6827
+ // // Data messages handling :
6828
+ // if(doOnMessage){
6829
+ // socketToServer.onIncomingMessage((message)=>{
6830
+ // if(selfParam) doOnMessage.apply(selfParam,[message]);
6831
+ // else doOnMessage(message);
6832
+ // });
6833
+ // }
6834
+
6835
+ // DBG
6836
+ lognow("aotraClient.clientSocket:",aotraClient.clientSocket);
6837
+
6838
+ // DBG
6839
+ // aotraClient.clientSocket.addEventListener("close",()=>{
6840
+ // // TRACE
6841
+ // lognow("WARN : CONNECTION CLOSED !");
6842
+ // });
6719
6843
 
6720
- //=========================================================================
6721
- // GLOBAL CONSTANTS :
6844
+
6845
+ aotraClient.client.socketToServer=socketToServer;
6846
+
6847
+ return aotraClient;
6848
+ };
6849
+
6850
+
6851
+ return aotraClient;
6852
+ }
6722
6853
 
6723
6854
 
6724
- Math.TAU=6.28318530718; // == Math.PI*2
6725
- Math.PSI=1.57079632679; // == Math.PI/2
6726
- Math.TAN_RATIOS_DEGREES={
6727
- 22.5:0.4142135624,
6728
- 45:1,
6729
- 67.5:2.4142135624,
6730
- 112.5:-2.4142135624,
6731
- 135:-1,
6732
- 157.5:-0.4142135624,
6733
- 202.5:0.4142135624,
6734
- 225:1,
6735
- 247.5:2.4142135624,
6736
- 292.5:-2.4142135624,
6737
- 315:-1,
6738
- 337.5:-0.4142135624
6739
- };
6740
- Math.LN_2_INVERTED=1.44269504089; // == 1/ln(2)
6741
6855
 
6742
- Math.INVERSE_OF_SQUARE_OF_TWO=0.707106781; // == 1/sqrt(2)
6743
- Math.RATIO_TO_DEGREES=57.295779513; // == 180/PI
6744
- Math.RATIO_TO_RADIANS=0.017453293; // == PI/180
6745
6856
 
6857
+ // ========================= FUSRODA CLIENT : =========================
6746
6858
 
6859
+ class VNCFrame2D{
6860
+
6861
+ constructor(url, port=6080, isSecure=true, htmlConfig={parentElement:null,imageMode:"CSSBackground"}, mouseEventsRefreshMillis=100){
6862
+
6863
+
6864
+ this.url=url;
6865
+ this.port=port;
6866
+ this.isSecure=isSecure;
6867
+ this.parentElement=nonull(htmlConfig.parentElement, document.body);
6868
+ this.imageMode=htmlConfig.imageMode;
6869
+ this.mouseEventsRefreshMillis=mouseEventsRefreshMillis;
6747
6870
 
6748
- //=========================================================================
6871
+
6872
+ this.fusrodaClient=null;
6873
+
6874
+
6875
+ this.screenAndAudioConfig=null;
6876
+ this.image=null;
6877
+ self.audioCtx=null;
6878
+
6879
+ if(this.imageMode=="canvas"){
6880
+ this.canvas=document.createElement("canvas");
6881
+ this.parentElement.appendChild(this.canvas);
6882
+ }
6883
+
6884
+ this.lastPointerMoveTime=null;
6885
+
6886
+ }
6887
+
6888
+
6889
+ start(){
6890
+
6891
+ const self=this;
6892
+ this.fusrodaClient=createFusrodaClient(
6893
+ // doOnClientReady:
6894
+ (messageConfig)=>{
6895
+
6896
+ self.screenAndAudioConfig=messageConfig;
6897
+
6898
+ if(self.imageMode=="canvas"){
6899
+ self.image=new Image();
6900
+ self.image.addEventListener("load",(event)=>{
6901
+
6902
+
6903
+
6904
+ const image=event.target;
6905
+ image.isImageLoading=false;
6906
+
6907
+ if(!self.canvasSizeHasBeenSet){
6908
+ self.canvas.width=image.width;
6909
+ self.canvas.height=image.height;
6910
+ self.canvasSizeHasBeenSet=true;
6911
+ }
6912
+
6913
+ self.canvas.getContext("2d").drawImage(image,0,0);
6914
+
6915
+
6916
+ self.fusrodaClient.client.socketToServer.send("screenFrameRequest", {});
6917
+
6918
+ });
6919
+
6920
+ }
6921
+
6922
+
6923
+ self.audioCtx=new AudioContext({sampleRate: self.screenAndAudioConfig.sampleRate});
6924
+
6925
+
6926
+ },// doOnDataReception:
6927
+ {
6928
+ "image":(imageDataStr)=>{
6929
+
6930
+
6931
+ const base64String="data:image/png;base64,"+imageDataStr;
6932
+ if(self.imageMode=="canvas"){
6933
+ if(self.image.complete){
6934
+ self.image.src=base64String;
6935
+ self.image.isImageLoading=true;
6936
+ }
6937
+ }else{
6938
+ self.parentElement.style["background-image"]="url('"+base64String+"');";
6939
+ }
6940
+
6941
+
6942
+ },
6943
+ "sound":(soundDataStr)=>{
6944
+ const soundData=getDecodedArrayFromSoundDataString(soundDataStr);
6945
+ playAudioData(soundData,self.audioCtx,self.screenAndAudioConfig.audioBufferSize,1,self.screenAndAudioConfig.sampleRate,()=>{
6946
+ // doOnEndedPlaying
6947
+
6948
+ self.fusrodaClient.client.socketToServer.send("soundSampleRequest", {});
6949
+
6950
+ });
6951
+ }
6952
+ }, this.url, this.port, this.isSecure);
6953
+
6954
+
6955
+ // TODO : FIXME : DUPLICATED CODE !
6956
+ this.parentElement.addEventListener("pointermove",(event)=>{
6957
+
6958
+ const config=self.screenAndAudioConfig;
6959
+ if(!config || !self.canvas.width) return;
6960
+
6961
+
6962
+ if(!hasDelayPassed(self.lastPointerMoveTime,self.mouseEventsRefreshMillis)) return;
6963
+ self.lastPointerMoveTime=getNow();
6964
+
6965
+
6966
+ const width=config.width;
6967
+ const height=config.height;
6968
+
6969
+ const x=Math.floor((event.clientX/self.canvas.width)*width);
6970
+ const y=Math.floor((event.clientY/self.canvas.height)*height);
6971
+
6972
+
6973
+ self.fusrodaClient.sendMouseMoveEvent({x:x,y:y});
6974
+
6975
+ // DBG
6976
+ lognow("sendMouseMoveEvent:"+x+";"+y);
6977
+
6978
+ });
6979
+ // TODO : FIXME : DUPLICATED CODE !
6980
+ this.parentElement.addEventListener("mousedown",(event)=>{
6981
+ const mouseButton=event.button;
6982
+ self.fusrodaClient.sendMouseClickEvent({mouseButton:mouseButton});
6983
+
6984
+ // DBG
6985
+ lognow("mousedown:",event);
6986
+ });
6987
+ // TODO : FIXME : DUPLICATED CODE !
6988
+ this.parentElement.addEventListener("mouseup",(event)=>{
6989
+ const mouseButton=event.button;
6990
+ self.fusrodaClient.sendMouseReleasedEvent({mouseButton:mouseButton});
6991
+ });
6992
+ // TODO : FIXME : DUPLICATED CODE !
6993
+ const WHEEL_EVENT_FACTOR=.01;
6994
+ this.parentElement.addEventListener("wheel",(event)=>{
6995
+ const wheelAmount=event.deltaY*WHEEL_EVENT_FACTOR;
6996
+ self.fusrodaClient.sendWheelEvent({wheelAmount:wheelAmount});
6997
+
6998
+ // DBG
6999
+ lognow("wheel:",event);
7000
+ });
7001
+ // TODO : FIXME : DUPLICATED CODE !
7002
+ // To remove the local contextual menu :
7003
+ this.parentElement.addEventListener("contextmenu",(event)=>{event.preventDefault();});
7004
+
7005
+ // TODO : FIXME : DUPLICATED CODE !
7006
+ this.parentElement.addEventListener("keydown",(event)=>{
7007
+ let keyChar=event.key;
7008
+ const keyCode=event.keyCode;
7009
+
7010
+ // To avoid server-side «A JSONObject text must end with '}'» errors :
7011
+ if(keyChar==="{") keyChar="left_brace";
7012
+ if(keyChar==="}") keyChar="right_brace";
7013
+ self.fusrodaClient.sendKeyboardPressedEvent({keyChar:keyChar,keyCode:keyCode});
7014
+ });
7015
+ // TODO : FIXME : DUPLICATED CODE !
7016
+ this.parentElement.addEventListener("keyup",(event)=>{
7017
+ let keyChar=event.key;
7018
+ const keyCode=event.keyCode;
7019
+ // DBG
7020
+ lognow("!!! keydown KEYUP event:[event.key:"+event.key+"][keyCode:"+keyCode+"][keyChar:"+keyChar+"]",event);
7021
+
7022
+ // To avoid server-side «A JSONObject text must end with '}'» errors :
7023
+ if(keyChar==="{") keyChar="left_brace";
7024
+ if(keyChar==="}") keyChar="right_brace";
7025
+ self.fusrodaClient.sendKeyboardReleasedEvent({keyChar:keyChar,keyCode:keyCode});
7026
+ });
7027
+
7028
+
7029
+
7030
+ this.fusrodaClient.client.start();
7031
+
7032
+
7033
+ return this;
7034
+ }
7035
+
7036
+ }
7037
+
7038
+
7039
+
7040
+ /*private*/getDecodedArrayFromSoundDataString=function(soundDataStr){
7041
+ const decodedIntsArray=[];
7042
+
7043
+ const decodedString=atob(soundDataStr);
7044
+
7045
+ //// DBG
7046
+ //const decodedString=soundDataStr;
7047
+
7048
+ for(let i=0;i<decodedString.length;i++){
7049
+ const dataInt=decodedString.charCodeAt(i)-127;
7050
+
7051
+ // CAUTION : audio needs to be in [-1.0; 1.0]
7052
+ decodedIntsArray.push(dataInt/127);
7053
+ }
7054
+
7055
+ return decodedIntsArray;
7056
+ }
7057
+
7058
+
7059
+
7060
+ createFusrodaClient=function(doOnClientReady, doOnDataReception, urlParam=null, portParam=6080){
7061
+ // CAUTION : WORKS BETTER WHEN UNSECURE, BUT 'NEEDS CLIENT BROWSER TO ALLOW MIXED (SECURE/UNSECURE) CONTENT !
7062
+
7063
+ const isSecure=(contains(urlParam.toLowerCase(),"https") || contains(urlParam.toLowerCase(),"wss"));
7064
+
7065
+
7066
+ const fusrodaClient=initClient(false,true, (browserClientInstance)=>{
7067
+
7068
+ // DBG
7069
+ lognow("SETTING UP");
7070
+
7071
+ //0)
7072
+ fusrodaClient.client.socketToServer.send("protocol", { type:"hello" });
7073
+ //1)
7074
+ browserClientInstance.receive("protocolConfig", (messageConfig)=>{
7075
+ //2)
7076
+ fusrodaClient.client.socketToServer.send("screenFrameRequest", {});
7077
+ browserClientInstance.receive("imagePacket", (doOnDataReception && doOnDataReception["image"]?doOnDataReception["image"]:()=>{/*DO NOTHING*/}));
7078
+
7079
+ //2)
7080
+ fusrodaClient.client.socketToServer.send("soundSampleRequest", {});
7081
+ browserClientInstance.receive("soundPacket", (doOnDataReception && doOnDataReception["sound"]?doOnDataReception["sound"]:()=>{/*DO NOTHING*/}));
7082
+
7083
+
7084
+ doOnClientReady(messageConfig);
7085
+ });
7086
+
7087
+
7088
+ }, urlParam, portParam, isSecure);
7089
+
7090
+ fusrodaClient.sendMouseMoveEvent=(mouseEvent)=>{
7091
+ fusrodaClient.client.socketToServer.send("mouseMoveEvent", mouseEvent);
7092
+ };
7093
+
7094
+ fusrodaClient.sendMouseClickEvent=(mouseEvent)=>{
7095
+ fusrodaClient.client.socketToServer.send("mouseClickEvent", mouseEvent);
7096
+ };
7097
+ fusrodaClient.sendMouseReleasedEvent=(mouseEvent)=>{
7098
+ fusrodaClient.client.socketToServer.send("mouseReleasedEvent", mouseEvent);
7099
+ };
7100
+ fusrodaClient.sendWheelEvent=(wheelEvent)=>{
7101
+ fusrodaClient.client.socketToServer.send("wheelEvent", wheelEvent);
7102
+ };
7103
+
7104
+ fusrodaClient.sendKeyboardPressedEvent=(keyboardEvent)=>{
7105
+ fusrodaClient.client.socketToServer.send("keyboardPressedEvent", keyboardEvent);
7106
+ };
7107
+ fusrodaClient.sendKeyboardReleasedEvent=(keyboardEvent)=>{
7108
+ fusrodaClient.client.socketToServer.send("keyboardReleasedEvent", keyboardEvent);
7109
+ };
7110
+
7111
+
7112
+
7113
+ // // DBG
7114
+ // fusrodaClient.clientSocket.addEventListener("close",()=>{
7115
+ // lognow("CONNECTION CLOSED !");
7116
+ // });
7117
+
7118
+ return fusrodaClient;
7119
+ }
7120
+
7121
+
7122
+
7123
+
7124
+ /*utils GEOMETRY library associated with aotra version : «1.0.0.000 (11/07/2022-20:58:07)»*/
7125
+ /*-----------------------------------------------------------------------------*/
7126
+
7127
+
7128
+ /* ## Utility global methods in a javascript, at least console (nodejs) server, or vanilla javascript with no browser environment.
7129
+ *
7130
+ * This set of methods gathers utility generic-purpose methods usable in any JS project.
7131
+ * Several authors of snippets published freely on the Internet contributed to this library.
7132
+ * Feel free to use/modify-enhance/publish them under the terms of its license.
7133
+ *
7134
+ * # Library name : «aotrautils»
7135
+ * # Library license : HGPL(Help Burma) (see aotra README information for details : https://alqemia.com/aotra.js )
7136
+ * # Author name : Jérémie Ratomposon (massively helped by his native country free education system)
7137
+ * # Author email : info@alqemia.com
7138
+ * # Organization name : Alqemia
7139
+ * # Organization email : admin@alqemia.com
7140
+ * # Organization website : https://alqemia.com
7141
+ *
7142
+ *
7143
+ */
7144
+
7145
+
7146
+ // COMPATIBILITY browser javascript / nodejs environment :
7147
+
7148
+ if(typeof(window)==="undefined") window=global;
7149
+
7150
+
7151
+
7152
+ //=========================================================================
7153
+ // GLOBAL CONSTANTS :
7154
+
7155
+
7156
+ Math.TAU=6.28318530718; // == Math.PI*2
7157
+ Math.PSI=1.57079632679; // == Math.PI/2
7158
+ Math.TAN_RATIOS_DEGREES={
7159
+ 22.5:0.4142135624,
7160
+ 45:1,
7161
+ 67.5:2.4142135624,
7162
+ 112.5:-2.4142135624,
7163
+ 135:-1,
7164
+ 157.5:-0.4142135624,
7165
+ 202.5:0.4142135624,
7166
+ 225:1,
7167
+ 247.5:2.4142135624,
7168
+ 292.5:-2.4142135624,
7169
+ 315:-1,
7170
+ 337.5:-0.4142135624
7171
+ };
7172
+ Math.LN_2_INVERTED=1.44269504089; // == 1/ln(2)
7173
+
7174
+ Math.INVERSE_OF_SQUARE_OF_TWO=0.707106781; // == 1/sqrt(2)
7175
+ Math.RATIO_TO_DEGREES=57.295779513; // == 180/PI
7176
+ Math.RATIO_TO_RADIANS=0.017453293; // == PI/180
7177
+
7178
+
7179
+
7180
+ //=========================================================================
6749
7181
 
6750
7182
 
6751
7183
  // ==================== COMMONS Geometry ====================
@@ -6893,6 +7325,14 @@ Math.polarPositionRadiansToCartesianPosition3D=(distance,gamma,alpha,xOffset=0,y
6893
7325
  return {x:x,y:y,z:z};
6894
7326
  }
6895
7327
 
7328
+ // DOES NOT SEEM TO WORK :
7329
+ //Math.getArcCoordinatesOnSphereFromCartesianVector=function(vector3D,sphereRadius=1){
7330
+ // const zAxisAngle=(vector3D.x==0?Math.PI*.5:Math.atan(vector3D.y/vector3D.x));
7331
+ // const yAxisAngle=(vector3D.z==0?0:Math.atan(vector3D.x/vector3D.z));
7332
+ // const y=yAxisAngle*sphereRadius;
7333
+ // const x=zAxisAngle*sphereRadius;
7334
+ // return {x:x,y:y};
7335
+ //}
6896
7336
 
6897
7337
 
6898
7338
  Math.expApprox=function(x){
@@ -7005,9 +7445,9 @@ window.calculateAngleRadians2D=function(p1, p2, approximate=false){
7005
7445
  //}
7006
7446
 
7007
7447
  window.calculateAngleRadians3D=function(p1, p2, approximate=false){
7008
- let a=calculateAngleRadians2D({x:p1.x, y:p1.z}, {x:p2.x, y:p2.z}, approximate);
7009
- let b=calculateAngleRadians2D({x:p1.x, y:p1.y}, {x:p2.x, y:p2.y}, approximate);
7010
- let g=calculateAngleRadians2D({x:p1.z, y:p1.y}, {x:p2.z, y:p2.y}, approximate);
7448
+ let a=calculateAngleRadians2D({x:p1.x, y:p1.y}, {x:p2.x, y:p2.y}, approximate); // alpha => angle [x,y]
7449
+ let b=calculateAngleRadians2D({x:p1.z, y:p1.y}, {x:p2.z, y:p2.y}, approximate); // beta => angle [z,y]
7450
+ let g=calculateAngleRadians2D({x:p1.x, y:p1.z}, {x:p2.x, y:p2.z}, approximate); // gamma => angle [x,z]
7011
7451
  return {a:a, b:b, g:g};
7012
7452
  }
7013
7453
 
@@ -7017,15 +7457,21 @@ window.calculateLinearlyMovedPoint2DPolar=function(currentPoint, angleRadians, l
7017
7457
  return {x:x, y:y};
7018
7458
  }
7019
7459
 
7460
+
7461
+ // NEW : WE ACTUALL ONLY NEED 2 ANGLES ! BETA angle in plan [Z,Y] AND GAMMA angle in plan [X,Z] !
7020
7462
  window.calculateLinearlyMovedPoint3DPolar=function(currentPoint, anglesRadians, linearMotion){
7463
+
7464
+ // IN THIS NORM (MOBILE NORM:)
7465
+ // alpha => angle [x,y]
7466
+ // beta => angle [z,y]
7467
+ // gamma => angle [x,z]
7468
+
7469
+ let translatedBeta= calculateLinearlyMovedPoint2DPolar({x:currentPoint.z, y:currentPoint.y}, anglesRadians.b, linearMotion);
7470
+ let translatedGamma=calculateLinearlyMovedPoint2DPolar({x:currentPoint.x, y:currentPoint.z}, anglesRadians.g, linearMotion);
7021
7471
 
7022
- let translatedAlpha=calculateLinearlyMovedPoint2DPolar({x:currentPoint.x, y:currentPoint.z}, anglesRadians.a, linearMotion);
7023
- let translatedBeta= calculateLinearlyMovedPoint2DPolar({x:currentPoint.x, y:currentPoint.y}, anglesRadians.b, linearMotion);
7024
- let translatedGamma=calculateLinearlyMovedPoint2DPolar({x:currentPoint.z, y:currentPoint.y}, anglesRadians.g, linearMotion);
7025
-
7026
- let x=translatedAlpha.x+translatedBeta.x;
7027
- let y= translatedBeta.y+translatedGamma.y;
7028
- let z=translatedAlpha.y +translatedGamma.x;
7472
+ let x=translatedGamma.x;
7473
+ let y=translatedBeta.y;
7474
+ let z=translatedBeta.x;
7029
7475
 
7030
7476
  return {x:x, y:y, z:z};
7031
7477
  }
@@ -7493,12 +7939,6 @@ Math.getQuadrant=function(centerPoint,point){
7493
7939
 
7494
7940
 
7495
7941
 
7496
-
7497
-
7498
-
7499
-
7500
-
7501
-
7502
7942
  Math.compareDistances=function(p1, p2, distance, comparison){
7503
7943
  let distanceSquare=Math.pow(distance, 2);
7504
7944
  let diffSquare=Math.getSquaredDistance(p1, p2);
@@ -7516,7 +7956,7 @@ Math.compareDistances=function(p1, p2, distance, comparison){
7516
7956
  };
7517
7957
 
7518
7958
  Math.getSquaredDistance=function(p1, p2){
7519
- return Math.pow(Math.abs(p1.x - p2.x), 2) + Math.pow(Math.abs(p1.y - p2.y), 2);
7959
+ return Math.pow(Math.abs(p2.x-p1.x), 2) + Math.pow(Math.abs(p2.y-p1.y), 2) + ((p1.z!=null && p2.z!=null)?Math.pow(Math.abs(p2.z-p1.z), 2):0);
7520
7960
  };
7521
7961
 
7522
7962
  Math.getDistance=function(p1, p2){
@@ -7740,1287 +8180,154 @@ Math.getBidimensional8Direction=(currentPosition,destination,invertYAxis=false)=
7740
8180
  intersectionXMin=xMin2; // Basically zone2 x !
7741
8181
  intersectionXMax=xMax2;
7742
8182
  // We calculate edges :
7743
- hasXAxisWholeZone2=true;
7744
- }
7745
- }
7746
-
7747
- let w=intersectionXMax-intersectionXMin;
7748
- let x=(intersectionXMax+intersectionXMin)*.5; // (The average)
7749
-
7750
-
7751
- // Vertical axis :
7752
-
7753
- let hasYAxisWholeZone1=false;
7754
- let hasYAxisWholeZone2=false;
7755
-
7756
- let demiHeight1=zone1.h*.5;
7757
- let demiHeight2=zone2.h*.5;
7758
-
7759
- let yMin1=zone1.y-demiHeight1;
7760
- let yMax1=zone1.y+demiHeight1;
7761
- let yMin2=zone2.y-demiHeight2;
7762
- let yMax2=zone2.y+demiHeight2;
7763
-
7764
-
7765
- let deltaYMins=yMin2-yMin1;
7766
- let deltaYMaxs=yMax2-yMax1;
7767
-
7768
- let deltaYMinsSign=(deltaYMins<0?-1:1);
7769
- let deltaYMaxsSign=(deltaYMaxs<0?-1:1);
7770
- let intersectionYMin=0;
7771
- let intersectionYMax=0;
7772
- if(deltaYMinsSign==deltaYMaxsSign){ // case «parallelogram»
7773
- if(deltaYMinsSign<0){ // case /=/ parallelogram
7774
- intersectionYMin=yMin1;
7775
- intersectionYMax=yMax2;
7776
- // We calculate edges :
7777
- edges.push("bottom"); // Here the Y axis is NOT inverted !
7778
- }else{ // case \=\ parallelogram
7779
- intersectionYMin=yMin2;
7780
- intersectionYMax=yMax1;
7781
- // We calculate edges :
7782
- edges.push("top"); // Here the Y axis is NOT inverted !
7783
- }
7784
- }else{ // case «trapeze»
7785
- if(deltaYMinsSign<0){ // case /=\ trapeze
7786
- intersectionYMin=yMin1; // Basically zone2 y !
7787
- intersectionYMax=yMax1;
7788
- // We calculate edges :
7789
- hasYAxisWholeZone1=true;
7790
- }else{ // case \=/ trapeze
7791
- intersectionYMin=yMin2; // Basically zone2 y !
7792
- intersectionYMax=yMax2;
7793
- // We calculate edges :
7794
- hasYAxisWholeZone2=true;
7795
- }
7796
- }
7797
-
7798
- let h=intersectionYMax-intersectionYMin;
7799
- let y=(intersectionYMax+intersectionYMin)*.5; // (The average)
7800
-
7801
- // We calculate edges :
7802
- if(hasXAxisWholeZone1 && hasYAxisWholeZone1) edges.push("wholeZone1");
7803
- if(hasXAxisWholeZone2 && hasYAxisWholeZone2) edges.push("wholeZone2");
7804
-
7805
- return {x:x,y:y,w:w,h:h,edges:edges};
7806
- }
7807
-
7808
- /*public static*/window.areZonesIdentical=function(zone1, zone2){
7809
- return (zone1==zone2 || (zone1.x==zone2.x || zone1.y==zone2.y || zone1.w==zone2.w || zone1.h==zone2.h));
7810
- }
7811
-
7812
-
7813
-
7814
- // 3D :
7815
-
7816
-
7817
- /*
7818
- // (needs the jsgl library file !)
7819
- // UNUSED
7820
- function screenToSpherePt(mouseX, mouseY) {
7821
- var p = { x: camera_mat.e12, y: camera_mat.e13, z: camera_mat.e14 + 1 };
7822
- // camera dir
7823
- var r = { x: camera_mat.e0, y: camera_mat.e1, z: camera_mat.e2 };
7824
- var up = { x: camera_mat.e4, y: camera_mat.e5, z: camera_mat.e6 };
7825
- var right = { x: camera_mat.e8, y: camera_mat.e9, z: camera_mat.e10 };
7826
- var tan_half = Math.tan(horizontal_fov_radians / 2);
7827
- r = jsgl.vectorAdd(r, jsgl.vectorScale(right, mouseX * tan_half));
7828
- r = jsgl.vectorAdd(r, jsgl.vectorScale(up, mouseY * tan_half));
7829
- r = jsgl.vectorNormalize(r);
7830
-
7831
- return rayVsUnitSphereClosestPoint(p, r);
7832
- }
7833
- */
7834
-
7835
- /*
7836
- // (needs the jsgl library file !)
7837
- // UNUSED
7838
- // Return the first exterior hit or closest point between the unit
7839
- // sphere and the ray starting at p and going in the r direction.
7840
- function rayVsUnitSphereClosestPoint(p, r) {
7841
- var p_len2 = jsgl.dotProduct(p, p);
7842
- if (p_len2 < 1) {
7843
- // Ray is inside sphere, no exterior hit.
7844
- return null;
7845
- }
7846
-
7847
- var along_ray = -jsgl.dotProduct(p, r);
7848
- if (along_ray < 0) {
7849
- // Behind ray start-point.
7850
- return null;
7851
- }
7852
-
7853
- var perp = jsgl.vectorAdd(p, jsgl.vectorScale(r, along_ray));
7854
- var perp_len2 = jsgl.dotProduct(perp, perp);
7855
- if (perp_len2 >= 0.999999) {
7856
- // Return the closest point.
7857
- return jsgl.vectorNormalize(perp);
7858
- }
7859
-
7860
- // Compute intersection.
7861
- var e = Math.sqrt(1 - jsgl.dotProduct(perp, perp));
7862
- var hit = jsgl.vectorAdd(p, jsgl.vectorScale(r, (along_ray - e)));
7863
- return jsgl.vectorNormalize(hit);
7864
- }
7865
- */
7866
-
7867
-
7868
-
7869
-
7870
-
7871
-
7872
-
7873
-
7874
-
7875
-
7876
-
7877
-
7878
-
7879
-
7880
-
7881
-
7882
-
7883
-
7884
-
7885
-
7886
-
7887
- // MUST REMAIN AT THE END OF THIS LIBRARY FILE !
7888
-
7889
- AOTRAUTILS_GEOMETRY_LIB_IS_LOADED=true;/*utils SERVER library associated with aotra version : «1.0.0.000 (11/01/2022-00:18:33)»*/
7890
- /*-----------------------------------------------------------------------------*/
7891
-
7892
-
7893
- /* ## Utility global methods in a javascript, console (nodejs) server, or vanilla javascript with no browser environment.
7894
- *
7895
- * This set of methods gathers utility generic-purpose methods usable in any JS project.
7896
- * Several authors of snippets published freely on the Internet contributed to this library.
7897
- * Feel free to use/modify-enhance/publish them under the terms of its license.
7898
- *
7899
- * # Library name : «aotrautils»
7900
- * # Library license : HGPL(Help Burma) (see aotra README information for details : https://alqemia.com/aotra.js )
7901
- * # Author name : Jérémie Ratomposon (massively helped by its programming egos legions)
7902
- * # Author email : info@alqemia.com
7903
- * # Organization name : Alqemia
7904
- * # Organization email : admin@alqemia.com
7905
- * # Organization website : https://alqemia.com
7906
- *
7907
- *
7908
- */
7909
-
7910
-
7911
-
7912
- // COMPATIBILITY browser javascript / nodejs environment :
7913
-
7914
- if(typeof(window)==="undefined") window=global;
7915
-
7916
-
7917
-
7918
-
7919
- // =================================================================================
7920
- // NODEJS UTILS
7921
-
7922
- const FILE_ENCODING="utf8";
7923
-
7924
-
7925
- // Nodejs filesystem utils :
7926
- if(typeof(fs)==="undefined"){
7927
- // TRACE
7928
- console.log("WARN : Could not find the nodejs dependency «fs», aborting persister setup.");
7929
- window.getPersister=()=>{ return null; };
7930
-
7931
- }else{
7932
-
7933
-
7934
- window.getPersister=function(dataDirPath,suffix=""){
7935
-
7936
- let self={
7937
-
7938
- dataDirPath:dataDirPath,
7939
- suffix:suffix,
7940
-
7941
- //FILE_NAME_PATTERN:"data.clientId.repositoryName.json",
7942
- /*private*/getPath:function(clientId,repositoryName){
7943
- // let path=self.FILE_NAME_PATTERN.replace(new RegExp("@clientId@","g"),clientId);
7944
- let path=`${self.dataDirPath}`
7945
- + (blank(self.suffix)?"":(self.suffix+"."))
7946
- +`${clientId}.${repositoryName}.json`;
7947
- return path;
7948
- },
7949
-
7950
- readTreeObjectFromFile:function(clientId, repositoryName, CLASSNAME_ATTR_NAME=DEFAULT_CLASSNAME_ATTR_NAME){
7951
-
7952
- let path=self.getPath(clientId,repositoryName);
7953
-
7954
- let resultFlat=null;
7955
-
7956
- try{
7957
- resultFlat=fs.readFileSync(path, FILE_ENCODING);
7958
-
7959
- }catch(error){
7960
- // TRACE
7961
- console.log("ERROR : Could not read file «"+path+"».");
7962
-
7963
- return null;
7964
- }
7965
-
7966
-
7967
- let resultData={};
7968
- if(!empty(resultFlat)) resultData=JSON.parse(resultFlat);
7969
-
7970
-
7971
- if(!empty(resultData) && isFlatMap(resultData)){
7972
-
7973
- resultData=getAsTreeStructure(resultData,true
7974
- // We have to keep the type information, here too ! (in the sub-objects)
7975
- ,false);
7976
-
7977
- }
7978
-
7979
- return resultData;
7980
- },
7981
-
7982
-
7983
- saveDataToFileForClient:function(clientId,repositoryName,dataFlatForClient,forceKeepUnflatten=false,doOnSuccess=null){
7984
-
7985
-
7986
-
7987
- if(!empty(dataFlatForClient) && !isFlatMap(dataFlatForClient) && !forceKeepUnflatten){
7988
- dataFlatForClient=getAsFlatStructure(dataFlatForClient,true);
7989
- }
7990
-
7991
- // reserved characters : -/\^$*+?.()|[]{}
7992
- // CANNOT USE stringifyObject(...) function because we are in a common, lower-level library !
7993
- let dataFlatStr=JSON.stringify(dataFlatForClient)
7994
- // We «aerate» the produced JSON :
7995
- .replace(/":[\w]*\{/gim,"\":{\n").replace(/,"/gim,",\n\"")
7996
- // ...except for inline, escaped JSON string representations :
7997
- .replace(/\\\":[\w]*\{\n/gim,"\\\":{");
7998
- // NO : .replace(/}/gim,"}\n");
7999
-
8000
-
8001
- let path=self.getPath(clientId,repositoryName);
8002
-
8003
- fs.writeFile(path, dataFlatStr, FILE_ENCODING, (error) => {
8004
- if(error){
8005
- // TRACE
8006
- console.log("ERROR : Could not write file «"+path+"»:",error);
8007
- throw error;
8008
- }
8009
- if(doOnSuccess) doOnSuccess(dataFlatForClient);
8010
- });
8011
- }
8012
-
8013
- };
8014
-
8015
-
8016
- return self;
8017
- };
8018
-
8019
- }
8020
-
8021
-
8022
-
8023
- // Nodejs server launching helper functions :
8024
- //Networking management :
8025
- //- WEBSOCKETS AND NODEJS :
8026
-
8027
- // -Server :
8028
-
8029
- getServerParams=function(portParam,isSecureParam,certPathParam,keyPathParam){
8030
-
8031
- // Node dependencies :
8032
- // https=require("https");
8033
- // fs=require("fs");
8034
-
8035
- if(!https){
8036
- // TRACE
8037
- console.log("WARN : Could not find the nodejs dependency «https», aborting SSL setup.");
8038
- return null;
8039
- }
8040
- if(!fs){
8041
- // TRACE
8042
- console.log("WARN : Could not find the nodejs dependency «fs», aborting SSL setup.");
8043
- return null;
8044
- }
8045
-
8046
- var result={};
8047
-
8048
- // We read the command-line arguments if needed :
8049
- var argCLPort;
8050
- var argCLCertPath;
8051
- var argCLKeyPath;
8052
- process.argv.forEach(function (val, i){
8053
- if(i<=1) return;
8054
- else if(i==2) argCLPort=val;
8055
- else if(i==3) argCLCertPath=val;
8056
- else if(i==4) argCLKeyPath=val;
8057
- });
8058
-
8059
- // Console, command-line arguments OVERRIDE parameters values :
8060
-
8061
- result.port=nonull(argCLPort,portParam);
8062
- result.certPath=null;
8063
- result.keyPath=null;
8064
- result.isSecure=isSecureParam;
8065
-
8066
- if(isSecureParam){
8067
- result.certPath=nonull(argCLCertPath,certPathParam);
8068
- result.keyPath=nonull(argCLKeyPath,keyPathParam);
8069
- }
8070
-
8071
- // Eventual encryption options :
8072
- result.sslOptions=null;
8073
- if(isSecureParam){
8074
- result.sslOptions={
8075
- cert: fs.readFileSync(certPath),
8076
- key: fs.readFileSync(keyPath),
8077
- };
8078
- }
8079
-
8080
- return result;
8081
- }
8082
-
8083
-
8084
- // NODE ONLY SERVER / CLIENTS :
8085
- WebsocketImplementation={
8086
-
8087
- isNode:true,
8088
-
8089
- // COMMON METHODS
8090
- /*private static*/isInRoom(clientSocket, clientsRoomsTag){
8091
- return !clientsRoomsTag || empty(clientsRoomsTag) || contains(clientsRoomsTag, clientSocket.clientRoomTag);
8092
- },
8093
-
8094
- // NODE SERVER
8095
-
8096
- getStatic:(isNode=true)=>{
8097
-
8098
- WebsocketImplementation.isNode=isNode;
8099
-
8100
- // OLD : socket.io :
8101
- // https://stackoverflow.com/questions/31156884/how-to-use-https-on-node-js-using-express-socket-io
8102
- // https://stackoverflow.com/questions/6599470/node-js-socket-io-with-ssl
8103
- // https://nodejs.org/api/https.html#https_https_createserver_options_requestlistener
8104
- // if(typeof(socketIO)==="undefined"){
8105
- // // TRACE
8106
- // console.log("«socket.io» SERVER library not called yet, calling it now.");
8107
- // socketIO=require("socket.io");
8108
- // return socketIO;
8109
- // }
8110
- // if(typeof(socketIO)==="undefined"){
8111
- // // TRACE
8112
- // console.log("ERROR : «socket.io» SERVER library not found. Cannot launch nodejs server. Aborting.");
8113
- // return null;
8114
- // }
8115
-
8116
- // *********************************************************************************
8117
-
8118
- // NEW : ws :
8119
- // https://github.com/websockets/ws#installing
8120
- // https://github.com/websockets/ws/blob/master/doc/ws.md#event-message
8121
- // ON BROWSER SIDE : Native Websockets :
8122
- // https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
8123
- // https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_client_applications
8124
- if(typeof(WebSocket)==="undefined"){
8125
- WebSocket=require("ws");
8126
- // TRACE
8127
- console.log("«ws» SERVER library not called yet, calling it now.");
8128
- return WebsocketImplementation;
8129
- }
8130
- if(typeof(WebSocket)==="undefined"){
8131
- // TRACE
8132
- console.log("ERROR : «ws» SERVER library not found. Cannot launch nodejs server. Aborting.");
8133
- return null;
8134
- }
8135
-
8136
- return WebsocketImplementation;
8137
- },
8138
-
8139
-
8140
- getServer:(listenableServer)=>{
8141
-
8142
- if(!WebsocketImplementation.isNode){
8143
- // TRACE
8144
- throw new Error("ERROR : SERVER : Server launch is not supported in a non-nodejs context.");
8145
- }
8146
-
8147
- // OLD : socket.io :
8148
- // // Loading socket.io
8149
- // let serverSocket=socketIO.listen(listenableServer);
8150
-
8151
- const serverSocket=new WebSocket.Server({ "server":listenableServer });
8152
-
8153
-
8154
- const nodeServerInstance={
8155
-
8156
- clientTimeoutMillis:20000,
8157
-
8158
- // clientsSockets:[],
8159
-
8160
- serverSocket:serverSocket,
8161
-
8162
- receptionEntryPoints:[],
8163
-
8164
-
8165
-
8166
- receive:(channelName, doOnIncomingMessage, clientsRoomsTag=null)=>{
8167
-
8168
- const receptionEntryPoint={
8169
- channelName:channelName,
8170
- clientsRoomsTag:clientsRoomsTag,
8171
- execute:(clientSocketParam)=>{
8172
-
8173
-
8174
- // With «ws» library we have no choive than register message events inside a «connection» event !
8175
- // nodeServerInstance.onConnectionToClient((serverParam, clientSocketParam)=>{
8176
-
8177
- // TODO : Find a way to remove this !
8178
-
8179
- clientSocketParam.on("message", (dataWrapped)=>{
8180
-
8181
- dataWrapped=JSON.parse(dataWrapped);
8182
-
8183
-
8184
- // Channel information is stored in exchanged data :
8185
- if(dataWrapped.channelName!==receptionEntryPoint.channelName) return;
8186
-
8187
-
8188
- // nodeServerInstance.serverSocket.clients.forEach((clientSocket)=>{
8189
-
8190
- const clientSocket=clientSocketParam;
8191
-
8192
- // Room information is stored in client socket object :
8193
- if(!WebsocketImplementation.isInRoom(clientSocket, receptionEntryPoint.clientsRoomsTag)) return;
8194
-
8195
-
8196
-
8197
- doOnIncomingMessage(dataWrapped.data, clientSocket);
8198
-
8199
- });
8200
-
8201
-
8202
- // });
8203
-
8204
-
8205
-
8206
- },
8207
- };
8208
-
8209
-
8210
- // DBG
8211
- console.log("ADD RECEPTION ENTRY POINT channelName:«"+channelName+"»! nodeServerInstance.receptionEntryPoints.length:",nodeServerInstance.receptionEntryPoints.length);
8212
-
8213
- nodeServerInstance.receptionEntryPoints.push(receptionEntryPoint);
8214
-
8215
-
8216
- return nodeServerInstance;
8217
- },
8218
-
8219
-
8220
- send:(channelName, data, clientsRoomsTag=null, clientSocketParam=null)=>{
8221
-
8222
- if(!clientSocketParam){
8223
-
8224
- nodeServerInstance.serverSocket.clients.forEach((clientSocket)=>{
8225
-
8226
- if(clientSocket.readyState!==WebSocket.OPEN) return;
8227
-
8228
- // Room information is stored in client socket object :
8229
- if(!WebsocketImplementation.isInRoom(clientSocket,clientsRoomsTag)) return;
8230
-
8231
- // Channel information is stored in exchanged data :
8232
- let dataWrapped={channelName:channelName, data:data};
8233
-
8234
-
8235
- dataWrapped=JSON.stringify(dataWrapped);
8236
-
8237
- clientSocket.send(dataWrapped);
8238
-
8239
- });
8240
-
8241
- }else{
8242
-
8243
- let clientSocket=clientSocketParam;
8244
- if(clientSocket.readyState!==WebSocket.OPEN) return;
8245
-
8246
- // Room information is stored in client socket object :
8247
- if(!WebsocketImplementation.isInRoom(clientSocket,clientsRoomsTag)) return;
8248
-
8249
- // Channel information is stored in exchanged data :
8250
- let dataWrapped={channelName:channelName, data:data};
8251
-
8252
-
8253
- dataWrapped=JSON.stringify(dataWrapped);
8254
-
8255
- clientSocket.send(dataWrapped);
8256
-
8257
- }
8258
-
8259
-
8260
- return nodeServerInstance;
8261
- },
8262
-
8263
-
8264
- onConnectionToClient:(doOnConnection)=>{
8265
- // «connection» is the only event fired by the serverSocket :
8266
- nodeServerInstance.serverSocket.on("connection", (clientSocket)=>{
8267
-
8268
- // DBG
8269
- console.log("SERVER : ON CONNECTION !");
8270
-
8271
-
8272
- // if(contains(nodeServerInstance.clientsSockets, clientSocket)) return;
8273
- // nodeServerInstance.clientsSockets.push(clientSocket);
8274
-
8275
-
8276
- // DBG
8277
- console.log("nodeServerInstance.receptionEntryPoints.length:",nodeServerInstance.receptionEntryPoints.length);
8278
-
8279
-
8280
-
8281
- // We execute the events registration listeners entry points:
8282
- foreach(nodeServerInstance.receptionEntryPoints,(receptionEntryPoint)=>{
8283
-
8284
- // DBG
8285
- console.log("ENTRY POINT !");
8286
-
8287
- receptionEntryPoint.execute(clientSocket);
8288
- });
8289
-
8290
-
8291
- doOnConnection(nodeServerInstance, clientSocket);
8292
-
8293
-
8294
-
8295
-
8296
-
8297
-
8298
- // To make the server aware of the clients connections states :
8299
- clientSocket.stateCheckInterval=setInterval(()=>{
8300
- if (clientSocket.isAlive===false){
8301
- clearInterval(clientSocket.stateCheckInterval);
8302
-
8303
- return clientSocket.terminate();
8304
- }
8305
- clientSocket.isAlive=false;
8306
- try{
8307
- clientSocket.ping();
8308
- }catch(error){
8309
- lognow("ERROR : A problem occurred when tried to ping client socket : ",error);
8310
- // We effectively end the client connection to this server :
8311
- clientSocket.terminate();
8312
- }
8313
- }, nodeServerInstance.clientTimeoutMillis);
8314
- clientSocket.on("pong",()=>{
8315
- clientSocket.isAlive=true;
8316
- });
8317
-
8318
-
8319
-
8320
- });
8321
-
8322
-
8323
- return nodeServerInstance;
8324
- },
8325
-
8326
- onFinalize:(doOnFinalizeServer)=>{
8327
-
8328
- doOnFinalizeServer(nodeServerInstance);
8329
-
8330
- // TRACE
8331
- console.log("INFO : SERVER : Node server setup complete.");
8332
-
8333
- return nodeServerInstance;
8334
- },
8335
-
8336
- addToRoom:(clientSocket,clientRoomTag)=>{
8337
- clientSocket.clientRoomTag=clientRoomTag;
8338
- },
8339
-
8340
-
8341
-
8342
- };
8343
-
8344
- // Join room server part protocol :
8345
- nodeServerInstance.receive("protocol",(message, clientSocket)=>{
8346
-
8347
- if(message.type!=="joinRoom" || !clientSocket) return;
8348
-
8349
- nodeServerInstance.addToRoom(clientSocket, message.clientRoomTag);
8350
-
8351
- });
8352
-
8353
-
8354
-
8355
- // To make the server aware of the clients connections states :
8356
- nodeServerInstance.serverSocket.on("close", function close() {
8357
- nodeServerInstance.serverSocket.clients.forEach((clientSocket)=>{
8358
- clearInterval(clientSocket.stateCheckInterval);
8359
- });
8360
- });
8361
-
8362
-
8363
-
8364
- return nodeServerInstance;
8365
- },
8366
-
8367
-
8368
- // CLIENT
8369
- connectToServer:(serverURL, port, timeout)=>{
8370
- if(WebsocketImplementation.isNode)
8371
- return WebsocketImplementation.connectToServerFromNode(serverURL, port, timeout);
8372
- // else
8373
- return WebsocketImplementation.connectToServerFromBrowser(serverURL, port, timeout);
8374
- },
8375
-
8376
-
8377
-
8378
-
8379
-
8380
- // NODE CLIENT
8381
- /*private*/connectToServerFromNode:(serverURL, port, timeout)=>{
8382
-
8383
- // OLD : socket.io :
8384
- //client on server-side:
8385
- //if(typeof(socketIO)!=="undefined"){
8386
- // // Node client :
8387
- // connectToServer=function (server, port, timeout=10000){
8388
- // return socketIO.connect(server + ":" + port,{timeout: timeout});;
8389
- // }
8390
- //}else
8391
- // if(typeof(io)!=="undefined"){
8392
- // // Browser client :
8393
- // connectToServer=function (server, port, timeout=10000){
8394
- // return io.connect(server + ":" + port,{timeout: timeout});
8395
- //// ALTERNATIVE : return io(server + ":" + port,{timeout: timeout});
8396
- // };
8397
- // }else{
8398
- // // TRACE
8399
- // console.log("ERROR : Could not initialize socket !");
8400
- // }
8401
-
8402
- // NEW : ws :
8403
- if(typeof(WebSocket)==="undefined"){
8404
- // TRACE
8405
- lognow("ERROR : CLIENT : Could not find websocket client lib, aborting client connection.");
8406
- return null;
8407
- }
8408
-
8409
- const clientSocket=new WebSocket(serverURL+":"+port,/*WORKAROUND:*/{ rejectUnauthorized:false });
8410
-
8411
-
8412
- const nodeClientInstance={
8413
- clientSocket:clientSocket,
8414
-
8415
- receptionEntryPoints:[],
8416
-
8417
-
8418
- receive:(channelName, doOnIncomingMessage, clientsRoomsTag=null)=>{
8419
-
8420
-
8421
- const receptionEntryPoint={
8422
- channelName:channelName,
8423
- clientsRoomsTag:clientsRoomsTag,
8424
- execute:(clientSocketParam)=>{
8425
-
8426
-
8427
- nodeClientInstance.clientSocket.on("message", (dataWrapped)=>{
8428
-
8429
- dataWrapped=JSON.parse(dataWrapped);
8430
-
8431
-
8432
- // Channel information is stored in exchanged data :
8433
- if(dataWrapped.channelName!==channelName) return;
8434
-
8435
- let clientSocket=nodeClientInstance.clientSocket;
8436
-
8437
- // Room information is stored in client socket object :
8438
- if(!WebsocketImplementation.isInRoom(clientSocket,clientsRoomsTag)) return;
8439
-
8440
- doOnIncomingMessage(dataWrapped.data, clientSocket);
8441
-
8442
-
8443
- });
8444
-
8445
- }
8446
- };
8447
-
8448
-
8449
- nodeClientInstance.receptionEntryPoints.push(receptionEntryPoint);
8450
-
8451
-
8452
- return nodeClientInstance;
8453
- },
8454
-
8455
-
8456
- send:(channelName, data, clientsRoomsTag=null)=>{
8457
-
8458
- let clientSocket=nodeClientInstance.clientSocket;
8459
-
8460
-
8461
- // // DBG
8462
- // console.log("(NODE CLIENT) TRYING TO SEND : clientSocket.readyState:",clientSocket.readyState);
8463
-
8464
-
8465
- if(clientSocket.readyState!==WebSocket.OPEN) return;
8466
-
8467
- // Room information is stored in client socket object :
8468
- if(!WebsocketImplementation.isInRoom(clientSocket,clientsRoomsTag)) return;
8469
-
8470
- // Channel information is stored in exchanged data :
8471
- let dataWrapped={channelName:channelName, data:data};
8472
-
8473
-
8474
- // // DBG
8475
- // console.log("(NODE CLIENT) SENDING DATA ! dataWrapped:",dataWrapped);
8476
-
8477
-
8478
- dataWrapped=JSON.stringify(dataWrapped);
8479
-
8480
-
8481
- // // DBG
8482
- // console.log("(NODE CLIENT) SENDING DATA ! channelName:«"+channelName+"» ; clientsRoomsTag:«"+clientsRoomsTag+"»");
8483
- // console.log("(NODE CLIENT) SENDING DATA ! dataWrapped:",dataWrapped);
8484
-
8485
-
8486
- clientSocket.send(dataWrapped);
8487
-
8488
- return nodeClientInstance;
8489
- },
8490
-
8491
-
8492
- join:(clientRoomTag)=>{
8493
- // Join room client part protocol :
8494
- const message={type:"joinRoom",clientRoomTag:clientRoomTag};
8495
- nodeClientInstance.send("protocol",message);
8496
-
8497
- },
8498
-
8499
-
8500
- onConnectionToServer:(doOnConnection)=>{
8501
-
8502
- nodeClientInstance.clientSocket.on("open", ()=>{
8503
-
8504
- let clientSocket=nodeClientInstance.clientSocket;
8505
-
8506
- doOnConnection(nodeClientInstance, clientSocket);
8507
-
8508
- // We execute the listeners entry points:
8509
- foreach(nodeClientInstance.receptionEntryPoints,(receptionEntryPoint)=>{
8510
- receptionEntryPoint.execute(clientSocket);
8511
- });
8512
-
8513
- });
8514
- },
8515
-
8516
-
8517
- };
8518
-
8519
-
8520
- return nodeClientInstance;
8521
- },
8522
-
8523
-
8524
-
8525
- // BROWSER CLIENT
8526
-
8527
- /*private*/connectToServerFromBrowser:(serverURL, port, timeout)=>{
8528
-
8529
- // NEW : ws :
8530
- if(typeof(WebSocket)==="undefined"){
8531
- // TRACE
8532
- lognow("ERROR : CLIENT : Could not find websocket client lib, aborting client connection.");
8533
- return null;
8534
- }
8535
-
8536
- const clientSocket=new WebSocket(serverURL+":"+port,["wss"]);
8537
-
8538
-
8539
- const browserInstance={
8540
- clientSocket:clientSocket,
8541
-
8542
-
8543
- receive:(channelName, doOnIncomingMessage, clientsRoomsTag=null)=>{
8544
-
8545
- browserInstance.clientSocket.addEventListener("message", (event)=>{
8546
-
8547
- let dataWrapped=JSON.parse(event.data);
8548
-
8549
-
8550
- // Channel information is stored in exchanged data :
8551
- if(dataWrapped.channelName!==channelName) return;
8552
-
8553
- let clientSocket=browserInstance.clientSocket;
8554
-
8555
- // Room information is stored in client socket object :
8556
- if(!WebsocketImplementation.isInRoom(clientSocket,clientsRoomsTag)) return;
8557
-
8558
- doOnIncomingMessage(dataWrapped.data, clientSocket);
8559
-
8560
- });
8561
-
8562
- return browserInstance;
8563
- },
8564
-
8565
-
8566
- send:(channelName, data, clientsRoomsTag=null)=>{
8567
-
8568
- let clientSocket=browserInstance.clientSocket;
8569
-
8570
-
8571
- // // DBG
8572
- // console.log("(BROWSER) TRYING TO SEND : clientSocket.readyState:",clientSocket.readyState);
8573
-
8574
-
8575
- if(clientSocket.readyState!==WebSocket.OPEN) return;
8576
-
8577
- // Room information is stored in client socket object :
8578
- if(!WebsocketImplementation.isInRoom(clientSocket,clientsRoomsTag)) return;
8579
-
8580
- // Channel information is stored in exchanged data :
8581
- let dataWrapped={channelName:channelName, data:data};
8582
-
8583
-
8584
- // // DBG
8585
- // console.log("(BROWSER) SENDING... : dataWrapped :",dataWrapped);
8586
-
8587
-
8588
- dataWrapped=JSON.stringify(dataWrapped);
8589
-
8590
- clientSocket.send(dataWrapped);
8591
-
8592
- return browserInstance;
8593
- },
8594
-
8595
-
8596
- join:(clientRoomTag)=>{
8597
- // Join room client part protocol :
8598
- const message={type:"joinRoom",clientRoomTag:clientRoomTag};
8599
- browserInstance.send("protocol",message);
8600
- },
8601
-
8602
-
8603
- onConnectionToServer:(doOnConnection)=>{
8604
- browserInstance.clientSocket.addEventListener("open",doOnConnection);
8605
- },
8606
-
8607
-
8608
- };
8609
-
8610
-
8611
- return browserInstance;
8612
- },
8613
-
8614
- };
8615
-
8616
-
8617
-
8618
- launchNodeHTTPServer=function(port, doOnConnect=null, doOnFinalizeServer=null, /*OPTIONAL*/sslOptions=null){
8619
-
8620
- const EXCLUDED_FILENAMES_PARTS=[".saltkey."];
8621
-
8622
-
8623
- if(typeof(https)==="undefined"){
8624
- // TRACE
8625
- console.log("«https» SERVER library not called yet, calling it now.");
8626
- https=require("https");
8627
- }
8628
- if(typeof(http)==="undefined"){
8629
- // TRACE
8630
- console.log("«http» SERVER library not called yet, calling it now.");
8631
- http=require("http");
8632
- }
8633
-
8634
-
8635
- const handler=function(request, response){
8636
-
8637
- const url=request.url;
8638
-
8639
- let isURLInExclusionZone=!!foreach(EXCLUDED_FILENAMES_PARTS,(excludedStr)=>{
8640
- if(contains(url,excludedStr)){
8641
- return true;
8642
- }
8643
- });
8644
-
8645
- if(isURLInExclusionZone){
8646
- // TRACE
8647
- console.log("ERROR 403 forbidden access error :");
8648
- console.log(error);
8649
-
8650
- response.writeHead(403);
8651
- response.end("Sorry, cannot access resource : error: "+error.code+" ..\n");
8652
- response.end();
8653
- return;
8654
- }
8655
-
8656
-
8657
- const urlFile="." + url;
8658
-
8659
- let filePath=urlFile.indexOf("?")!==-1?urlFile.split("?")[0]:urlFile;
8660
- if(filePath == "./") filePath="./index.html";
8661
-
8662
- let extname=path.extname(filePath);
8663
- let contentType="text/html";
8664
-
8665
-
8666
- fs.readFile(filePath, function(error, fileContent){
8667
- if(error){
8668
- if(error.code == "ENOENT"){
8669
- // TRACE
8670
- console.log("ERROR 404 file not found :"+filePath);
8671
-
8672
- fs.readFile("./404.html", function(error, fileContent){
8673
- response.writeHead(200, { "Content-Type": contentType });
8674
- response.end(fileContent, "utf-8");
8675
- });
8676
-
8677
- }else {
8678
-
8679
- // TRACE
8680
- console.log("ERROR 500 server error :");
8681
- console.log(error);
8682
-
8683
- response.writeHead(500);
8684
- response.end("Sorry, check with the site admin for error: "+error.code+" ..\n");
8685
- response.end();
8686
-
8687
- }
8688
- }else {
8689
-
8690
- // TRACE
8691
- console.log("INFO 200 OK :"+filePath);
8692
-
8693
- response.writeHead(200, { "Content-Type": contentType });
8694
- response.end(fileContent, "utf-8");
8695
-
8696
- // res.writeHead(200);
8697
- // res.end("hello world\n");
8698
- // res.sendFile(__dirname + "/public/index.html");
8699
-
8700
- }
8701
- });
8702
-
8703
- };
8704
-
8705
-
8706
- let listenableServer;
8707
- if(sslOptions){
8708
- let httpsServer=https.createServer(sslOptions, handler).listen(port);
8709
- // TRACE
8710
- console.log("INFO : SERVER : HTTPS Server launched and listening on port " + port + "!");
8711
- listenableServer=httpsServer;
8712
- }else{
8713
- let httpServer=http.createServer(handler).listen(port);
8714
- // TRACE
8715
- console.log("INFO : SERVER : HTTP Server launched and listening on port " + port + "!");
8716
- listenableServer=httpServer;
8183
+ hasXAxisWholeZone2=true;
8184
+ }
8717
8185
  }
8718
8186
 
8719
-
8720
- const server=WebsocketImplementation.getStatic(true).getServer(listenableServer);
8721
-
8722
- // When a client connects, we execute the callback :
8723
- // CAUTION : MUST BE CALLED ONLY ONCE !
8724
- server.onConnectionToClient((serverParam, clientSocketParam)=>{
8725
- if(doOnConnect) doOnConnect(serverParam, clientSocketParam);
8726
- });
8727
-
8728
-
8729
- server.onFinalize((serverParam)=>{
8730
- if(doOnFinalizeServer) doOnFinalizeServer(serverParam);
8731
- });
8732
-
8733
-
8734
- // TRACE
8735
- console.log("INFO : SERVER : Generic Nodejs server launched and listening on port " + port + "!");
8187
+ let w=intersectionXMax-intersectionXMin;
8188
+ let x=(intersectionXMax+intersectionXMin)*.5; // (The average)
8736
8189
 
8737
8190
 
8738
- return server;
8739
- }
8191
+ // Vertical axis :
8740
8192
 
8193
+ let hasYAxisWholeZone1=false;
8194
+ let hasYAxisWholeZone2=false;
8741
8195
 
8742
- initNodeServer=function(doOnClientConnection=null, doOnFinalizeServer=null, /*OPTIONAL*/portParam, /*OPTIONAL*/certPathParam, /*OPTIONAL*/keyPathParam){
8196
+ let demiHeight1=zone1.h*.5;
8197
+ let demiHeight2=zone2.h*.5;
8743
8198
 
8744
- // TRACE
8745
- console.log("Server launched.");
8746
- console.log("Usage : node <server.js> [port] [ssl certificate path | unsecure ] [ssl key path]");
8747
- console.log("Or (to generate password hash) : node <server.js> hash <clientId@repositoryName> <clearText>");
8748
- // EXAMPLE : node orita-srv.js hash orita.global@saltkey 1234567890
8749
- console.log("Server launched.");
8750
-
8751
- // We read the command-line arguments if needed :
8752
-
8753
- let argCLPort;
8754
- let argCLCertPath;
8755
- let argCLKeyPath;
8756
- let isForceUnsecure;
8757
-
8758
- let isHashAsked=false;
8759
- let clearTextParam=null;
8760
- let persisterId=null;
8761
- process.argv.forEach(function (val, i){
8762
- if(!val) return;
8763
- // 0 corresponds to «node / nodejs»
8764
- if(i<=1) return; // 1 corresponds to « <server.js> »
8765
- else if(i==2){
8766
- if(val!=="hash") argCLPort=val;
8767
- else isHashAsked=true;
8768
- }else if(i==3){
8769
- if(!isHashAsked) argCLCertPath=val;
8770
- else persisterId=val;
8771
- }else if(i==4){
8772
- if(!isHashAsked) argCLKeyPath=val;
8773
- else clearTextParam=val;
8774
- }
8775
- });
8199
+ let yMin1=zone1.y-demiHeight1;
8200
+ let yMax1=zone1.y+demiHeight1;
8201
+ let yMin2=zone2.y-demiHeight2;
8202
+ let yMax2=zone2.y+demiHeight2;
8776
8203
 
8777
- isForceUnsecure=(argCLCertPath==="unsecure");
8778
-
8779
- let aotraNodeServer={};
8780
- aotraNodeServer.serverManager={ start:()=>{/*DEFAULT START FUNCTION, WILL BE OVERRIDEN LATER*/}};
8781
8204
 
8782
- if(isHashAsked){
8783
- // We isntanciate a temporary persister just to read the saltkey file:
8784
- const persister=getPersister("./");
8785
- let persisterIdSplits=persisterId.split("@");
8786
- if(empty(persisterIdSplits) || persisterIdSplits.length!=2){
8787
- // TRACE
8788
- console.log("ERROR : No persister repository IDs provided correctly. Cannot read saltkey. Aborting hash generation.");
8789
- return aotraNodeServer;
8205
+ let deltaYMins=yMin2-yMin1;
8206
+ let deltaYMaxs=yMax2-yMax1;
8207
+
8208
+ let deltaYMinsSign=(deltaYMins<0?-1:1);
8209
+ let deltaYMaxsSign=(deltaYMaxs<0?-1:1);
8210
+ let intersectionYMin=0;
8211
+ let intersectionYMax=0;
8212
+ if(deltaYMinsSign==deltaYMaxsSign){ // case «parallelogram»
8213
+ if(deltaYMinsSign<0){ // case /=/ parallelogram
8214
+ intersectionYMin=yMin1;
8215
+ intersectionYMax=yMax2;
8216
+ // We calculate edges :
8217
+ edges.push("bottom"); // Here the Y axis is NOT inverted !
8218
+ }else{ // case \=\ parallelogram
8219
+ intersectionYMin=yMin2;
8220
+ intersectionYMax=yMax1;
8221
+ // We calculate edges :
8222
+ edges.push("top"); // Here the Y axis is NOT inverted !
8790
8223
  }
8791
- const persisterClientId=persisterIdSplits[0];
8792
- const persisterRepositoryName=persisterIdSplits[1];
8793
- let globalSaltkeyObject=persister.readTreeObjectFromFile(persisterClientId, persisterRepositoryName);
8794
- if(!globalSaltkeyObject || !globalSaltkeyObject.saltkey){
8795
- // TRACE
8796
- console.log("WARN : No saltkey found. Generating one now.");
8797
- globalSaltkeyObject={saltkey:getUUID(), hashes:[]};
8798
- persister.saveDataToFileForClient(persisterClientId,persisterRepositoryName,globalSaltkeyObject,false,()=>{
8799
- // TRACE
8800
- console.log("INFO : Saltkey generated and saved successfully.");
8801
- });
8224
+ }else{ // case «trapeze»
8225
+ if(deltaYMinsSign<0){ // case /=\ trapeze
8226
+ intersectionYMin=yMin1; // Basically zone2 y !
8227
+ intersectionYMax=yMax1;
8228
+ // We calculate edges :
8229
+ hasYAxisWholeZone1=true;
8230
+ }else{ // case \=/ trapeze
8231
+ intersectionYMin=yMin2; // Basically zone2 y !
8232
+ intersectionYMax=yMax2;
8233
+ // We calculate edges :
8234
+ hasYAxisWholeZone2=true;
8802
8235
  }
8803
- const globalSaltkey=globalSaltkeyObject.saltkey;
8804
-
8805
- let firstHash=getHashedString(clearTextParam);
8806
-
8807
- let generatedHash=getHashedString( firstHash + globalSaltkey, true);// (we use the heavy treatment thing.)
8808
- globalSaltkeyObject.hashes.push(generatedHash);
8809
-
8810
- // We update the repository :
8811
- persister.saveDataToFileForClient(persisterClientId,persisterRepositoryName,globalSaltkeyObject,false,()=>{
8812
- // TRACE
8813
- console.log("INFO : Hash added to repository and saved successfully.");
8814
- });
8815
-
8816
- // OUTPUT
8817
- console.log("Here is your key : share it with your clients but DO NOT LEAK IT !\n********************\n"+clearTextParam+"\n********************\n");
8818
-
8819
- return aotraNodeServer;
8820
8236
  }
8821
8237
 
8238
+ let h=intersectionYMax-intersectionYMin;
8239
+ let y=(intersectionYMax+intersectionYMin)*.5; // (The average)
8822
8240
 
8823
-
8824
-
8825
- const DEFAULT_PORT=nonull(argCLPort,25000);
8826
- const DEFAULT_CERT_PATH=nonull(argCLCertPath,"cert.pem");
8827
- const DEFAULT_KEY_PATH=nonull(argCLKeyPath,"key.key");
8828
-
8829
-
8830
- let port=portParam ? portParam : DEFAULT_PORT;
8831
- let certPath=null;
8832
- let keyPath=null;
8833
-
8834
- if(!isForceUnsecure){
8835
- certPath=certPathParam?certPathParam:DEFAULT_CERT_PATH;
8836
- keyPath=keyPathParam?keyPathParam:DEFAULT_KEY_PATH;
8837
- }
8838
-
8241
+ // We calculate edges :
8242
+ if(hasXAxisWholeZone1 && hasYAxisWholeZone1) edges.push("wholeZone1");
8243
+ if(hasXAxisWholeZone2 && hasYAxisWholeZone2) edges.push("wholeZone2");
8839
8244
 
8840
- // UNUSEFUL :
8841
- // aotraNodeServer.serverManager.microClientsSockets=[];
8842
- // UNUSEFUL :
8843
- // aotraNodeServer.serverManager.mainClientsSockets=[];
8844
- aotraNodeServer.serverManager.start=function(){
8245
+ return {x:x,y:y,w:w,h:h,edges:edges};
8246
+ }
8845
8247
 
8846
- // Eventual encryption options :
8847
- let sslOptions=null;
8848
- if(!isForceUnsecure){
8849
- if(!fs){
8850
- // TRACE
8851
- lognow("ERROR : «fs» node subsystem not present, cannot access files. Aborting SSL configuration of server.");
8852
- }else{
8853
- try{
8854
- sslOptions={
8855
- cert: fs.readFileSync(certPath),
8856
- key: fs.readFileSync(keyPath),
8857
- };
8858
- }catch(exception){
8859
- // TRACE
8860
- lognow("ERROR : Could not open SSL files certPath:«"+certPath+"» or keyPath:«"+keyPath+"». Aborting SSL configuration of server.");
8861
- }
8862
- }
8863
- }
8864
-
8865
- aotraNodeServer.server=launchNodeHTTPServer(port, doOnClientConnection, doOnFinalizeServer, sslOptions);
8866
-
8248
+ /*public static*/window.areZonesIdentical=function(zone1, zone2){
8249
+ return (zone1==zone2 || (zone1.x==zone2.x || zone1.y==zone2.y || zone1.w==zone2.w || zone1.h==zone2.h));
8250
+ }
8867
8251
 
8868
- return aotraNodeServer;
8869
- };
8870
8252
 
8871
- return aotraNodeServer;
8872
- }
8873
8253
 
8254
+ // 3D :
8874
8255
 
8875
- // -Client :
8876
8256
 
8877
- // TODO : Use everywhere it's applicable !
8878
- initClient=function(isNode=true, doOnServerConnection=null, doOnError=null, url, /*OPTIONAL*/port=25000, /*OPTIONAL*/timeout=10000, /*OPTIONAL*/selfParam=null){
8879
- let aotraClient={};
8257
+ /*
8258
+ // (needs the jsgl library file !)
8259
+ // UNUSED
8260
+ function screenToSpherePt(mouseX, mouseY) {
8261
+ var p = { x: camera_mat.e12, y: camera_mat.e13, z: camera_mat.e14 + 1 };
8262
+ // camera dir
8263
+ var r = { x: camera_mat.e0, y: camera_mat.e1, z: camera_mat.e2 };
8264
+ var up = { x: camera_mat.e4, y: camera_mat.e5, z: camera_mat.e6 };
8265
+ var right = { x: camera_mat.e8, y: camera_mat.e9, z: camera_mat.e10 };
8266
+ var tan_half = Math.tan(horizontal_fov_radians / 2);
8267
+ r = jsgl.vectorAdd(r, jsgl.vectorScale(right, mouseX * tan_half));
8268
+ r = jsgl.vectorAdd(r, jsgl.vectorScale(up, mouseY * tan_half));
8269
+ r = jsgl.vectorNormalize(r);
8880
8270
 
8881
-
8882
- aotraClient.client={};
8883
- aotraClient.client.start=function(){
8884
-
8885
-
8886
- let socketToServer=WebsocketImplementation.getStatic(isNode).connectToServer(url, port, timeout);
8887
- if(!socketToServer){
8888
- // TRACE
8889
- lognow("ERROR : CLIENT : Could not get socketToServer, aborting client connection.");
8890
- return null;
8891
- }
8892
-
8893
- // When client is connected, we execute the callback :
8894
- // CAUTION : MUST BE CALLED ONLY ONCE !
8895
- socketToServer.onConnectionToServer(()=>{
8896
- if(doOnServerConnection){
8897
- if(selfParam) doOnServerConnection.apply(selfParam,[socketToServer]);
8898
- else doOnServerConnection(socketToServer);
8899
- }
8900
- });
8271
+ return rayVsUnitSphereClosestPoint(p, r);
8272
+ }
8273
+ */
8901
8274
 
8902
- // Errors handling :
8903
- if(doOnError){
8904
-
8905
- // // DBG
8906
- // lognow(">>> serverSocket.connected:"+serverSocket.connected);
8907
- // lognow(">>> serverSocket:",serverSocket);
8275
+ /*
8276
+ // (needs the jsgl library file !)
8277
+ // UNUSED
8278
+ // Return the first exterior hit or closest point between the unit
8279
+ // sphere and the ray starting at p and going in the r direction.
8280
+ function rayVsUnitSphereClosestPoint(p, r) {
8281
+ var p_len2 = jsgl.dotProduct(p, p);
8282
+ if (p_len2 < 1) {
8283
+ // Ray is inside sphere, no exterior hit.
8284
+ return null;
8285
+ }
8908
8286
 
8287
+ var along_ray = -jsgl.dotProduct(p, r);
8288
+ if (along_ray < 0) {
8289
+ // Behind ray start-point.
8290
+ return null;
8291
+ }
8909
8292
 
8910
- const errorMethod=function(){
8911
- // TRACE
8912
- lognow("ERROR : CLIENT : Client encountered an error while trying to connect to server.");
8913
-
8914
- if(selfParam) doOnError.apply(selfParam);
8915
- else doOnError();
8916
- };
8917
-
8918
- // // DOES NOT SEEM TO WORK :
8919
- // socketToServer.on("connect_failed", errorMethod);
8920
- // // DOES NOT SEEM TO WORK :
8921
- // socketToServer.on("error", errorMethod);
8922
-
8923
- }
8293
+ var perp = jsgl.vectorAdd(p, jsgl.vectorScale(r, along_ray));
8294
+ var perp_len2 = jsgl.dotProduct(perp, perp);
8295
+ if (perp_len2 >= 0.999999) {
8296
+ // Return the closest point.
8297
+ return jsgl.vectorNormalize(perp);
8298
+ }
8924
8299
 
8925
-
8926
- aotraClient.client.socketToServer=socketToServer;
8927
-
8928
- return aotraClient;
8929
- };
8930
-
8931
-
8932
- return aotraClient;
8300
+ // Compute intersection.
8301
+ var e = Math.sqrt(1 - jsgl.dotProduct(perp, perp));
8302
+ var hit = jsgl.vectorAdd(p, jsgl.vectorScale(r, (along_ray - e)));
8303
+ return jsgl.vectorNormalize(hit);
8933
8304
  }
8305
+ */
8934
8306
 
8935
8307
 
8936
8308
 
8937
8309
 
8938
- // ===================================================================================================
8939
-
8940
- /*FUSRODA server stands from FSRD SERVER, for Fucking Simple Remote Desktop SERVER*/
8941
- createFusrodaServer=function(certPathParam=null,keyPathParam=null,portParam=6001){
8942
-
8943
- // https://www.npmjs.com/package/screenshot-desktop
8944
- // https://github.com/octalmage/robotjs
8945
- // npm install --save screenshot-desktop robotjs
8946
-
8947
- const screenshot=require("screenshot-desktop");
8948
8310
 
8949
- const REFRESH_SCREENSHOTS_MILLIS=500;
8950
8311
 
8951
-
8952
- const server=initNodeServer(
8953
- // On client connection :
8954
- // (serverParam, clientSocketParam)=>{},
8955
- null,
8956
- // On server finalization :
8957
- (serverParam)=>{
8958
8312
 
8959
- serverParam.receive("protocol_fusroda", (message, clientSocket)=> {
8960
- serverParam.addToRoom(clientSocket,"clients");
8961
- });
8962
-
8963
-
8964
- serverParam.sendScreenshotsRoutine=setInterval(()=>{
8965
- if(serverParam.isScreenshotStarted) return;
8966
-
8967
- serverParam.isScreenshotStarted=true;
8968
-
8969
- screenshot().then((img) => {
8970
-
8971
- const data={image:img};
8972
- serverParam.send("data_fusroda", data, "clients");
8973
-
8974
- serverParam.isScreenshotStarted=false;
8975
8313
 
8976
- }).catch((error) => {
8977
- // TRACE
8978
- lognow("ERROR : Error during screenshot :",error);
8979
- });
8980
-
8981
- },REFRESH_SCREENSHOTS_MILLIS);
8982
-
8983
-
8984
- },portParam,certPathParam,keyPathParam);
8985
8314
 
8986
8315
 
8987
- // const doOnConnect=(serverParam, clientSocketParam)=>{
8988
- // };
8989
- // const doOnFinalizeServer=(serverParam)=>{
8990
- // /*DO NOTHING*/
8991
- // };
8992
- // const server={};
8993
- // server.start=(port=6001)=>{
8994
- // server.httpServer=launchNodeHTTPServer(port, doOnConnect, doOnFinalizeServer, sslOptions);
8995
- // };
8996
8316
 
8997
- return server;
8998
- }
8999
8317
 
9000
8318
 
9001
8319
 
9002
- createFusrodaClient=function(doOnDataReception, urlParam=null,portParam=6001){
9003
-
9004
- const isNode=false;
9005
- const client=WebsocketImplementation.getStatic(isNode).connectToServer(urlParam, portParam);
9006
- client.onConnectionToServer((nodeClientInstance)=>{
9007
-
9008
- nodeClientInstance.send("protocol_fusroda");
9009
8320
 
9010
- nodeClientInstance.receive("data_fusroda", doOnDataReception, "clients");
9011
-
9012
- });
9013
-
9014
- return client;
9015
- }
9016
8321
 
9017
8322
 
9018
8323
 
9019
8324
 
9020
8325
 
9021
8326
 
8327
+ // MUST REMAIN AT THE END OF THIS LIBRARY FILE !
9022
8328
 
9023
- /*utils COMMONS library associated with aotra version : «1.0.0.000 (11/01/2022-00:18:33)»*/
8329
+ AOTRAUTILS_GEOMETRY_LIB_IS_LOADED=true;
8330
+ /*utils COMMONS library associated with aotra version : «1.0.0.000 (11/07/2022-20:58:07)»*/
9024
8331
  /*-----------------------------------------------------------------------------*/
9025
8332
 
9026
8333
 
@@ -9032,7 +8339,7 @@ createFusrodaClient=function(doOnDataReception, urlParam=null,portParam=6001){
9032
8339
  *
9033
8340
  * # Library name : «aotrautils»
9034
8341
  * # Library license : HGPL(Help Burma) (see aotra README information for details : https://alqemia.com/aotra.js )
9035
- * # Author name : Jérémie Ratomposon (massively helped by its programming egos legions)
8342
+ * # Author name : Jérémie Ratomposon (massively helped by his native country free education system)
9036
8343
  * # Author email : info@alqemia.com
9037
8344
  * # Organization name : Alqemia
9038
8345
  * # Organization email : admin@alqemia.com
@@ -10884,9 +10191,9 @@ window.LZWString={
10884
10191
 
10885
10192
  readBits : function(numBits, data){
10886
10193
  var res=0;
10887
- var maxpower=Math.pow(2,numBits);
10194
+ var maxPower=Math.pow(2,numBits);
10888
10195
  var power=1;
10889
- while (power!=maxpower){
10196
+ while (power!=maxPower){
10890
10197
  res |= this.readBit(data) * power;
10891
10198
  power <<= 1;
10892
10199
  }
@@ -11230,6 +10537,31 @@ window.copy=function(array){
11230
10537
  return result;
11231
10538
  }
11232
10539
 
10540
+
10541
+
10542
+ window.removeByIndex=function(array, elementIndex){
10543
+ if(array instanceof Array){
10544
+ if(empty(array)) return false;
10545
+ if(elementIndex<0) return false;
10546
+ array.splice(elementIndex, 1);
10547
+ return true;
10548
+ }
10549
+ let hasDeleted=false;
10550
+ // Case of regular objects that are like associative arrays :
10551
+ let i=0;
10552
+ for (key in array){
10553
+ if(!array.hasOwnProperty(key)) continue;
10554
+ if(i===elementIndex){
10555
+ delete array[key];
10556
+ hasDeleted=true;
10557
+ break;
10558
+ }
10559
+ i++;
10560
+ }
10561
+ return hasDeleted;
10562
+ }
10563
+
10564
+
11233
10565
  window.remove=function(array, element){
11234
10566
  if(array instanceof Array){
11235
10567
  if(empty(array)) return false;
@@ -11335,15 +10667,41 @@ Math.minInArray=function(arrayOfValues){
11335
10667
  return min;
11336
10668
  };
11337
10669
 
10670
+
10671
+
11338
10672
  // TODO : FIXME : Create a namespace MathUtils. and ArraysUtils. and sort out the corresponding static methods.
11339
- Math.getRandomInArray=function(arrayOfValues){
11340
- if(empty(arrayOfValues))
11341
- return null;
11342
- var size=getArraySize(arrayOfValues);
10673
+ Math.getRandomsInArray=function(arrayOfValues, numberOfItemsToGet=1){
10674
+
10675
+ if(numberOfItemsToGet<0 || empty(arrayOfValues)) return [];
10676
+ const size=getArraySize(arrayOfValues);
10677
+ // We skip a costly random calculation if we have only one element in array :
10678
+ if(size===1) return [valueAt(arrayOfValues,0)];
10679
+
10680
+ const indexesPool=[];
10681
+ for(let i=0;i<size;i++) indexesPool.push(i);
10682
+
10683
+ const results=[];
10684
+ for(let i=0;i<numberOfItemsToGet;i++){
10685
+ if(empty(indexesPool)) break;
10686
+ const chosenIndex=Math.getRandomInArray(indexesPool,true);
10687
+ const item=valueAt(arrayOfValues,chosenIndex);
10688
+ results.push(item);
10689
+ }
10690
+
10691
+ return results;
10692
+ }
10693
+
10694
+ // TODO : FIXME : Create a namespace MathUtils. and ArraysUtils. and sort out the corresponding static methods.
10695
+ Math.getRandomInArray=function(arrayOfValues,removeFromArray=false){
10696
+ if(empty(arrayOfValues)) return null;
10697
+ const size=getArraySize(arrayOfValues);
11343
10698
  // We skip a costly random calculation if we have only one element in array :
11344
10699
  if(size===1) return valueAt(arrayOfValues,0);
11345
- var chosenIndex=Math.getRandomInt(size-1,0);
11346
- return valueAt(arrayOfValues,chosenIndex);
10700
+
10701
+ const chosenIndex=Math.getRandomInt(size-1,0);
10702
+ const result=valueAt(arrayOfValues, chosenIndex);
10703
+ if(removeFromArray) removeByIndex(arrayOfValues, chosenIndex);
10704
+ return result;
11347
10705
  }
11348
10706
 
11349
10707
  // This function is for arrays, like Array or Objects used as associative arrays you want to test as holding elements :
@@ -11956,6 +11314,52 @@ Math.demiCurve=function(min, max, value, strategy="triangle", precision=2){
11956
11314
  return Math.roundTo(cursorFactor,precision);
11957
11315
  }
11958
11316
 
11317
+ // Not the same as curve, where min and max are the minimum and maximum values taken by returned value :
11318
+ Math.curveTriggered=function(minTriggerFactor, maxTriggerFactor, valueFactorParam, strategy="linear"){
11319
+ const valueFactor=Math.coerceInRange(valueFactorParam,0,1);
11320
+ let result=1;
11321
+ if(valueFactor<minTriggerFactor){
11322
+ // Ascending slope :
11323
+ if(minTriggerFactor==0) return 1;
11324
+ result=valueFactor/minTriggerFactor;
11325
+ }else{
11326
+ if(maxTriggerFactor==0) return 1;
11327
+ if(maxTriggerFactor<valueFactor){
11328
+ // Descending slope :
11329
+ result=1-((valueFactor-maxTriggerFactor)/(1-maxTriggerFactor));
11330
+ }else{
11331
+ // Plateau :
11332
+ return 1;
11333
+ }
11334
+ }
11335
+ return result;
11336
+ }
11337
+
11338
+ Math.slopeTriggered=function(minTriggerFactor, maxTriggerFactor, valueFactorParam, direction="ascending", strategy="linear"){
11339
+
11340
+ const valueFactor=Math.coerceInRange(valueFactorParam,0,1);
11341
+
11342
+ if(valueFactor<minTriggerFactor){
11343
+ // Beginning plateau :
11344
+ if(direction=="ascending") return 0;
11345
+ else return 1;
11346
+ }else if(maxTriggerFactor<valueFactor){
11347
+ // Ending plateau :
11348
+ if(direction=="ascending") return 1;
11349
+ else return 0;
11350
+ }
11351
+
11352
+ // Intermediate slope :
11353
+ const total=(maxTriggerFactor-minTriggerFactor);
11354
+ if(total==0) return ((direction=="ascending")?1:0);
11355
+
11356
+ const factor=((valueFactor-minTriggerFactor)/total);
11357
+
11358
+ return ((direction=="ascending")?factor:(1-factor));
11359
+ }
11360
+
11361
+
11362
+
11959
11363
  Math.curve=function(min, max, value, strategy="triangle",
11960
11364
  // It is actually where to place the «summit» of the curve, before returning to 0,
11961
11365
  // the factor value where the values starts decreasing, after having increased all along, and then goes back to 0: