Browse Source

started map: drawing concernement with entities and contours

bach 1 year ago
parent
commit
e1978d8e58

File diff suppressed because it is too large
+ 536 - 162
package-lock.json


+ 57 - 13
src/App.vue

@@ -1,27 +1,71 @@
+<script>
+import { RouterLink, RouterView } from 'vue-router'
 
+import { mapState, mapActions } from 'pinia'
+import { UserStore } from '@/stores/user'
+import { ConcernementsStore } from '@/stores/concernements'
 
-<script setup>
-  import { onMounted } from 'vue';
-  import { RouterLink, RouterView } from 'vue-router'
-  import { UserStore } from '@/stores/user'
-  import Header from '@components/Header.vue'
+import StaticMenu from '@components/block/StaticMenu.vue'
+import UserBlock from '@components/block/UserBlock.vue'
 
+import MapConcernements from '@components/MapConcernements.vue'
+import ConcernementMapItem from '@components/ConcernementMapItem.vue'
 
-  const userStore = UserStore()
-
-  onMounted(() => {
+export default {
+  created () {
+    this.loadConcernements()
+  },
+  mounted () {
     console.log('APP onMounted')
-    userStore.checkUser()
-  })
-
+    this.checkUser()      
+  },
+  computed: {
+    ...mapState(UserStore,['isloggedin']),
+    ...mapState(ConcernementsStore,['concernements'])
+  },
+  methods: {
+    ...mapActions(ConcernementsStore,['loadConcernements']),
+    ...mapActions(UserStore,['checkUser'])
+  },
+  components: {
+    MapConcernements,
+    ConcernementMapItem,
+    StaticMenu,
+    UserBlock
+  }
+}
 
 </script>
 
 <template>
-  <Header />
+  <header id="header">
+    <div class="row top">
+      <h1>
+        <router-link :to="{ name: 'home' }">Où atterrir</router-link>  
+      </h1>
+    </div>
+    <div class="row bottom">
+      <StaticMenu/>
+      <UserBlock/>
+    </div>
+  </header>
+
   <div id="main-content">
-    <RouterView />
+    <MapConcernements v-if="isloggedin">
+      <ConcernementMapItem
+        v-for="concernement in concernements"
+        :key="concernement.id"
+        :concernement="concernement"
+      />
+      <!-- <ConcernementMapItem
+        :concernement="concernements[0]"
+      /> -->
+    </MapConcernements>
+    <div class="row">
+      <RouterView />
+    </div>
   </div>
+
 </template>
 
 <style lang="scss" scoped>

+ 3 - 0
src/assets/colors.scss

@@ -0,0 +1,3 @@
+$back: #eee;
+$front: #fff;
+$btns_back: #ddd;

+ 7 - 0
src/assets/common.scss

@@ -0,0 +1,7 @@
+$pad_btn: 0.5em;
+@mixin btn() {
+  padding: $pad_btn;
+  border-radius: 5px;
+  background-color: $btns_back;
+  cursor: pointer;
+}

+ 24 - 5
src/assets/layout.scss

@@ -4,12 +4,18 @@ html,body{
 
 }
 
+
+@mixin layout-row{
+  box-sizing: content-box;
+  max-width: 1920px;
+  margin: 0 auto;
+  padding: 0 1rem;
+}
+
 #app {
   box-sizing: border-box;
-  max-width: 1920px;
+  width: 100vw;
   height: 100vh;
-  margin: 0 auto;
-  padding: 1rem;
 
   font-weight: normal;
 
@@ -18,11 +24,24 @@ html,body{
 }
 
 #header{
-  flex: 0 0 5rem;
+  flex: 0 0 auto;
+  padding: 1rem 0;
+  >.row{
+    @include layout-row();
+  }
 }
 
 #main-content{
   flex: 1 1 auto;
-
+  overflow-x: hidden;
+  overflow-y: auto;
+  // padding: 1rem 0;
+  #map-concernements{
+    width:100%;
+    height:100%;
+  }
+  >.row{
+    @include layout-row();
+  }
 }
 

+ 16 - 4
src/assets/main.scss

@@ -1,23 +1,35 @@
 // @import './node_modules/@mdi/font/scss/materialdesignicons.scss';
 @import "./base.scss";
+@import "./colors.scss";
+@import "./common.scss";
 @import "./layout.scss";
 @import "./fonts/snap_it/snap_it.css";
 
+body{
+  background-color: $back;
+}
 
 #app{
 
 }
 
-#app>header{
+#app>header#header{
+  background-color: $front;
   .row{
     display: flex;
     flex-direction: row;
     >*{
       margin-right: 1em;
     }
-  }
+    &.top{
+      h1{
+        font-family: "snap-it";
+      }
+    }
+    &.bottom{
+      padding:1em 0;
 
-  h1{
-    font-family: "snap-it";
+    }
   }
+
 }

+ 156 - 22
src/components/ConcernementMapItem.vue

@@ -10,33 +10,167 @@
 // import MA from '/api/ma-axios'
 
 export default {
+  inject: ['canvasMap'],
+  data() {
+    return {
+      canvas: null,
+      ctx: null,
+      pos : {
+        x: 0,
+        y: 0
+      },
+      ray: 60,
+      time: 0,
+      salientPoints: []
+    }
+  },
   props: ['concernement'],
-  // data(){
-  //   return {
-  //     block: null
-  //   }
-  // },
-  // computed: {
-  //   ...mapState(UserStore,['isloggedin'])
-  // },
   created () {
-    console.log("ConcernementsMapItem created");
+    console.log("ConcernementsMapItem concernement", this.concernement);
+    this.entites = this.concernement.entites
+    this.parsePoints()
+    this.getSalientPoints()
+  },
+  watch: {
+    // canvasMap (n, o) {
+    //   console.log("concernementItem watch canvasMap", o, n);
+    // }
+    canvasMap: {
+      handler (n, o){
+        // console.log("concernementItem watch canvasMap.ctx", o, n);
+        this.canvas = this.canvasMap.canvas
+        this.ctx = this.canvasMap.ctx
+        this.pos.x = this.ray/2 + Math.random()*(this.canvas.width - this.ray)
+        this.pos.y = this.ray/2 + Math.random()*(this.canvas.height - this.ray)
+        // listen for animate event dispatched from parent
+        this.canvas.addEventListener('animate', this.animate)
+      },
+      deep: true
+    } 
   },
   methods: {
-    // ...mapActions(ConcernementsStore,['loadConcernements'])
-  }
-  // components: {
-  //   LoginBlock,
-  //   UserTools
-  // }
-}
+    parsePoints (){
+      for (let i = 0; i < this.entites.length; i++) {
+        let entite = this.entites[i]
+        console.log('entite', entite);
+        
+        this.entites[i].display = {
+          alpha: null,
+          ray: null
+        }
+        // RAYON
+        // https://stackoverflow.com/questions/5731863/mapping-a-numeric-range-onto-another
+        // slope = (output_end - output_start) / (input_end - input_start)
+        // output = output_start + slope * (input - input_start)
+        // from range 0 -> 100 to range 0 -> this.ray
+        let slope = this.ray / 100
+        this.entites[i].display.ray = slope * (100 - entite.prise);
 
-</script>
+        // ANGLE
+        // -90 <= mm <= 90
+        if (entite.actuelfuture) {
+          // future en haut : 180 <= a <= 360
+          // from -90 -> 90 to range 180 -> 360
+          this.entites[i].display.alpha = entite.menacemaintien + 270
+        } else {
+          // actuel: en bas : O <= a <= 180
+          // from -90 -> 90 to range 180 -> 0
+          this.entites[i].display.alpha = -1 * entite.menacemaintien + 90
+        }
+
+        // POSITION X Y (par rapport au centre de l'entite)
+        this.entites[i].display.pos = {
+          x: this.entites[i].display.ray * Math.cos(this.entites[i].display.alpha * (Math.PI/180)),
+          y: this.entites[i].display.ray * Math.sin(this.entites[i].display.alpha * (Math.PI/180))
+        }
+      }
+    },
+    getSalientPoints () {
+      // debugger
+      // console.log(this.entites);
+      let arc = 60;
+      // loop through arcs
+      for (let i = 0; i <= 360/arc; i++) {
+        // loop through entities to find the farest on the arc
+        let max_r = 0;
+        let farest = null;
+        for (let j = 0; j < this.entites.length; j++) {
+          let entite = this.entites[j];
+          if(arc*i <= entite.display.alpha && entite.display.alpha <= arc*i+arc) {
+            // if entity is in arc
+            if (entite.display.ray > max_r) {
+              // if entity is farest from precedent one
+              max_r = entite.display.ray;
+              farest = entite;
+            }
+          }
+        }
+        if (farest) {
+          this.salientPoints.push(farest.display)
+        }
+      }
+      console.log('this.salientPoints', this.salientPoints);
+    },
+    animate () {
+      if (this.ctx) {
+        // this var is only here to trigger the render()
+        this.time = Date.now();
+        // this.pos.x += 0;
+      }
+    }
+  },
+  render() {
+    // console.log('render()');
+    
+    if (!this.ctx) return;
+    
+    // this var is only here to trigger the render() from animate()
+    let time = this.time;
 
-<template>
-    <span>{{ concernement.title }}</span>
-</template>
+    // place all entities points
+    for (let i = 0; i < this.entites.length; i++) {
+      let entite = this.entites[i];
+      // console.log('entite', entite);
+      this.ctx.beginPath();
+      this.ctx.arc(this.pos.x+entite.display.pos.x, this.pos.y+entite.display.pos.y, 2, 0, 2 * Math.PI, false);
+      this.ctx.fillStyle = "#F00";
+      this.ctx.fill();
+    }
 
-<style lang="css" scoped>
+    // concernement id @center
+    this.ctx.beginPath();
+    // this.ctx.arc(this.pos.x, this.pos.y, 4, 0, 2 * Math.PI, false);
+    this.ctx.fillStyle = "#000";
+    this.ctx.fillText(this.concernement.id, this.pos.x, this.pos.y)
+    this.ctx.fill();
 
-</style>
+    // exterieur circle
+    this.ctx.beginPath();
+    this.ctx.lineWidth = 0.5;
+    this.ctx.strokeStyle = "#888";
+    this.ctx.arc(this.pos.x, this.pos.y, this.ray, 0, 2 * Math.PI, false);
+    this.ctx.stroke();
+
+    // exterieur circle
+    this.ctx.beginPath();
+    this.ctx.lineWidth = 0.5;
+    this.ctx.strokeStyle = "#888";
+    this.ctx.arc(this.pos.x, this.pos.y, this.ray/2, 0, 2 * Math.PI, false);
+    this.ctx.stroke();
+
+    // contours
+    if (this.salientPoints.length > 3) {
+      this.ctx.beginPath();
+      this.ctx.lineWidth = 1;
+      this.ctx.strokeStyle = "#000";
+      this.ctx.moveTo(this.pos.x+this.salientPoints[0].pos.x, this.pos.y+this.salientPoints[0].pos.y)
+      for (let j = 1; j < this.salientPoints.length; j++) {
+        this.ctx.lineTo(this.pos.x+this.salientPoints[j].pos.x, this.pos.y+this.salientPoints[j].pos.y)
+      }
+      this.ctx.lineTo(this.pos.x+this.salientPoints[0].pos.x, this.pos.y+this.salientPoints[0].pos.y)
+      this.ctx.stroke();
+    }
+  },
+}
+
+</script>

+ 0 - 46
src/components/Header.vue

@@ -1,46 +0,0 @@
-<script>
-
-import { mapState } from 'pinia'
-import { UserStore } from '@/stores/user'
-import UserBlock from '@components/block/UserBlock.vue'
-import StaticMenu from '@components/block/StaticMenu.vue'
-
-
-export default {
-  // data(){
-  //   return {
-  //     block: null
-  //   }
-  // },
-  computed: {
-    ...mapState(UserStore,['isloggedin'])
-  },
-  methods: {
-    
-  },
-  components: {
-    UserBlock,
-    StaticMenu
-  }
-}
-
-</script>
-
-<template>
-    <header>
-    <div class="row top">
-      <h1>
-        <router-link :to="{ name: 'home' }">Où atterrir</router-link>  
-      </h1>
-    </div>
-    <div class="row bottom">
-      <StaticMenu/>
-      <UserBlock/>
-    </div>
-  </header>
-
-</template>
-
-<style lang="scss" scoped>
-
-</style>

+ 37 - 27
src/components/MapConcernements.vue

@@ -1,32 +1,50 @@
 <script>
 
-import { mapActions, mapState } from 'pinia'
-import { ConcernementsStore } from '@/stores/concernements'
-import ConcernementMapItem from '@components/ConcernementMapItem.vue'
-
+// import { mapActions, mapState } from 'pinia'
+import { computed } from 'vue'
 // import LoginBlock from '@components/block/LoginBlock.vue'
 // import UserTools from '@components/block/UserTools.vue'
 
 // import MA from '/api/ma-axios'
 
+// https://www.digitalocean.com/community/tutorials/vuejs-vue-html5-canvas
+
 export default {
-  // data(){
-  //   return {
-  //     block: null
-  //   }
-  // },
-  computed: {
-    ...mapState(ConcernementsStore,['concernements'])
+  data() {
+    return {
+      canvasMap: {
+        canvas: null,
+        ctx: null
+      },
+      animateEvent: new Event('animate')
+    }
   },
-  created () {
-    console.log("mapConcernements created");
-    this.loadConcernements()
+  provide() {
+    return {
+      // explicitly provide a computed property
+      canvasMap: computed(() => this.canvasMap)
+    }
   },
+  mounted() {
+    this.canvasMap.canvas = this.$refs['canvas-map'];
+    this.canvasMap.ctx = this.canvasMap.canvas.getContext('2d');
+
+    this.canvasMap.canvas.width = this.canvasMap.canvas.parentElement.clientWidth;
+    this.canvasMap.canvas.height = this.canvasMap.canvas.parentElement.clientHeight;
+    this.animate()
+  },
+  // computed: {
+  // },
+  // created () {
+  // },
   methods: {
-    ...mapActions(ConcernementsStore,['loadConcernements'])
+    animate () {
+      this.canvasMap.ctx.clearRect(0, 0, this.canvasMap.canvas.width, this.canvasMap.canvas.height)
+      this.canvasMap.canvas.dispatchEvent(this.animateEvent)
+      window.requestAnimationFrame(this.animate);
+    }
   },
-  components: {
-    ConcernementMapItem
+  beforeUpdate () {
   }
 }
 
@@ -34,16 +52,8 @@ export default {
 
 <template>
   <div id="map-concernements">
-    <!-- <h1>Concernements</h1> -->
-    <!-- <canvas rel="canvas-map"></canvas> -->
-    <ul>
-      <li
-        v-for="concernement in concernements"
-        v-bind:key="concernement.id"
-      >
-        <ConcernementMapItem :concernement="concernement"/>
-      </li>
-    </ul>
+    <canvas ref="canvas-map"></canvas>
+    <slot></slot>
   </div>
 </template>
 

+ 46 - 7
src/components/block/LoginBlock.vue

@@ -54,13 +54,52 @@ export default {
 </script>
 
 <template>
-  <form action="" @submit.prevent="onSubmitLogin">
-    <input type="email" placeholder="email" name="email" v-model="mail">
-    <input type="password" placeholder="mot de passe" name="passwd" v-model="passwd">
-    <input type="submit" value="se connecter">
-    <p v-if="loginMessage">{{ loginMessage }}</p>
-  </form>
+  <div id="login-block">
+    <span>connexion</span>
+    <form action="" @submit.prevent="onSubmitLogin">
+      <input type="email" placeholder="email" name="email" v-model="mail">
+      <input type="password" placeholder="mot de passe" name="passwd" v-model="passwd">
+      <input type="submit" value="se connecter">
+      <p v-if="loginMessage">{{ loginMessage }}</p>
+    </form>
+  </div>
 </template>
 
-<style lang="css" scoped>
+<style lang="scss" scoped>
+  $pad: 1em;
+  #login-block{
+    position: relative;
+
+    span{
+      display: inline-block;
+      @include btn();
+    }
+    form{
+      background-color: #fff;
+      border-radius: 5px;
+      padding: 0 $pad;
+      position: absolute;
+      bottom: 100%;
+      left: -$pad;
+      >*{
+        margin: 0 0 0.5em 0;
+      }
+      overflow: hidden;
+      max-height:1px;
+      opacity: 0;
+      $delay: 4s;
+      transition: opacity 0.3s ease-out $delay,max-height 0.3s ease-out $delay, padding 0.3s ease-out $delay + 0.1s;
+    }
+    &:hover{
+      form{
+        box-shadow: 0 0 5px $btns_back;
+        padding: $pad;
+        max-height: 100px;
+        opacity: 1;
+        transition: opacity 0.3s ease-out,max-height 0.3s ease-out, padding 0.3s ease-out 0.1s;
+      }
+    }
+  }
+  
+
 </style>

+ 4 - 0
src/components/block/StaticMenu.vue

@@ -51,6 +51,10 @@ export default {
     padding: 0;
     margin: 0;
     list-style: none;
+    display: flex;
+    a{
+      @include btn();
+    }
   }
 
 </style>

+ 4 - 1
src/components/block/UserTools.vue

@@ -47,6 +47,9 @@ export default {
   #user-tools{
     display: flex;
     flex-direction: row;
-    gap: 0.5em;
+    gap: 0em;
+    a{
+      padding: $pad_btn;
+    }
   }
 </style>

+ 2 - 1
src/main.js

@@ -9,8 +9,9 @@ import '@mdi/font/css/materialdesignicons.css'
 import './assets/main.scss'
 
 const app = createApp(App)
+// https://vuejs.org/guide/components/provide-inject.html#provide
+app.config.unwrapInjectedRef = true;
 
 app.use(createPinia())
 app.use(router)
-
 app.mount('#app')

+ 1 - 1
src/stores/statics.js

@@ -36,7 +36,7 @@ export const StaticsStore = defineStore({
             this.statics = allstatics
             
             allstatics.forEach((s) => {
-              console.log("s", s);
+              // console.log("s", s);
               this.statics_byid[s.id] = s
             });
             console.log("statics_byid", this.statics_byid);

+ 3 - 6
src/views/Home.vue

@@ -1,8 +1,5 @@
 <script>
 
-import { mapState } from 'pinia'
-import { UserStore } from '@/stores/user'
-import MapConcernements from '@components/MapConcernements.vue'
 
 
 export default {
@@ -12,20 +9,20 @@ export default {
   //   }
   // },
   computed: {
-    ...mapState(UserStore,['isloggedin'])
+    // ...mapState(UserStore,['isloggedin'])
   },
   methods: {
     
   },
   components: {
-    MapConcernements
+    // MapConcernements
   }
 }
 
 </script>
 
 <template>
-  <MapConcernements v-if="isloggedin"/>
+
 </template>
 
 <style lang="scss" scoped>

+ 10 - 1
vite.config.js

@@ -15,5 +15,14 @@ export default defineConfig({
       '@api': fileURLToPath(new URL('./src/api', import.meta.url))
       // '@icons': fileURLToPath(new URL('./node_modules/vue-material-design-icons', import.meta.url)),
     }
-  }
+  },
+  css: {
+    preprocessorOptions: {
+      scss: { 
+         // example : additionalData: `@import "./src/design/styles/variables";`
+         // dont need include file extend .scss
+         additionalData: `@import "./src/assets/colors.scss";@import "./src/assets/common.scss";` 
+     },
+    },
+  },
 })

Some files were not shown because too many files changed in this diff