Browse Source

admin front: entite active boolean

bach 1 month ago
parent
commit
d6b3bdf2aa

+ 4 - 0
src/api/gql/concernement.fragment.gql

@@ -30,6 +30,7 @@ fragment ConcernementFields on Concernement {
   entites {
     id
     revision_id
+    active
     menacemaintien
     actuelfuture
     entite {
@@ -50,6 +51,9 @@ fragment ConcernementFields on Concernement {
     revision_id
     changed
     entites {
+      id
+      revision_id
+      active
       menacemaintien
       prise
       actuelfuture

+ 66 - 33
src/components/ConcernementMapItem.vue

@@ -31,6 +31,7 @@ export default {
       concernement: null,
       active_revision: null,
       entities: null,
+      activeEntites: null,
       superposedEntiteID: null,
       // superposedEntitesIDsList: [],
       canvas: null,
@@ -116,9 +117,9 @@ export default {
       // 
       this.parseEntityPointsValues();
 
-
+      let activeEntites = this.getActiveEntites(this.entites);
       // this.getSalientPoints()
-      this.sailentEntites = this.concernement.sailentEntites = this.getJarvisEnvelopeConvexeEntites(this.entites)
+      this.sailentEntites = this.concernement.sailentEntites = this.getJarvisEnvelopeConvexeEntites(activeEntites)
 
       // define init position of the item
       this.pos = this.getRandomPos();
@@ -412,7 +413,7 @@ export default {
       // TODO sometimes active revision is not on revisions_by_id ... why ?
       let revisions_ids = Object.keys(this.concernement.revisions_byid);
       if (revisions_ids.indexOf(this.concernement.revision_id) !== -1) {
-      this.entites = this.concernement.revisions_byid[this.concernement.revision_id].entites;
+        this.entites = this.concernement.revisions_byid[this.concernement.revision_id].entites;
       } else {
         this.entites = this.concernement.revisions_byid[revisions_ids.pop()].entites;
       }
@@ -465,6 +466,17 @@ export default {
       this.concernement.parsedEntites = true;
 
     },
+    getActiveEntites(entites){
+      // console.log('getActiveEntites', entites);
+      
+      let activeEntites = [];
+      for (let i = 0; i < entites.length; i++) {
+        if (entites[i].active) {
+          activeEntites.push(entites[i])
+        }
+      }
+      return activeEntites;
+    },
     getSalientPoints_OLD() {
       // debugger
       // console.log(this.entites);
@@ -500,6 +512,12 @@ export default {
       // console.log(`this.salientPoints ${this.concernement.id}`, this.salientPoints);
     },
     getJarvisEnvelopeConvexeEntites(entites){
+      // console.log(`getJarvisEnvelopeConvexeEntites cid:${this.cid}`, entites);
+      if (entites.length === 0) {
+        console.error('getJarvisEnvelopeConvexeEntites entites must not be empty')
+        return;
+      }
+
       let sailentEntites = [];
       // https://www.geeksforgeeks.org/convex-hull-using-jarvis-algorithm-or-wrapping/
       // find the most left point
@@ -530,19 +548,21 @@ export default {
         // clockwise than q, then update q.
         q = (p + 1) % entites.length;
         for (let i = 0; i < entites.length; i++) {
-          let p_x = (entites[p].display.ray) * Math.cos(entites[p].display.alpha * (Math.PI/180));
-          let p_y = (entites[p].display.ray) * Math.sin(entites[p].display.alpha * (Math.PI/180));
-          let i_x = (entites[i].display.ray) * Math.cos(entites[i].display.alpha * (Math.PI/180));
-          let i_y = (entites[i].display.ray) * Math.sin(entites[i].display.alpha * (Math.PI/180));
-          let q_x = (entites[q].display.ray) * Math.cos(entites[q].display.alpha * (Math.PI/180));
-          let q_y = (entites[q].display.ray) * Math.sin(entites[q].display.alpha * (Math.PI/180));
-          
-          let val = (i_y - p_y) * (q_x - i_x) - (i_x - p_x) * (q_y - i_y);
-          
-          // If i is more clockwise than current q, then update q
-          if (val > 0){
-            q = i;
-          }
+          // if (entite.active) {
+            let p_x = (entites[p].display.ray) * Math.cos(entites[p].display.alpha * (Math.PI/180));
+            let p_y = (entites[p].display.ray) * Math.sin(entites[p].display.alpha * (Math.PI/180));
+            let i_x = (entites[i].display.ray) * Math.cos(entites[i].display.alpha * (Math.PI/180));
+            let i_y = (entites[i].display.ray) * Math.sin(entites[i].display.alpha * (Math.PI/180));
+            let q_x = (entites[q].display.ray) * Math.cos(entites[q].display.alpha * (Math.PI/180));
+            let q_y = (entites[q].display.ray) * Math.sin(entites[q].display.alpha * (Math.PI/180));
+            
+            let val = (i_y - p_y) * (q_x - i_x) - (i_x - p_x) * (q_y - i_y);
+            
+            // If i is more clockwise than current q, then update q
+            if (val > 0){
+              q = i;
+            }
+          // }
         }
 
         // Now q is the most clockwise with
@@ -817,7 +837,8 @@ export default {
       });
       // rebuild the contours with the new ray
       this.parseEntityPointsValues()
-      this.sailentEntites = this.concernement.sailentEntites = this.getJarvisEnvelopeConvexeEntites(this.entites)
+      let activeEntites = this.getActiveEntites(this.entites);
+      this.sailentEntites = this.concernement.sailentEntites = this.getJarvisEnvelopeConvexeEntites(activeEntites)
       // redraw the contours
       this.paper_main_object.addChild(this.setPaperContour());
 
@@ -916,8 +937,9 @@ export default {
         console.log(`entite len : ${this.concernement.revisions_byid[this.active_revision].entites.length}`);
 
         this.parseEntityPointsValues()
-  
-        this.sailentEntites = this.getJarvisEnvelopeConvexeEntites(this.concernement.revisions_byid[this.active_revision].entites);
+        
+        let activeEntites = this.getActiveEntites(this.concernement.revisions_byid[this.active_revision].entites);
+        this.sailentEntites = this.getJarvisEnvelopeConvexeEntites(activeEntites);
         
         // remove contours if already exists
         if (this.paper_main_object.children && this.paper_main_object.children.contours) {
@@ -1098,23 +1120,29 @@ export default {
       // for (let i = 0; i < this.entites.length; i++) {
       for (let i = 0; i < this.concernement.revisions_byid[this.active_revision].entites.length; i++) {
         let entite = this.concernement.revisions_byid[this.active_revision].entites[i];
+
+        if (!entite.active && !this.concernement.can_update) {
+          continue;
+        }
+
         // item type
-        let item_type = entite.entite ? entite.entite.agissante ? 'entite_action' : 'entite' : 'entite_hidden';
+        let item_type = entite.entite ? entite.entite.agissante ? 'entite_action' : entite.active ? 'entite' : 'entite_inactive' : 'entite_hidden';
 
         // use paper symbol
         let symbol_name = item_type;
-        switch (item_type) {
-          case 'entite':
-            if(parseInt(this.opened_entite_id) === entite.entite.id){
+        if(parseInt(this.opened_entite_id) === entite.entite.id){
+          switch (item_type) {
+            case 'entite':
               symbol_name = 'entite_hover'; 
-            } 
-            break;
-          case 'entite_action':
-            if(parseInt(this.opened_entite_id) === entite.entite.id){
+              break;
+            case 'inactive_entite':
+              symbol_name = 'entite_inactive_hover'; 
+              break;
+            case 'entite_action':
               symbol_name = 'entite_action_hover'; 
-            } 
-            break;
-        }
+              break;
+          }
+        } 
 
         // console.log(`item_type: ${item_type}, symbol_name: ${symbol_name}`);
 
@@ -1398,9 +1426,10 @@ export default {
           }); // end of loop on proximite for each main entite
           
 
+          let activeEntites = this.getActiveEntites(allEntiteRefConcernementContourEntites);
           // general contour arround proximité
           // console.log('allEntiteRefConcernementContourEntites', allEntiteRefConcernementContourEntites);
-          let genContoursEntite = this.getJarvisEnvelopeConvexeEntites(allEntiteRefConcernementContourEntites);
+          let genContoursEntite = this.getJarvisEnvelopeConvexeEntites(activeEntites);
           // console.log('genContoursEntite', genContoursEntite);
           let points = [];
           genContoursEntite.forEach(ent => {
@@ -2132,11 +2161,15 @@ export default {
           group.children.forEach((item) => {
             if (this.opened_entite_id && item.item_id === parseInt(this.opened_entite_id) && item.item_type !== 'entite_hidden') {
               // switch symbole definition to hover
-              item.definition = item.item_type === 'entite' ? this.paper_symbol_definitions.entite_hover : this.paper_symbol_definitions.entite_action_hover;
+              item.definition = item.item_type === 'entite' ? this.paper_symbol_definitions.entite_hover 
+                : item.item_type === 'entite_inactive' ? this.paper_symbol_definitions.entite_inactive_hover 
+                  : this.paper_symbol_definitions.entite_action_hover;
             } else {
               // switch back symbole definition to normal
               if ((!this.hover_elmt || item.item_id !== this.hover_elmt.id) && item.item_type !== 'entite_hidden') {
-                item.definition = item.item_type === 'entite' ? this.paper_symbol_definitions.entite : this.paper_symbol_definitions.entite_action;
+                item.definition = item.item_type === 'entite' ? this.paper_symbol_definitions.entite 
+                  : item.item_type === 'entite_inactive' ? this.paper_symbol_definitions.entite_inactive 
+                    : this.paper_symbol_definitions.entite_action;
               }
             }
           })

+ 3 - 2
src/components/ConcernementMapPopup.vue

@@ -52,12 +52,13 @@ export default {
   },
   methods: {
     parseInfos(){
-      // console.log('mapPopup infos', this.infos);
+      console.log('mapPopup infos', this.infos);
       switch (this.infos.type) {
         case 'concernement':
           this.concernement = this.concernementsByID[this.infos.cid];  
           break;
         case 'entite':
+        case 'entite_inactive':
         case 'entite_proximite':
         case 'entite_superpose':
         case 'entite_action':
@@ -156,7 +157,7 @@ export default {
         </div>
       </section>
 
-      <section v-if="infos.type === 'entite' || infos.type === 'entite_action' || infos.type === 'superposition' || infos.type === 'entite_superpose' || infos.type === 'entite_proximite'" class="entite-map-popup">
+      <section v-if="infos.type === 'entite' || infos.type === 'entite_inactive' || infos.type === 'entite_action' || infos.type === 'superposition' || infos.type === 'entite_superpose' || infos.type === 'entite_proximite'" class="entite-map-popup">
         <template
           v-for="(entite, index) of entites"
           :key="index"

+ 75 - 5
src/components/MapConcernements.vue

@@ -405,8 +405,10 @@ export default {
       // this.addPaperSymbolDefinition('doleance_icon', this.setPaperDoleanceICONSymbol());
       if (!update) {
         this.addPaperSymbolDefinition('entite', this.setPaperEntiteSymbol());
-        this.addPaperSymbolDefinition('entite_hidden', this.setPaperHiddenEntiteSymbol());
         this.addPaperSymbolDefinition('entite_hover', this.setPaperEntiteHoverSymbol());
+        this.addPaperSymbolDefinition('entite_inactive', this.setPaperInactiveEntiteSymbol());
+        this.addPaperSymbolDefinition('entite_inactive_hover', this.setPaperInactiveEntiteHoverSymbol());
+        this.addPaperSymbolDefinition('entite_hidden', this.setPaperHiddenEntiteSymbol());
         this.addPaperSymbolDefinition('entite_action_icon', this.setPaperEntiteActionIconSymbol());
         this.addPaperSymbolDefinition('entite_action', this.setPaperEntiteActionSymbol());
         this.addPaperSymbolDefinition('entite_action_hover', this.setPaperEntiteActionHoverSymbol());
@@ -1089,6 +1091,16 @@ export default {
         strokeWidth:2
       })
     },
+    setPaperEntiteHoverSymbol(){
+      return new paper.Path.Circle({
+        pivot: new paper.Point({x:0,y:0}),
+        center: [0,0],
+        radius: 0.5,
+        fillColor: '#01ffe2',
+        strokeColor: 'rgba(255,255,255,0.05)',
+        strokeWidth:2
+      })
+    },
     setPaperHiddenEntiteSymbol(){
       return new paper.Path.Circle({
         pivot: new paper.Point({x:0,y:0}),
@@ -1099,16 +1111,74 @@ export default {
         strokeWidth:2
       })
     },
-    setPaperEntiteHoverSymbol(){
+    setPaperInactiveEntiteSymbol(){
       return new paper.Path.Circle({
         pivot: new paper.Point({x:0,y:0}),
         center: [0,0],
-        radius: 0.5,
-        fillColor: '#01ffe2',
-        strokeColor: 'rgba(255,255,255,0.05)',
+        radius: 0.5, //0.3
+        fillColor: '#F00',
+        strokeColor: 'rgba(255,0,0,0.05)',
         strokeWidth:2
       })
     },
+    setPaperInactiveEntiteHoverSymbol(){
+      // let svgIcon = paper.project.importSVG(iconAction);
+      // svgIcon.strokeColor = '#01ffe2';
+      // svgIcon.strokeWidth = 0.25;
+      // svgIcon.scale(0.15);
+      // svgIcon.fillColor = null;
+      // svgIcon.position = {x:0, y:0};
+
+      // let circle = new paper.Path.Circle({
+      //   radius: 3,
+      //   fillColor: 'rgba(255,255,255,0.05)'
+      // })
+
+      // return new paper.Group({
+      //   children: [circle, svgIcon],
+      //   name: 'action_icon'
+      // });
+      let children = [];
+      let ray = 0.7; //this.map_item_ray;
+      // let pos = {x:0, y:0};
+      // let strokewidth = 0.4;
+
+      // children.push(new paper.Path.Line({
+      //   from: [- ray, ray],
+      //   to: [ray, - ray],
+      //   strokeColor: '#01ffe2',
+      //   strokeWidth: strokewidth
+      // })); 
+
+      // children.push(new paper.Path.Line({
+      //   from: [ray, ray],
+      //   to: [- ray, - ray],
+      //   strokeColor: '#01ffe2',
+      //   strokeWidth: strokewidth
+      // })); 
+
+      children.push(new paper.Path.Circle({
+        radius: ray/2,
+        fillColor: '#F00'
+      }))
+
+      children.push(new paper.Path.Circle({
+        radius: ray * 1.2,
+        strokeColor: '#01ffe2',
+        strokeWidth: 0.2,
+        fillColor: 'rgba(0,0,0,0)'
+      }))
+
+      children.push(new paper.Path.Circle({
+        radius: ray,
+        fillColor: 'rgba(255,255,255,0.01)'
+      }))
+
+      return new paper.Group({
+        children: children,
+        name: 'action_icon'
+      });
+    },
     setPaperEntiteActionIconSymbol(){
       // let svgIcon = paper.project.importSVG(iconAction);
       // svgIcon.strokeWidth = 0.8;

+ 26 - 8
src/components/contents/TerrainDeVie.vue

@@ -233,6 +233,14 @@ export default {
         .then(({data: { data: { entite }}}) => {
           console.log('load entite loaded', entite)
           this.entite = entite;
+
+          // record the paragraphe field id from concernement wher the entity is referenced
+          this.concernement.entites.forEach(parag_entite => {
+            if (parag_entite.entite && parag_entite.entite.id === this.entite.id) {
+              this.parag_entite = parag_entite;
+            }
+          });
+
           // this.setProxSuggestions()
           this.checkForUserProxmite();
         })
@@ -567,6 +575,18 @@ export default {
           </div>
         </section>
 
+        <!-- active -->
+        <CheckboxEditable 
+          v-if="entite && entite.can_update"
+          :checked="this.parag_entite.active"
+          label="Active" 
+          :data="{
+            entitytype: 'paragraph',
+            bundle: 'entite_concernement',
+            id: this.parag_entite.id,
+            field: 'field_active'}"
+            v-on:updated="reloadConcernementEntites(cid)"/>
+
         <!-- agissante -->
         <CheckboxEditable 
           v-if="entite && entite.can_update"
@@ -575,9 +595,9 @@ export default {
           :data="{
             entitytype: 'node',
             bundle: 'entite',
-            nid: this.entite.id,
-            field: 'field_entite_agissante'
-          }" />
+            id: this.entite.id,
+            field: 'field_entite_agissante'}"
+            v-on:updated="reloadConcernementEntites(cid)"/>
 
         <SelectEditable 
           v-if="entite && entite.can_update"
@@ -586,15 +606,13 @@ export default {
           :options="{
             'confidentialite_prive': 'privé',
             'confidentialite_interne': 'interne',
-            'confidentialite_public': 'public'
-          }"
+            'confidentialite_public': 'public'}"
           :data="{
             entitytype: 'node',
             bundle: 'entite',
             nid: this.entite.id,
-            field: 'field_confidentialite'
-          }" />
-
+            field: 'field_confidentialite'}"
+            v-on:updated="reloadConcernementEntites(cid)"/>
 
 
       </div>

+ 11 - 4
src/components/editable/CheckboxEditable.vue

@@ -38,17 +38,24 @@ export default {
     },
     save(checked){
       // console.log('save csrf_token', this.csrf_token);
-      const params = {
+      let params = {
         type: this.data.bundle,
-        nid: [{"value":this.data.nid}],
+        // nid: [{"value":this.data.nid}],
         [this.data.field]: {value: checked}
       };
+      if (this.data.entitytype === 'node') {
+        params.nid = [{"value":this.data.id}];
+      } else {
+        params.id = [{"value":this.data.id}];
+      }
       const configs = {
         headers: {'X-CSRF-Token': this.csrf_token}
       };
-      REST.patch(`/node/${this.data.nid}?_format=json`, params, configs)
+
+      let url_base = `/${this.data.entitytype === 'node' ? '' : 'entity/'}${this.data.entitytype}`;
+      REST.patch(`${url_base}/${this.data.id}?_format=json`, params, configs)
         .then(({ data }) => {
-          console.log('user REST post node data', data)
+          console.log(`checkboxEditable REST patch ${this.data.entitytype} data`, data)
           // TODO if success update the data in pinia 
           // this.reloadConcernements();
           this.$emit('updated');

+ 68 - 56
src/stores/concernements.js

@@ -74,7 +74,7 @@ export const ConcernementsStore = defineStore({
         // console.log('ast', ast);
         GQL.post('', { query: print(ast) })
           .then(({ data : { data  : { allconcernements } } }) => {
-            console.log('loadconcernements loaded', allconcernements)
+            console.log('|||||||||||| loadconcernements loaded ||||||||||||||||||', allconcernements)
             this.concernements = [];
             this.parseConcernements(allconcernements, true)
             this.concernements_loaded = true;
@@ -108,73 +108,80 @@ export const ConcernementsStore = defineStore({
             if (!concernement.entites_byid[entite.entite.id]) {
               concernement.entites_byid[entite.entite.id] = entite;
             }
-            // record entite agissante
-            if (entite.entite.agissante) {
-              if (!concernement.entitesagissantes_byid[entite.entite.id]) {
-                concernement.entitesagissantes_byid[entite.entite.id] = entite;
-              }
-              concernement.has_agissantes = true;
-            }
+
             // record a flat list of all entités of all concernement for map-popup
             if (!this.allEntitesById[entite.entite.id]) {
               this.allEntitesById[entite.entite.id] = entite;
               this.allEntitesById[entite.entite.id].cid = concernement.id;
             }
-        
-            // PROXIMITES
-            if (entite.entite.proximite.length) {
-              // console.log("proximite", entite.entite.proximite);
-              concernement.has_proximites = true;
-            }
 
-            // SUPERPOSITIONS
-            if (entite.entite.superposition.length) {
-              // create properties for later
-              if (init) {
-                concernement.superpositions = {};
-                concernement.superposition_constraints_id = {};
-                concernement.all_superposed_concernements_id = [];
-                concernement.superposed_mapitem_id_by_mapitem_id = {};
+            // Do not integrate the entite if not active
+            if(entite.active){
+              // record entite agissante
+              if (entite.entite.agissante) {
+                if (!concernement.entitesagissantes_byid[entite.entite.id]) {
+                  concernement.entitesagissantes_byid[entite.entite.id] = entite;
+                }
+                concernement.has_agissantes = true;
               }
 
-              entite.entite.superposition.forEach(entite_superpose => {
-                // console.log(`superposition eid:${entite.entite.id}, teid:${entite_superpose.id}`);
-                let already_recorded = false;
-                // loop through all already recorded superposition to complete the array instead of create duplicates
-                // TODO check if target cid and eid are accessible before recording the superposition
-                // check if half of the superpositions is already recorded, if yes complete it (add the missing concernement id)
-                for(let superposition of temp_allSuperpositions) { 
-                  for(let superposition_item of superposition) {
-                    if (superposition_item.eid === entite.entite.id && !superposition_item.cid) {
-                      // console.log(`already_recorded, eid:${entite.entite.id}, teid:${entite_superpose.id}`, entite.entite.title);
-                      already_recorded = true;
-                      superposition_item.cid = concernement.id;
+              // PROXIMITES
+              if (entite.entite.proximite.length) {
+                // console.log("proximite", entite.entite.proximite);
+                concernement.has_proximites = true;
+              }
+              
+              // SUPERPOSITIONS
+              if (entite.entite.superposition.length) {
+                // create properties for later
+                if (init) {
+                  concernement.superpositions = {};
+                  concernement.superposition_constraints_id = {};
+                  concernement.all_superposed_concernements_id = [];
+                  concernement.superposed_mapitem_id_by_mapitem_id = {};
+                }
+  
+                entite.entite.superposition.forEach(entite_superpose => {
+                  // console.log(`superposition eid:${entite.entite.id}, teid:${entite_superpose.id}`);
+                  let already_recorded = false;
+                  // loop through all already recorded superposition to complete the array instead of create duplicates
+                  // TODO check if target cid and eid are accessible before recording the superposition
+                  // check if half of the superpositions is already recorded, if yes complete it (add the missing concernement id)
+                  for(let superposition of temp_allSuperpositions) { 
+                    for(let superposition_item of superposition) {
+                      if (superposition_item.eid === entite.entite.id && !superposition_item.cid) {
+                        // console.log(`already_recorded, eid:${entite.entite.id}, teid:${entite_superpose.id}`, entite.entite.title);
+                        already_recorded = true;
+                        superposition_item.cid = concernement.id;
+                        break;
+                      }
+                    }
+                    if (already_recorded) {
                       break;
                     }
                   }
-                  if (already_recorded) {
-                    break;
+                  // if not already recorded, add it to the array. It is incomplete has it's missing one concernement id wich will be filled in next loops
+                  if (!already_recorded) {
+                    // console.log(`NOT already_recorded, eid:${entite.entite.id}, teid:${entite_superpose.id}`, entite.entite.title);
+                    let s = [
+                      { 
+                        cid: concernement.id, 
+                        eid: entite.entite.id
+                      },
+                      {
+                        cid: null,
+                        eid: entite_superpose.id
+                      }
+                    ];
+                    // concernement.superpositions.push(s); 
+                    temp_allSuperpositions.push(s);
+  
                   }
-                }
-                // if not already recorded, add it to the array. It is incomplete has it's missing one concernement id wich will be filled in next loops
-                if (!already_recorded) {
-                  // console.log(`NOT already_recorded, eid:${entite.entite.id}, teid:${entite_superpose.id}`, entite.entite.title);
-                  let s = [
-                    { 
-                      cid: concernement.id, 
-                      eid: entite.entite.id
-                    },
-                    {
-                      cid: null,
-                      eid: entite_superpose.id
-                    }
-                  ];
-                  // concernement.superpositions.push(s); 
-                  temp_allSuperpositions.push(s);
-
-                }
-              })
+                })
+              }
             }
+        
+
 
           }
         }); // end of concernement.entites loop
@@ -317,7 +324,7 @@ export const ConcernementsStore = defineStore({
     },
     reloadConcernementEntites(nid){
       let tmp_conc = this.concernementsByID[nid];
-      console.log(`reloadConcernementEntites len: ${tmp_conc.entites.length} revision: ${tmp_conc.revision_id} nid: ${nid}`);
+      console.log(`reloadConcernementEntites len: ${tmp_conc.entites.length} revision: ${tmp_conc.revision_id} nid: ${nid} ${Math.random()*10000}`);
       return new Promise((resolve, reject) => {
         const ast = gql`{
           concernement(id:${nid}) {
@@ -325,8 +332,10 @@ export const ConcernementsStore = defineStore({
             entites {
               id
               revision_id
+              active
               menacemaintien
               actuelfuture
+              prise
               entite {
                 title
                 id
@@ -346,6 +355,9 @@ export const ConcernementsStore = defineStore({
               revision_id
               changed
               entites {
+                id
+                revision_id
+                active
                 menacemaintien
                 prise
                 actuelfuture