Merge remote-tracking branch 'origin/master' into features/docgen-widget-generate-template

This commit is contained in:
2021-12-06 15:46:19 +01:00
49 changed files with 1610 additions and 522 deletions

View File

@@ -1,51 +1,6 @@
/// AccompanyingCourse Work list Page
div.accompanying_course_work-list {
div.timeline {
width: 100%;
ul {
display: flex;
align-items: center;
justify-content: center;
padding: 0;
list-style-type: none;
> li {
flex-grow: 1; flex-shrink: 1; flex-basis: auto;
div {
display: flex;
flex-direction: column;
align-items: center;
&.date {
margin-bottom: 1em;
}
&.label {
border-top: 3px solid $chill-green;
&:before {
content: '';
display: inline-block;
position: relative;
width: 15px;
height: 15px;
top: -9px;
background-color: $white;
border-radius: 12px;
border: 2px solid $chill-green;
}
&.no-label:before {
display: none;
}
}
}
}
}
}
div.objective_results {
width: 100%;
display: grid;
@@ -69,8 +24,10 @@ div.accompanying_course_work-list {
//&:nth-child(even) { background-color: $chill-llight-gray; }
&.without-objectives {}
&.with-objectives {}
}
div.objective_results,
div.evaluations {
h4.title_label {
display: block;
margin: 0.4em 0;

View File

@@ -39,69 +39,15 @@ span.fa-holder {
}
/*
* BADGE_TITLE
* Display Title like a badge (with background-colored label)
* DASHBOARDS
*/
h2.badge-title {
display: flex;
flex-direction: row;
width: 100%;
color: $dark;
span.title_label {
border-radius: 0.35rem 0 0 0.35rem;
color: $white;
font-size: 80%;
padding: 0.5em;
padding-right: 0;
h3 {
margin-bottom: 0.5rem;
}
//position: relative;
span {
display: none;
//position: absolute;
//top: 0;
//left: 0;
//transform: rotate(270deg);
//transform-origin: 0 0;
}
}
span.title_action {
flex-grow: 1;
margin: 0 0 0 auto;
border-radius: 0 0.35rem 0.35rem 0;
background-color: $chill-llight-gray;
padding: 0.2em 1em;
ul.small_in_title {
margin: 0;
//margin-top: 0.5em;
font-size: 70%;
padding-left: 1rem;
&.evaluations {
@include list_marker_triangle($orange);
}
}
ul.columns { // XS:1 SM:2 MD:1 LG:2 XL:2 XXL:2
@include media-breakpoint-only(sm) {
columns: 2; -webkit-columns: 2; -moz-columns: 2;
}
@include media-breakpoint-up(lg) {
columns: 2; -webkit-columns: 2; -moz-columns: 2;
}
}
}
}
/// Theses links apply on badge as parent tag.
/// Theses links apply on dashboards as parent tag.
/// They don't look like button, picto or simple text links
a.badge-link {
a.dashboard-link {
color: unset;
text-decoration: unset;
& > h2.badge-title {
& > div.dashboard {
&:hover {
//box-shadow: 0 0 7px 0 $chill-gray;
//opacity: 0.8;
@@ -114,21 +60,80 @@ a.badge-link {
}
}
/// badge_title in AccompanyingCourse Work list Page
div.dashboard {
font-weight: 700;
font-size: 1.5rem;
margin-bottom: 0.5rem;
line-height: 1.2;
span.like-h3 {
color: #334d5c;
}
}
div.dashboard,
h2.badge-title {
display: flex;
flex-direction: row;
width: 100%;
color: $dark;
span.title_label {
color: $white;
font-size: 80%;
padding: 0.5em;
padding-right: 0;
border-radius: 0.35rem 0 0 0.35rem;
h3 {
margin-bottom: 0.5rem;
}
}
span.title_action {
flex-grow: 1;
margin: 0 0 0 auto;
background-color: $chill-llight-gray;
padding: 0.2em 1em;
border-radius: 0 0.35rem 0.35rem 0;
ul.small_in_title {
font-size: 70%;
}
}
}
ul.small_in_title {
margin: 0;
//margin-top: 0.5em;
padding-left: 1rem;
&.evaluations {
@include list_marker_triangle($orange);
}
}
ul.columns { // XS:1 SM:2 MD:1 LG:2 XL:2 XXL:2
@include media-breakpoint-only(sm) {
columns: 2; -webkit-columns: 2; -moz-columns: 2;
}
@include media-breakpoint-up(lg) {
columns: 2; -webkit-columns: 2; -moz-columns: 2;
}
}
/// dashboard_like_badge in AccompanyingCourse Work list Page
div.accompanying_course_work-list {
div.dashboard,
h2.badge-title {
span.title_label {
// Calculate same color then border:groove
background-color: shade-color($social-action-color, 34%);
}
span.title_action {
@include badge_title($social-action-color);
@include dashboard_like_badge($social-action-color);
}
}
}
/// badge_title in Activities on resume page
/// dashboard_like_badge in Activities on resume page
div.activity-list {
div.dashboard,
h2.badge-title {
span.title_label {
// Calculate same color then border:groove
@@ -138,7 +143,7 @@ div.activity-list {
}
}
span.title_action {
@include badge_title($activity-color);
@include dashboard_like_badge($activity-color);
}
span.title_label {
div.duration {

View File

@@ -18,12 +18,6 @@ div.accompanyingcourse-list {
//&:nth-child(2) { flex-direction: row; }
//&:last-child { flex-direction: column; }
}
div.title h3 {
font-weight: 700;
font-size: 100%;
font-family: 'Open Sans';
}
div.list {}
}
/// Search Page (list_with_period.html.twig)

View File

@@ -27,11 +27,10 @@
}
///
/// Generic mixin for titles like badge
// define visual badge used in title area
/// Mixin for dashboards (with design like badge_social)
///
@mixin badge_title($color) {
@mixin dashboard_like_badge($color) {
@include chill_badge($color);
&:before {
margin: 0 0.3em 0 -1.05em;

View File

@@ -53,6 +53,7 @@
<div class="row">
<div class="col-12 text-center">{{ $t('visgraph.between') }}<br>{{ $t('visgraph.and') }}</div>
<div class="col">
<small>{{ getPersonAge(modal.data.from) }}</small>
<h4>{{ getPerson(modal.data.from).text }}</h4>
<p class="text-start" v-if="relation && relation.title">
<span v-if="reverse">
@@ -64,6 +65,7 @@
</p>
</div>
<div class="col text-end">
<small>{{ getPersonAge(modal.data.to) }}</small>
<h4>{{ getPerson(modal.data.to).text }}</h4>
<p class="text-end" v-if="relation && relation.title">
<span v-if="reverse">
@@ -119,8 +121,9 @@ import vis from 'vis-network/dist/vis-network'
import { mapState, mapGetters } from "vuex"
import Modal from 'ChillMainAssets/vuejs/_components/Modal'
import VueMultiselect from 'vue-multiselect'
import { getRelationsList, postRelationship, patchRelationship, deleteRelationship } from "./api";
import { splitId } from "./vis-network";
import { getRelationsList, postRelationship, patchRelationship, deleteRelationship } from "./api"
import { splitId, getAge } from "./vis-network"
import { visMessages } from "./i18n";
export default {
name: "App",
@@ -128,6 +131,7 @@ export default {
Modal,
VueMultiselect
},
props: ['household_id'],
data() {
return {
container: '',
@@ -152,7 +156,9 @@ export default {
class: null,
text: null
},
}
},
canvas: null,
link: null,
}
},
computed: {
@@ -164,7 +170,7 @@ export default {
]),
visgraph_data() {
console.log('::: visgraph_data :::', this.nodes.length, 'nodes,', this.edges.length, 'edges')
//console.log('::: visgraph_data :::', this.nodes.length, 'nodes,', this.edges.length, 'edges')
return {
nodes: this.nodes,
edges: this.edges
@@ -172,12 +178,12 @@ export default {
},
refreshNetwork() {
console.log('--- refresh network')
//console.log('--- refresh network')
window.network.setData(this.visgraph_data)
},
legendLayers() {
console.log('--- refresh legend and rebuild checked Layers')
//console.log('--- refresh legend and rebuild checked Layers')
this.checkedLayers = []
let layersDisplayed = [
...this.nodes.filter(n => n.id.startsWith('household')),
@@ -193,7 +199,7 @@ export default {
},
checkedLayers() { // required to refresh data checkedLayers
console.log('--- checkedLayers')
//console.log('--- checkedLayers')
return this.checkedLayers
},
@@ -218,7 +224,7 @@ export default {
},
watch: {
updateHack(newValue, oldValue) {
console.log(`--- updateHack ${oldValue} <> ${newValue}`)
//console.log(`--- updateHack ${oldValue} <> ${newValue}`)
if (oldValue !== newValue) {
this.forceUpdateComponent()
}
@@ -229,6 +235,9 @@ export default {
this.initGraph()
this.listenOnGraph()
this.getRelationsList()
this.canvas = document.getElementById('visgraph').querySelector('canvas')
this.link = document.getElementById('exportCanvasBtn')
},
methods: {
@@ -255,27 +264,27 @@ export default {
case 'person':
let person = this.nodes.filter(n => n.id === node)[0]
console.log('@@@@@@ event on selected Node', person.id)
//console.log('@@@@@@ event on selected Node', person.id)
if (this.listenPersonFlag === 'normal') {
if (person.folded === true) {
console.log(' @@> expand mode event')
//console.log(' @@> expand mode event')
this.$store.commit('unfoldPerson', person)
}
} else {
console.log(' @@> create link mode event')
//console.log(' @@> create link mode event')
this.listenStepsToAddRelationship(person)
}
break
case 'household':
let household = this.nodes.filter(n => n.id === node)[0]
console.log('@@@@@@ event on selected Node', household.id)
//console.log('@@@@@@ event on selected Node', household.id)
this.$store.dispatch('unfoldPersonsByHousehold', household)
break
case 'accompanying_period':
let course = this.nodes.filter(n => n.id === node)[0]
console.log('@@@@@@ event on selected Node', course.id)
//console.log('@@@@@@ event on selected Node', course.id)
this.$store.dispatch('unfoldPersonsByCourse', course)
break
@@ -290,7 +299,7 @@ export default {
}
let link = data.edges[0]
let linkType = splitId(link, 'link')
console.log('@@@@@ event on selected Edge', data.edges.length, linkType, data)
//console.log('@@@@@ event on selected Edge', data.edges.length, linkType, data)
if (linkType.startsWith('relationship')) {
//console.log('linkType relationship')
@@ -314,7 +323,7 @@ export default {
})
},
listenStepsToAddRelationship(person) {
console.log(' @@> listenStep', this.listenPersonFlag)
//console.log(' @@> listenStep', this.listenPersonFlag)
if (this.listenPersonFlag === 'step2') {
//console.log(' @@> person 2', person)
this.newEdgeData.to = person.id
@@ -333,7 +342,7 @@ export default {
/// control Layers
toggleLayer(value) {
let id = value.target.value
console.log('@@@@@@ toggle Layer', id)
//console.log('@@@@@@ toggle Layer', id)
this.forceUpdateComponent()
if (this.checkedLayers.includes(id)) {
this.removeLayer(id)
@@ -382,7 +391,7 @@ export default {
title: null,
button: { class: null, text: null, }
}
console.log('==- reset Form', this.modal.data)
//console.log('==- reset Form', this.modal.data)
},
getRelationsList() {
//console.log('fetch relationsList')
@@ -400,12 +409,16 @@ export default {
let person = this.persons.filter(p => p.id === id)
return person[0]
},
getPersonAge(id) {
let person = this.getPerson(id)
return getAge(person)
},
// actions
createRelationship() {
this.displayHelpMessage = true
this.listenPersonFlag = 'step1' // toggle listener in create link mode
console.log(' @@> switch listener to create link mode:', this.listenPersonFlag)
//console.log(' @@> switch listener to create link mode:', this.listenPersonFlag)
},
dropRelationship() {
//console.log('delete', this.modal.data)
@@ -417,13 +430,13 @@ export default {
this.forceUpdateComponent()
},
submitRelationship() {
console.log('submitRelationship', this.modal.action)
//console.log('submitRelationship', this.modal.action)
switch (this.modal.action) {
case 'create':
return postRelationship(this.modal.data)
.then(relationship => new Promise(resolve => {
console.log('post relationship response', relationship)
//console.log('post relationship response', relationship)
this.$store.dispatch('addLinkFromRelationship', relationship)
this.modal.showModal = false
this.resetForm()
@@ -435,7 +448,7 @@ export default {
case 'edit':
return patchRelationship(this.modal.data)
.then(relationship => new Promise(resolve => {
console.log('patch relationship response', relationship)
//console.log('patch relationship response', relationship)
this.$store.commit('updateLink', relationship)
this.modal.showModal = false
this.resetForm()
@@ -450,39 +463,44 @@ export default {
},
// export image
exportCanvasAsImage() {
const canvas = document.getElementById('visgraph')
.querySelector('canvas')
console.log(canvas)
async exportCanvasAsImage() {
let link = document.getElementById('exportCanvasBtn')
link.download = "filiation.png"
let
filename = `filiation_${this.household_id}.jpg`,
mime = 'image/jpeg',
quality = 0.85,
footer = `© Chill ${new Date().getFullYear()}`,
timestamp = `${visMessages.fr.visgraph.relationship_household}${this.household_id}${new Date().toLocaleString()}`
canvas.toBlob(blob => {
console.log(blob)
link.href = URL.createObjectURL(blob)
}, 'image/png')
// resolve toBlob in a Promise
const getCanvasBlob = canvas => new Promise(resolve => {
canvas.toBlob(blob => resolve(blob), mime, quality)
})
/*
TODO improve feature
// 1. fonctionne, mais pas de contrôle sur le nom
if (canvas && canvas.getContext('2d')) {
let img = canvas.toDataURL('image/png;base64;')
img = img.replace('image/png','image/octet-stream')
window.open(img, '', 'width=1000, height=1000')
}
// 2. fonctionne, mais 2 click et pas compatible avec tous les browsers
let link = document.getElementById('exportCanvasBtn')
link.download = "image.png"
canvas.toBlob(blob => {
link.href = URL.createObjectURL(blob)
}, 'image/png')
*/
// build image from new temporary canvas
let tmpCanvas = document.createElement('canvas')
tmpCanvas.width = this.canvas.width
tmpCanvas.height = this.canvas.height
let ctx = tmpCanvas.getContext('2d')
ctx.beginPath()
ctx.fillStyle = '#fff'
ctx.fillRect(0, 0, tmpCanvas.width, tmpCanvas.height);
ctx.fillStyle = '#9d4600'
ctx.fillText(footer +' — '+ timestamp, 5, tmpCanvas.height - 10)
ctx.drawImage(this.canvas, 0, 0)
return await getCanvasBlob(tmpCanvas)
.then(blob => {
let url = document.createElement("a")
url.download = filename
url.href = window.URL.createObjectURL(blob)
url.click()
console.log('url', url.href)
URL.revokeObjectURL(url.href)
})
}
}
}
</script>

View File

@@ -24,6 +24,7 @@ const visMessages = {
refresh: "Rafraîchir",
screenshot: "Prendre une photo",
choose_relation: "Choisissez le lien de parenté",
relationship_household: "Filiation du ménage",
},
edit: 'Éditer',
del: 'Supprimer',

View File

@@ -16,7 +16,12 @@ persons.forEach(person => {
})
const app = createApp({
template: `<app></app>`
template: `<app :household_id="this.household_id"></app>`,
data() {
return {
household_id: JSON.parse(container.dataset.householdId)
}
}
})
.use(store)
.use(i18n)

View File

@@ -112,7 +112,7 @@ const store = createStore({
}
})
//console.log('array', array.map(item => item.person.id))
console.log('get persons group', group.map(f => f.id))
//console.log('get persons group', group.map(f => f.id))
return group
},
@@ -120,13 +120,17 @@ const store = createStore({
},
mutations: {
addPerson(state, [person, options]) {
let age = getAge(person)
age = (age === '')? '' : ' - ' + age
let debug = ''
/// Debug mode: uncomment to display person_id on visgraph
//debug = `\nid ${person.id}`
person.group = person.type
person._id = person.id
person.id = `person_${person.id}`
person.label = `*${person.text}*\n_${getGender(person.gender)} - ${getAge(person.birthdate)}_${debug}` //
person.label = `*${person.text}*\n_${getGender(person.gender)}${age}_${debug}`
person.folded = false
// folded is used for missing persons
if (options.folded) {
@@ -161,7 +165,7 @@ const store = createStore({
state.links.push(link)
},
updateLink(state, link) {
console.log('updateLink', link)
//console.log('updateLink', link)
let link_ = {
from: `person_${link.fromPerson.id}`,
to: `person_${link.toPerson.id}`,
@@ -264,7 +268,7 @@ const store = createStore({
fetchInfoForPerson({ dispatch }, person) {
// TODO enfants hors ménages
// example: household 61
// console.log(person.text, 'household', person.current_household_id)
//console.log(person.text, 'household', person.current_household_id)
if (null !== person.current_household_id) {
dispatch('fetchHouseholdForPerson', person)
}
@@ -305,15 +309,16 @@ const store = createStore({
*/
addLinkFromPersonsToHousehold({ commit, getters, dispatch }, household) {
let members = getters.getMembersByHousehold(household.id)
console.log('add link for', members.length, 'members')
//console.log('add link for', members.length, 'members')
members.forEach(m => {
commit('addLink', {
from: `${m.person.type}_${m.person.id}`,
to: `household_${m.person.current_household_id}`,
id: `household_${m.person.current_household_id}-person_${m.person.id}`,
to: `${household.id}`,
id: `${household.id}-person_${m.person.id}`,
arrows: 'from',
color: 'pink',
font: { color: '#D04A60' },
dashes: (getHouseholdWidth(m) === 1)? [0,4] : false, //edge style: [dash, gap, dash, gap]
label: getHouseholdLabel(m),
width: getHouseholdWidth(m),
})
@@ -362,7 +367,7 @@ const store = createStore({
*/
addLinkFromPersonsToCourse({ commit, getters, dispatch }, course) {
const participations = getters.getParticipationsByCourse(course.id)
console.log('add link for', participations.length, 'participations')
//console.log('add link for', participations.length, 'participations')
participations.forEach(p => {
//console.log(p.person.id)
commit('addLink', {
@@ -445,7 +450,7 @@ const store = createStore({
* @param array
*/
addMissingPerson({ commit, getters, dispatch }, [person, parent]) {
console.log('! add missing Person', person.id)
//console.log('! add missing Person', person.id)
commit('markPersonLoaded', person.id)
commit('addPerson', [person, { folded: true }])
if (getters.isExcludedNode(parent.id)) {
@@ -467,7 +472,7 @@ const store = createStore({
getters.getPersonsGroup(participations)
.forEach(person => {
if (person.folded === true) {
console.log('-=. unfold and expand person', person.id)
//console.log('-=. unfold and expand person', person.id)
commit('unfoldPerson', person)
dispatch('fetchInfoForPerson', person)
}
@@ -485,7 +490,7 @@ const store = createStore({
getters.getPersonsGroup(members)
.forEach(person => {
if (person.folded === true) {
console.log('-=. unfold and expand person', person.id)
//console.log('-=. unfold and expand person', person.id)
commit('unfoldPerson', person)
dispatch('fetchInfoForPerson', person)
}

View File

@@ -13,13 +13,12 @@ window.options = {
locale: 'fr',
locales: visMessages,
/*
*/
configure: {
enabled: true,
filter: 'nodes,edges',
//container: undefined,
filter: 'physics',
showButton: true
},
*/
physics: {
enabled: true,
barnesHut: {
@@ -37,8 +36,8 @@ window.options = {
centralGravity: 0.01,
springLength: 100,
springConstant: 0.08,
damping: 0.4,
avoidOverlap: 0
damping: 0.75,
avoidOverlap: 0.00
},
repulsion: {
centralGravity: 0.2,
@@ -159,17 +158,21 @@ const getGender = (gender) => {
}
/**
* TODO Repeat getAge() in PersonRenderBox.vue
* @param birthdate
* TODO only one abstract function (-> getAge() is repeated in PersonRenderBox.vue)
* @param person
* @returns {string|null}
*/
const getAge = (birthdate) => {
if (null === birthdate) {
return null
const getAge = (person) => {
if (person.birthdate) {
let birthdate = new Date(person.birthdate.datetime)
if (person.deathdate) {
let deathdate = new Date(person.deathdate.datetime)
return (deathdate.getFullYear() - birthdate.getFullYear()) + visMessages.fr.visgraph.years
}
let now = new Date()
return (now.getFullYear() - birthdate.getFullYear()) + visMessages.fr.visgraph.years
}
const birthday = new Date(birthdate.datetime)
const now = new Date()
return (now.getFullYear() - birthday.getFullYear()) + ' '+ visMessages.fr.visgraph.years
return ''
}
/**

View File

@@ -192,6 +192,7 @@ export default {
return `/fr/person/${this.person.id}/general`;
},
getAge: function() {
// TODO only one abstract function
if(this.person.birthdate && !this.person.deathdate){
const birthday = new Date(this.person.birthdate.datetime)
const now = new Date()