started map: drawing concernement with entities and contours

This commit is contained in:
Bachir Soussi Chiadmi 2023-02-22 13:40:44 +01:00
parent a8883be9b7
commit e1978d8e58
16 changed files with 1867 additions and 764 deletions

2123
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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 StaticMenu from '@components/block/StaticMenu.vue'
import { onMounted } from 'vue'; import UserBlock from '@components/block/UserBlock.vue'
import { RouterLink, RouterView } from 'vue-router'
import { UserStore } from '@/stores/user'
import Header from '@components/Header.vue'
import MapConcernements from '@components/MapConcernements.vue'
import ConcernementMapItem from '@components/ConcernementMapItem.vue'
const userStore = UserStore() export default {
created () {
onMounted(() => { this.loadConcernements()
},
mounted () {
console.log('APP onMounted') 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> </script>
<template> <template>
<Header /> <header id="header">
<div class="row top">
<h1>
<router-link :to="{ name: 'home' }"> atterrir</router-link>
</h1>
</div>
<div class="row bottom">
<StaticMenu/>
<UserBlock/>
</div>
</header>
<div id="main-content"> <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> </div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>

3
src/assets/colors.scss Normal file
View File

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

7
src/assets/common.scss Normal file
View File

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

View File

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

View File

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

View File

@ -10,33 +10,167 @@
// import MA from '/api/ma-axios' // import MA from '/api/ma-axios'
export default { export default {
inject: ['canvasMap'],
data() {
return {
canvas: null,
ctx: null,
pos : {
x: 0,
y: 0
},
ray: 60,
time: 0,
salientPoints: []
}
},
props: ['concernement'], props: ['concernement'],
// data(){
// return {
// block: null
// }
// },
// computed: {
// ...mapState(UserStore,['isloggedin'])
// },
created () { 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: { methods: {
// ...mapActions(ConcernementsStore,['loadConcernements']) parsePoints (){
} for (let i = 0; i < this.entites.length; i++) {
// components: { let entite = this.entites[i]
// LoginBlock, console.log('entite', entite);
// UserTools
// } 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);
// 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;
// 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();
}
// 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();
// 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> </script>
<template>
<span>{{ concernement.title }}</span>
</template>
<style lang="css" scoped>
</style>

View File

@ -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' }"> atterrir</router-link>
</h1>
</div>
<div class="row bottom">
<StaticMenu/>
<UserBlock/>
</div>
</header>
</template>
<style lang="scss" scoped>
</style>

View File

@ -1,32 +1,50 @@
<script> <script>
import { mapActions, mapState } from 'pinia' // import { mapActions, mapState } from 'pinia'
import { ConcernementsStore } from '@/stores/concernements' import { computed } from 'vue'
import ConcernementMapItem from '@components/ConcernementMapItem.vue'
// import LoginBlock from '@components/block/LoginBlock.vue' // import LoginBlock from '@components/block/LoginBlock.vue'
// import UserTools from '@components/block/UserTools.vue' // import UserTools from '@components/block/UserTools.vue'
// import MA from '/api/ma-axios' // import MA from '/api/ma-axios'
// https://www.digitalocean.com/community/tutorials/vuejs-vue-html5-canvas
export default { export default {
// data(){ data() {
// return { return {
// block: null canvasMap: {
// } canvas: null,
ctx: null
},
animateEvent: new Event('animate')
}
},
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 () {
// }, // },
computed: {
...mapState(ConcernementsStore,['concernements'])
},
created () {
console.log("mapConcernements created");
this.loadConcernements()
},
methods: { 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: { beforeUpdate () {
ConcernementMapItem
} }
} }
@ -34,16 +52,8 @@ export default {
<template> <template>
<div id="map-concernements"> <div id="map-concernements">
<!-- <h1>Concernements</h1> --> <canvas ref="canvas-map"></canvas>
<!-- <canvas rel="canvas-map"></canvas> --> <slot></slot>
<ul>
<li
v-for="concernement in concernements"
v-bind:key="concernement.id"
>
<ConcernementMapItem :concernement="concernement"/>
</li>
</ul>
</div> </div>
</template> </template>

View File

@ -54,13 +54,52 @@ export default {
</script> </script>
<template> <template>
<form action="" @submit.prevent="onSubmitLogin"> <div id="login-block">
<input type="email" placeholder="email" name="email" v-model="mail"> <span>connexion</span>
<input type="password" placeholder="mot de passe" name="passwd" v-model="passwd"> <form action="" @submit.prevent="onSubmitLogin">
<input type="submit" value="se connecter"> <input type="email" placeholder="email" name="email" v-model="mail">
<p v-if="loginMessage">{{ loginMessage }}</p> <input type="password" placeholder="mot de passe" name="passwd" v-model="passwd">
</form> <input type="submit" value="se connecter">
<p v-if="loginMessage">{{ loginMessage }}</p>
</form>
</div>
</template> </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> </style>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,5 +15,14 @@ export default defineConfig({
'@api': fileURLToPath(new URL('./src/api', import.meta.url)) '@api': fileURLToPath(new URL('./src/api', import.meta.url))
// '@icons': fileURLToPath(new URL('./node_modules/vue-material-design-icons', 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";`
},
},
},
}) })