Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • geocontrib/geocontrib-frontend
  • ext_matthieu/geocontrib-frontend
  • fnecas/geocontrib-frontend
  • MatthieuE/geocontrib-frontend
4 results
Show changes
Commits on Source (63)
Showing
with 541 additions and 421 deletions
......@@ -25,16 +25,14 @@ build testing docker image:
only:
- develop
tags:
- build
image:
name: gcr.io/kaniko-project/executor:debug
entrypoint: [""]
- build_docker
variables:
DOCKER_TAG: testing
script:
- mkdir -p /kaniko/.docker
- export
- echo "{\"auths\":{\"https://index.docker.io/v1/\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination neogeo/geocontrib-front:testing
- echo Image docker neogeo/geocontrib-front:testing livrée
- cat $DOCKER_PASSWORD | docker login --username $DOCKER_LOGIN --password-stdin
- docker-compose build geocontrib-front
- docker-compose push geocontrib-front
- echo Image docker neogeo/geocontrib-front:${DOCKER_TAG} livrée
deploy testing docker image:
stage: deploy
......@@ -52,36 +50,30 @@ build stable docker image:
only:
- master
tags:
- build
image:
name: gcr.io/kaniko-project/executor:debug
entrypoint: [""]
- build_docker
variables:
DOCKER_TAG: latest
script:
- mkdir -p /kaniko/.docker
- echo "{\"auths\":{\"https://index.docker.io/v1/\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination neogeo/geocontrib-front:latest
- echo Image docker neogeo/geocontrib:latest livrée
- cat $DOCKER_PASSWORD | docker login --username $DOCKER_LOGIN --password-stdin
- docker-compose build geocontrib-front
- docker-compose push geocontrib-front
- echo Image docker neogeo/geocontrib-front:${DOCKER_TAG} livrée
build tagged docker image:
stage: build
only:
- tags
tags:
- build
image:
name: gcr.io/kaniko-project/executor:debug
entrypoint: [""]
- build_docker
variables:
DOCKER_TAG: $CI_COMMIT_TAG
script:
# Don't build tag id package.json as wrong version
- grep "\"version\":.\"$CI_COMMIT_TAG\"" package.json
- mkdir -p /kaniko/.docker
- echo "{\"auths\":{\"https://index.docker.io/v1/\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination neogeo/geocontrib-front:$CI_COMMIT_TAG
- echo Image docker neogeo/geocontrib-front:$CI_COMMIT_TAG livrée
- cat $DOCKER_PASSWORD | docker login --username $DOCKER_LOGIN --password-stdin
- docker-compose build geocontrib-front
- docker-compose push geocontrib-front
- echo Image docker neogeo/geocontrib-front:${DOCKER_TAG} livrée
sonarqube-check:
image:
......
......@@ -2,7 +2,7 @@
version: "3"
services:
geocontrib-front:
image: neogeo/geocontrib-front:geocontrib-latest
image: neogeo/geocontrib-front:${DOCKER_TAG:-testing}
build: .
environment:
- BASE_URL=${BASE_URL}
......
{
"name": "geocontrib-frontend",
"version": "3.0.2",
"version": "3.1.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
......@@ -2562,6 +2562,16 @@
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
"dev": true
},
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"optional": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"array-union": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
......@@ -2571,6 +2581,34 @@
"array-uniq": "^1.0.1"
}
},
"chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"optional": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"optional": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"optional": true
},
"dir-glob": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz",
......@@ -2642,6 +2680,13 @@
"slash": "^2.0.0"
}
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"optional": true
},
"ignore": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
......@@ -2679,6 +2724,28 @@
"requires": {
"minipass": "^3.1.1"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"optional": true,
"requires": {
"has-flag": "^4.0.0"
}
},
"vue-loader-v16": {
"version": "npm:vue-loader@16.8.3",
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.8.3.tgz",
"integrity": "sha512-7vKN45IxsKxe5GcVCbc2qFU5aWzyiLrYJyUuMz4BQLKctCj/fmCa0w6fGiiQ2cLFetNcek1ppGJQDCup0c1hpA==",
"dev": true,
"optional": true,
"requires": {
"chalk": "^4.1.0",
"hash-sum": "^2.0.0",
"loader-utils": "^2.0.0"
}
}
}
},
......@@ -3561,8 +3628,7 @@
"bluebird": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
"dev": true
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
},
"bn.js": {
"version": "5.2.0",
......@@ -5207,6 +5273,16 @@
}
}
},
"csvtojson": {
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/csvtojson/-/csvtojson-2.0.10.tgz",
"integrity": "sha512-lUWFxGKyhraKCW8Qghz6Z0f2l/PqB1W3AO0HKJzGIQ5JRSlR651ekJDiGJbBT4sRNNv5ddnSGVEnsxP9XRCVpQ==",
"requires": {
"bluebird": "^3.5.1",
"lodash": "^4.17.3",
"strip-bom": "^2.0.0"
}
},
"cyclist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz",
......@@ -8321,6 +8397,11 @@
"integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
"dev": true
},
"is-utf8": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
"integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q=="
},
"is-weakref": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
......@@ -12273,6 +12354,14 @@
}
}
},
"strip-bom": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
"integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==",
"requires": {
"is-utf8": "^0.2.0"
}
},
"strip-comments": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-1.0.2.tgz",
......@@ -13257,75 +13346,6 @@
}
}
},
"vue-loader-v16": {
"version": "npm:vue-loader@16.8.3",
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.8.3.tgz",
"integrity": "sha512-7vKN45IxsKxe5GcVCbc2qFU5aWzyiLrYJyUuMz4BQLKctCj/fmCa0w6fGiiQ2cLFetNcek1ppGJQDCup0c1hpA==",
"dev": true,
"optional": true,
"requires": {
"chalk": "^4.1.0",
"hash-sum": "^2.0.0",
"loader-utils": "^2.0.0"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"optional": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"optional": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"optional": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"optional": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"optional": true
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"optional": true,
"requires": {
"has-flag": "^4.0.0"
}
}
}
},
"vue-multiselect": {
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/vue-multiselect/-/vue-multiselect-2.1.6.tgz",
......
{
"name": "geocontrib-frontend",
"version": "3.1.0",
"version": "3.2.0",
"private": true,
"scripts": {
"serve": "npm run init-proxy & npm run init-serve",
......@@ -21,6 +21,7 @@
"@turf/helpers": "^6.5.0",
"axios": "^0.21.1",
"core-js": "^3.20.2",
"csvtojson": "^2.0.10",
"lodash": "^4.17.21",
"ol": "6.8.1",
"ol-mapbox-style": "^6.8.3",
......
......@@ -12,30 +12,4 @@ export function fileConvertSizeToMo(aSize){
aSize = Math.abs(parseInt(aSize, 10));
const def = [1024*1024, 'Mo', 1];
return (aSize/def[0]).toFixed(def[2]);
}
export function csvToJson(csv, delimiter) {
const result = [];
const allLines = csv.split('\n');
const headers = allLines[0].split(delimiter).map(el => {
return el.replace('\r', '');
});
const [, ...lines] = allLines;
for (const line of lines) {
if (line) {
const obj = {};
const currentLine = line.split(delimiter).map(el => {
return el.replace('\r', '');
});
for (let i = 0; i < headers.length; i++) {
obj[headers[i]] = currentLine[i];
}
result.push(obj);
}
}
return JSON.parse(JSON.stringify(result));
}
}
\ No newline at end of file
......@@ -47,11 +47,11 @@
<div class="description">
<p>{{ project.description }}</p>
</div>
<div class="meta">
<div class="meta top">
<span
class="right floated"
>
Projet {{ project.moderation ? "" : "non" }} modéré
<strong>Projet {{ project.moderation ? "" : "non" }} modéré</strong>
</span>
<span>
Niveau d'autorisation requis : {{ project.access_level_pub_feature }}
......@@ -228,6 +228,12 @@ export default {
}
}
.description {
p {
text-align: justify;
}
}
@media only screen and (min-width: 767px) {
.item-content-wrapper {
align-items: flex-start;
......@@ -235,6 +241,12 @@ export default {
.middle.aligned.content {
width: 100%;
padding: 0 0 0 1.5em;
.meta.top {
span {
line-height: 1.2em;
}
}
}
}
}
......@@ -244,8 +256,25 @@ export default {
align-items: center;
.middle.aligned.content {
width: 70%;
width: 80%;
padding: 1.5em 0 0;
.meta.top {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
.right.floated {
float: none !important;
margin-left: 0 !important;
margin-bottom: 0.5em;
}
span {
margin: 0.15em 0;
}
}
}
}
}
......
......@@ -207,10 +207,12 @@ export default {
'user',
'isOnline',
]),
...mapGetters([
'permissions',
]),
...mapState('feature', [
'currentFeature',
]),
DJANGO_BASE_URL() {
return this.$store.state.configuration.VUE_APP_DJANGO_BASE;
......@@ -231,14 +233,14 @@ export default {
if (this.validateForm()) {
featureAPI
.postComment({
featureId: this.$route.params.slug_signal,
featureId: this.currentFeature.feature_id,
comment: this.comment_form.comment.value,
})
.then((response) => {
if (response && this.comment_form.attachment_file.file) {
featureAPI
.postCommentAttachment({
featureId: this.$route.params.slug_signal,
featureId: this.currentFeature.feature_id,
file: this.comment_form.attachment_file.file,
fileName: this.comment_form.attachment_file.fileName,
title: this.comment_form.attachment_file.title,
......
......@@ -2,152 +2,163 @@
<div>
<h1 class="ui header">
<div class="content">
<span
v-if="fastEditionMode && form"
class="form ui half-block"
>
<input
id="feature_detail_title_input"
:value="form.title"
type="text"
required
maxlength="128"
name="title"
@blur="updateTitle"
>
</span>
<span v-else>
{{ currentFeature.title || currentFeature.feature_id }}
</span>
<div class="ui icon right floated compact buttons">
<router-link
v-if="displayToListButton"
id="feature-detail-to-features-list"
:to="{
name: 'liste-signalements',
params: { slug: $route.params.slug },
}"
custom
>
<div class="ui button tiny-margin teal">
<i class="ui icon arrow right" />
Retour à la liste des signalements
</div>
</router-link>
<span
v-if="featuresCount"
id="feature-count"
class="ui button tiny-margin basic"
>
{{ parseInt($route.query.offset) + 1 }} sur {{ featuresCount }}
</span>
<button
v-if="queryparams"
id="previous-feature"
:class="['ui button button-hover-green tiny-margin', { disabled: queryparams.previous < 0 }]"
data-tooltip="Voir le précédent signalement"
data-position="bottom center"
@click="toFeature('previous')"
<div class="two-block">
<div
v-if="fastEditionMode && form && canEditFeature"
class="form ui half-block"
>
<i
class="angle left fitted icon"
aria-hidden="true"
/>
</button>
<button
v-if="queryparams"
id="next-feature"
:class="[
'ui button button-hover-green tiny-margin',
{ disabled: queryparams.next >= featuresCount }
]"
data-tooltip="Voir le prochain signalement"
data-position="bottom center"
@click="toFeature('next')"
<input
id="feature_detail_title_input"
:value="form.title"
type="text"
required
maxlength="128"
name="title"
@blur="updateTitle"
>
</div>
<div
v-else
class="ellipsis"
>
<i
class="angle right fitted icon"
aria-hidden="true"
/>
</button>
{{ currentFeature.title || currentFeature.feature_id }}
</div>
<button
v-if="fastEditionMode && userCanFastEdit"
id="previous-feature"
:class="['ui button button-hover-orange tiny-margin', { disabled: false }]"
data-tooltip="Enregistrer les modifications"
data-position="bottom center"
@click="$store.dispatch('feature/SEND_FEATURE', $route.name)"
<div
id="feature-actions"
class="ui icon compact buttons"
>
<i
class="save fitted icon"
aria-hidden="true"
/>
</button>
<div>
<router-link
v-if="displayToListButton"
id="feature-detail-to-features-list"
:to="{
name: 'liste-signalements',
params: { slug: $route.params.slug },
}"
custom
>
<div class="ui button tiny-margin teal">
<i class="ui icon arrow right" />
Retour à la liste des signalements
</div>
</router-link>
</div>
<div>
<span
v-if="featuresCount"
id="feature-count"
class="ui button tiny-margin basic disabled no-opacity"
>
{{ parseInt($route.query.offset) + 1 }} sur {{ featuresCount }}
</span>
<button
v-if="queryparams"
id="previous-feature"
:class="['ui button button-hover-green tiny-margin', { disabled: queryparams.previous < 0 }]"
data-tooltip="Voir le précédent signalement"
data-position="bottom center"
@click="toFeature('previous')"
>
<i
class="angle left fitted icon"
aria-hidden="true"
/>
</button>
<button
v-if="queryparams"
id="next-feature"
:class="[
'ui button button-hover-green tiny-margin',
{ disabled: queryparams.next >= featuresCount }
]"
data-tooltip="Voir le prochain signalement"
data-position="bottom center"
@click="toFeature('next')"
>
<i
class="angle right fitted icon"
aria-hidden="true"
/>
</button>
</div>
<div>
<button
v-if="fastEditionMode && canEditFeature"
id="save-fast-edit"
:class="['ui button button-hover-orange tiny-margin', { disabled: false }]"
data-tooltip="Enregistrer les modifications"
data-position="bottom center"
@click="validateFastEdition"
>
<i
class="save fitted icon"
aria-hidden="true"
/>
</button>
<router-link
v-if="permissions && permissions.can_create_feature"
id="add-feature"
:to="{
name: 'ajouter-signalement',
params: {
slug_type_signal: $route.params.slug_type_signal || featureType.slug,
},
}"
class="ui button button-hover-green tiny-margin"
data-tooltip="Ajouter un signalement"
data-position="bottom center"
>
<i
class="plus icon"
aria-hidden="true"
/>
</router-link>
<router-link
v-if="permissions && permissions.can_create_feature"
id="add-feature"
:to="{
name: 'ajouter-signalement',
params: {
slug_type_signal: $route.params.slug_type_signal || featureType.slug,
},
}"
class="ui button button-hover-green tiny-margin"
data-tooltip="Ajouter un signalement"
data-position="bottom center"
>
<i
class="plus icon"
aria-hidden="true"
/>
</router-link>
<router-link
v-if="slugSignal &&
((permissions && permissions.can_update_feature) ||
isFeatureCreator ||
isModerator)
"
id="edit-feature"
:to="{
name: 'editer-signalement',
params: {
slug_signal: slugSignal,
slug_type_signal: $route.params.slug_type_signal || featureType.slug,
},
query: $route.query
}"
class="ui button button-hover-orange tiny-margin"
data-tooltip="Éditer le signalement"
data-position="bottom center"
>
<i
class="inverted grey pencil alternate icon"
aria-hidden="true"
/>
</router-link>
<router-link
v-if="slugSignal && canEditFeature"
id="edit-feature"
:to="{
name: 'editer-signalement',
params: {
slug_signal: slugSignal,
slug_type_signal: $route.params.slug_type_signal || featureType.slug,
},
query: $route.query
}"
class="ui button button-hover-orange tiny-margin"
data-tooltip="Éditer le signalement"
data-position="bottom center"
>
<i
class="inverted grey pencil alternate icon"
aria-hidden="true"
/>
</router-link>
<a
v-if="((permissions && permissions.can_update_feature) || isFeatureCreator) && isOnline"
id="currentFeature-delete"
class="ui button button-hover-red tiny-margin"
data-tooltip="Supprimer le signalement"
data-position="bottom right"
@click="$emit('setIsCancelling')"
>
<i
class="inverted grey trash alternate icon"
aria-hidden="true"
/>
</a>
<a
v-if="canDeleteFeature && isOnline"
id="currentFeature-delete"
class="ui button button-hover-red tiny-margin"
data-tooltip="Supprimer le signalement"
data-position="bottom right"
@click="$emit('setIsDeleting')"
>
<i
class="inverted grey trash alternate icon"
aria-hidden="true"
/>
</a>
</div>
</div>
</div>
<div class="ui hidden divider" />
<!-- <div class="ui hidden divider" /> -->
<div class="sub header prewrap">
<span
v-if="fastEditionMode && form"
v-if="fastEditionMode && canEditFeature && form"
class="form ui half-block"
>
<textarea
......@@ -196,12 +207,23 @@ export default {
type: Boolean,
default: false,
},
isFeatureCreator: {
type: Boolean,
default: false,
},
canEditFeature: {
type: Boolean,
default: false,
},
canDeleteFeature: {
type: Boolean,
default: false,
},
},
computed: {
...mapState([
'user',
'USER_LEVEL_PROJECTS',
'isOnline',
]),
...mapState('feature', [
......@@ -212,24 +234,6 @@ export default {
'permissions',
]),
isFeatureCreator() {
if (this.currentFeature && this.user) {
return this.currentFeature.creator === this.user.id;
}
return false;
},
isModerator() {
return this.USER_LEVEL_PROJECTS && this.USER_LEVEL_PROJECTS[this.$route.params.slug] === 'Modérateur';
},
userCanFastEdit() {
const superiorRoles = ['contributor', 'super_contributor', 'moderator', 'admin'];
return this.USER_LEVEL_PROJECTS &&
superiorRoles.includes(this.USER_LEVEL_PROJECTS[this.$route.params.slug]) ||
this.user.is_superuser;
},
queryparams() {
return this.$route.query.offset >= 0 ? {
previous: parseInt(this.$route.query.offset) - 1,
......@@ -258,26 +262,45 @@ export default {
updateDescription(e) {
this.$store.commit('feature/UPDATE_FORM_FIELD', { name: 'description', value: e.target.value });
},
validateFastEdition() {
this.$store.dispatch('feature/SEND_FEATURE', this.$route.name)
.then(() => this.$emit('updateEvents'));
}
}
};
</script>
<style>
#next-feature {
margin-right: .5rem !important;
}
#feature-detail-to-features-list {
line-height: 0;
margin-right: 5px;
}
.half-block {
display: inline-block;
width: 50%;
}
#feature_detail_title_input {
font-weight: bold;
font-size: 2em;
padding: .25em;
}
.two-block {
display: flex;
justify-content: space-between;
margin-bottom: .5em;
}
#feature-actions > div {
margin-left: .5rem;
}
#feature-actions .no-opacity {
opacity: 1 !important; /* overide disabled low opacity to customize button style */
}
@media screen and (max-width: 700px) {
.two-block {
flex-direction: column-reverse;
}
#feature-actions.ui.buttons {
flex-direction: column;
align-items: flex-end;
}
}
</style>
\ No newline at end of file
......@@ -23,7 +23,8 @@
<td>
<strong class="ui form">
<span
v-if="fastEditionMode && extra_forms.length > 0"
v-if="fastEditionMode && canEditFeature && extra_forms.length > 0"
:id="field.label"
>
<FeatureExtraForm
:field="getExtraForm(field)"
......@@ -60,7 +61,7 @@
aria-hidden="true"
/>
<FeatureEditStatusField
v-if="fastEditionMode && form"
v-if="fastEditionMode && canEditFeature && form"
:status="form.status.value"
class="inline"
/>
......@@ -115,6 +116,13 @@
{{ link.feature_to.created_on }})
</td>
</tr>
<tr v-if="linked_features.length === 0">
<td>
<em>
Aucune liaison associée au signalement.
</em>
</td>
</tr>
</tbody>
</table>
</div>
......@@ -154,7 +162,11 @@ export default {
fastEditionMode: {
type: Boolean,
default: false,
}
},
canEditFeature: {
type: Boolean,
default: false,
},
},
computed: {
......
<template>
<div
v-if="field && field.field_type === 'char'"
>
<div v-if="field && field.field_type === 'char'">
<label
v-if="$route.name === 'editer-signalement'"
v-if="displayLabels"
:for="field.name"
>
{{ field.label }}
......@@ -17,11 +15,9 @@
>
</div>
<div
v-else-if="field && field.field_type === 'list'"
>
<div v-else-if="field && field.field_type === 'list'">
<label
v-if="$route.name === 'editer-signalement'"
v-if="displayLabels"
:for="field.name"
>
{{ field.label }}
......@@ -32,11 +28,9 @@
:selection.sync="selected_extra_form_list"
/>
</div>
<div
v-else-if="field && field.field_type === 'integer'"
>
<div v-else-if="field && field.field_type === 'integer'">
<label
v-if="$route.name === 'editer-signalement'"
v-if="displayLabels"
:for="field.name"
>
{{ field.label }}
......@@ -52,9 +46,7 @@
>
</div>
</div>
<div
v-else-if="field && field.field_type === 'boolean'"
>
<div v-else-if="field && field.field_type === 'boolean'">
<div class="ui checkbox">
<input
:id="field.name"
......@@ -63,19 +55,14 @@
:name="field.name"
@change="updateStore_extra_form"
>
<label
v-if="$route.name === 'editer-signalement'"
:for="field.name"
>
{{ field.label }}
<label :for="field.name">
{{ displayLabels ? field.label : '' }}
</label>
</div>
</div>
<div
v-else-if="field && field.field_type === 'date'"
>
<div v-else-if="field && field.field_type === 'date'">
<label
v-if="$route.name === 'editer-signalement'"
v-if="displayLabels"
:for="field.name"
>
{{ field.label }}
......@@ -88,11 +75,9 @@
@blur="updateStore_extra_form"
>
</div>
<div
v-else-if="field && field.field_type === 'decimal'"
>
<div v-else-if="field && field.field_type === 'decimal'">
<label
v-if="$route.name === 'editer-signalement'"
v-if="displayLabels"
:for="field.name"
>
{{ field.label }}
......@@ -108,11 +93,9 @@
>
</div>
</div>
<div
v-else-if="field && field.field_type === 'text'"
>
<div v-else-if="field && field.field_type === 'text'">
<label
v-if="$route.name === 'editer-signalement'"
v-if="displayLabels"
:for="field.name"
>
{{ field.label }}
......@@ -155,6 +138,10 @@ export default {
this.$store.commit('feature/UPDATE_EXTRA_FORM', newExtraForm);
},
},
displayLabels() {
return this.$route.name === 'editer-signalement' || this.$route.name === 'ajouter-signalement';
}
},
methods: {
......
<template>
<div class="field">
<div
id="status"
class="field"
>
<Dropdown
v-if="selectedStatus"
:options="allowedStatusChoices"
......@@ -56,7 +59,7 @@ export default {
allowedStatusChoices() {
if (this.project && this.currentFeature && this.user) {
const isModerate = this.project.moderation;
const userStatus = this.USER_LEVEL_PROJECTS[this.project.slug];
const userStatus = this.USER_LEVEL_PROJECTS && this.USER_LEVEL_PROJECTS[this.project.slug];
const isOwnFeature = this.currentFeature.creator === this.user.id; //* si le contributeur est l'auteur du signalement
return allowedStatus2change(this.user, isModerate, userStatus, isOwnFeature, /* this.currentRouteName */);
}
......
......@@ -312,7 +312,7 @@ export default {
fillCustomFormData(customFormData) {
for (const el in customFormData) {
if (el && this.form[el] && customFormData[el]) {
if (el && this.form[el] && customFormData[el] !== undefined && customFormData[el] !== null) {
//* check if is an object, because data from api is a string, while import from django is an object
this.form[el].value = customFormData[el].value
? customFormData[el].value
......@@ -365,53 +365,46 @@ export default {
return occurences.length === 1;
},
checkFilledOptions() {
if (this.form.field_type.value === 'list') {
if (this.form.options.value.length < 1) {
return false;
} else if (
this.form.options.value.length === 1 &&
this.form.options.value[0] === ''
) {
return false;
}
}
return true;
checkListOptions() {
if (this.form.field_type.value !== 'list') return true;
return this.form.options.value.length >= 2 && !this.form.options.value.includes('');
},
checkCustomForm() {
this.form.label.errors = [];
this.form.name.errors = [];
this.form.options.errors = [];
let isValid = true;
if (!this.form.label.value) {
//* vérifier que le label est renseigné
this.form.label.errors = ['Veuillez compléter ce champ.'];
return false;
isValid = false;
} else if (!this.form.name.value) {
//* vérifier que le nom est renseigné
this.form.name.errors = ['Veuillez compléter ce champ.'];
return false;
isValid = false;
} else if (!this.hasRegularCharacters(this.form.name.value)) {
//* vérifier qu'il n'y a pas de caractères spéciaux
this.form.name.errors = [
'Veuillez utiliser seulement les caratères autorisés.',
];
return false;
isValid = false;
} else if (!this.checkUniqueName()) {
//* vérifier si les noms sont pas dupliqués
this.form.name.errors = [
'Les champs personnalisés ne peuvent pas avoir des noms similaires.',
];
return false;
} else if (!this.checkFilledOptions()) {
isValid = false;
} else if (!this.checkListOptions()) {
//* s'il s'agit d'un type liste, vérifier que le champ option est bien renseigné
this.form.options.errors = ['Veuillez compléter ce champ.'];
return false;
isValid = false;
} else if (this.hasDuplicateOptions()) {
//* pour le cas d'options dupliqués
return false;
isValid = false;
}
return true;
if (!isValid) document.getElementById(`custom_form-${this.form.position.value}`).scrollIntoView({ block: 'start', inline: 'nearest' });
return isValid;
},
},
};
......
......@@ -26,7 +26,9 @@
<td>
<h4 class="ui header align-right">
<div :data-tooltip="importFile.geojson_file_name">
{{ importFile.geojson_file_name | subString }}
<div class="ellipsis">
{{ importFile.geojson_file_name | subString }}
</div>
<div class="sub header">
ajouté le {{ importFile.created_on | setDate }}
</div>
......@@ -234,5 +236,9 @@ and also iPads specifically.
.margin-left {
margin-left: 94%;
}
h4.ui.header {
margin-left: 60px;
white-space: nowrap;
}
}
</style>
......@@ -426,10 +426,11 @@
</template>
<script>
import { csv } from 'csvtojson';
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex';
import { fileConvertSizeToMo, csvToJson } from '@/assets/js/utils';
import { fileConvertSizeToMo } from '@/assets/js/utils';
import FeatureTypeLink from '@/components/FeatureType/FeatureTypeLink';
export default {
......@@ -633,7 +634,6 @@ export default {
try {
fr.readAsText(this.csvFileToImport);
fr.onloadend = () => {
// Find csv delimiter
const commaDelimited = fr.result.split('\n')[0].includes(',');
const semicolonDelimited = fr.result.split('\n')[0].includes(';');
......@@ -644,19 +644,16 @@ export default {
this.featureTypeImporting = false;
return;
}
// Check if file contains 'lat' and 'long' fields
const headersLine =
fr.result
.split('\n')[0]
.split(delimiter)
.map(el => {
return el.replace('\r', '');
})
.filter(el => {
return el === 'lat' || el === 'lon';
});
const headers = fr.result
.split('\n')[0]
.split(delimiter)
.map(el => {
return el.replace('\r', '');
});
const headersCoord = headers.filter(el => {
return el === 'lat' || el === 'lon';
});
// Look for 2 decimal fields in first line of csv
// corresponding to lon and lat
const sampleLine =
......@@ -667,9 +664,13 @@ export default {
return !isNaN(el) && el.indexOf('.') !== -1;
})
.filter(Boolean);
if (sampleLine.length > 1 && headersLine.length === 2) {
if (sampleLine.length > 1 && headersCoord.length === 2) {
this.csvError = null;
this.csvImport = csvToJson(fr.result, delimiter);
csv()
.fromString(fr.result)
.then((jsonObj)=>{
this.csvImport = jsonObj;
});
this.featureTypeImporting = false;
//* stock filename to import features afterward
this.SET_FILE_TO_IMPORT(this.csvFileToImport);
......
......@@ -207,6 +207,7 @@
feature_type_slug: feature.feature_type.slug,
},
}"
class="ellipsis space-left"
>
{{ feature.feature_type.title }}
</router-link>
......@@ -220,6 +221,7 @@
},
query: { ...queryparams, offset: queryparams.offset + index }
}"
class="ellipsis space-left"
>
{{ feature.title || feature.feature_id }}
</router-link>
......@@ -465,7 +467,9 @@ export default {
Contributeur : ['draft', 'pending', 'published'],
};
if (this.userStatus === 'Contributeur' && feature.display_creator !== this.user.username) {
if (this.user.is_superuser) {
return true;
} else if (this.userStatus === 'Contributeur' && feature.display_creator !== `${this.user.first_name} ${this.user.last_name}`) {
return false;
} else if (permissions[this.userStatus]) {
return permissions[this.userStatus].includes(feature.status);
......@@ -725,6 +729,11 @@ and also iPads specifically.
text-align: center;
margin: .5em 0;
}
.space-left {
max-width: 100%;
display: inline-block;
padding-left: 3em;
}
}
@media only screen and (max-width: 410px) {
.ui.table tr td {
......
......@@ -23,7 +23,7 @@
<div class="description">
<p>{{ project.description }}</p>
</div>
<div class="meta">
<div class="meta top">
<span class="right floated">
<strong v-if="project.moderation">Projet modéré</strong>
<strong v-else>Projet non modéré</strong>
......@@ -110,3 +110,36 @@ export default {
};
</script>
<style lang="less" scoped>
.description {
p {
text-align: justify;
}
}
@media screen and (max-width: 767px) {
.content {
width: 90% !important;
.meta.top {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
.right.floated {
float: none !important;
margin-left: 0 !important;
margin-bottom: 0.5em;
}
span {
margin: 0.15em 0;
}
}
}
}
</style>
<template>
<div class="filters-container">
<div class="ui styled accordion">
<div id="filters-container">
<div
class="ui styled accordion"
@click="displayFilters = !displayFilters"
>
<div
id="filters"
class="title collapsible-filters"
>
FILTRES
<i
class="ui icon caret right down"
:class="['ui icon customcaret', { 'collapsed': !displayFilters }]"
aria-hidden="true"
/>
</div>
</div>
<div class="ui menu filters hidden">
<div :class="['ui menu filters', { 'hidden': displayFilters }]">
<div class="item">
<label>
Niveau d'autorisation requis
......@@ -40,7 +43,7 @@
v-on="$listeners"
/>
</div>
<div class="right item">
<div class="item">
<label>
Recherche par nom
</label>
......@@ -69,6 +72,7 @@ export default {
data() {
return {
displayFilters: false,
moderationOptions: [
{
label: 'Tous',
......@@ -157,26 +161,10 @@ export default {
}
},
mounted() {
const el = document.getElementsByClassName('collapsible-filters');
el[0].addEventListener('click', function() {
const icon = document.getElementsByClassName('caret');
icon[0].classList.toggle('right');
const content = document.getElementsByClassName('filters');
content[0].classList.toggle('hidden');
if (content[0].style.maxHeight){
content[0].style.maxHeight = null;
} else {
content[0].style.maxHeight = content[0].scrollHeight + 5 + 'px';
}
});
},
methods: {
...mapActions('projects', [
'SEARCH_PROJECTS'
])
]),
}
};
</script>
......@@ -189,7 +177,7 @@ export default {
transition: @arguments;
}
.filters-container {
#filters-container {
width: 100%;
display: flex;
flex-direction: column;
......@@ -200,6 +188,23 @@ export default {
.collapsible-filters {
font-size: 1.25em;
padding-right: 0;
.customcaret{
transition: transform .2s ease;
&.collapsed {
transform: rotate(180deg);
}
&::before{
position: relative;
right: 0;
top: 65%;
color: #999;
margin-top: 4px;
border-color: #999 transparent transparent;
border-style: solid;
border-width: 5px 5px 0;
content: "";
}
}
}
}
.filters {
......@@ -207,16 +212,16 @@ export default {
height:auto;
min-height: 0;
max-height:75px;
opacity: 1;
margin: 0 0 1em 0;
border: none;
box-shadow: none;
.transition-properties(max-height 0.2s ease-out;);
.transition-properties(all 0.2s ease-out;);
.item {
display: flex;
flex-direction: column;
align-items: flex-start !important;
padding: 0.4em 0.6em 0.4em 0;
padding: 0.5em;
label {
margin-bottom: 0.2em;
......@@ -230,20 +235,43 @@ export default {
.item::before {
width: 0;
}
.right.item {
padding-right: 0;
#search-projects {
width: 100%;
}
#search-projects {
width: 100%;
}
.right.item::before {
width: 0;
}
}
.filters.hidden {
max-height: 0;
overflow: hidden;
border: none;
opacity: 0;
max-height: 0;
}
}
@media screen and (min-width: 701px) {
.item {
&:first-child {
padding-left: 0;
}
&:last-child {
padding-right: 0;
}
}
}
@media screen and (max-width: 700px) {
#filters-container {
.filters {
display: flex;
flex-direction: column;
max-height: 275px;
.transition-properties(all 0.2s ease-out;);
.item {
width: 100%;
padding-right: 0;
padding-left: 0;
}
}
}
}
</style>
......@@ -44,7 +44,7 @@ const onConfigLoaded = function(config){
store.commit('SET_CONFIG', config);
setInterval(() => { //* check if navigator is online
store.commit('SET_IS_ONLINE', navigator.onLine);
}, 10000);
}, 2000);
// set title and favico
document.title = `${config.VUE_APP_APPLICATION_NAME} ${config.VUE_APP_APPLICATION_ABSTRACT}`;
......
......@@ -51,6 +51,7 @@ const mapService = {
lng,
mapDefaultViewCenter,
mapDefaultViewZoom,
maxZoom,
zoom,
zoomControl = true,
interactions = { doubleClickZoom: false, mouseWheelZoom: false, dragPan: true },
......@@ -60,7 +61,7 @@ const mapService = {
el.innerHTML = '';
}
this.map = new Map({
const mapOptions = {
layers: [],
target: el,
controls: [
......@@ -70,14 +71,17 @@ const mapService = {
})],
interactions: defaults(interactions),
view: new View({
center: transform([
!lng ? mapDefaultViewCenter[1] : lng,
!lat ? mapDefaultViewCenter[0] : lat,
center: transform([ //* since 0 is considered false, check for number instead of just defined (though boolean will pass through)
Number(lng) ? lng : mapDefaultViewCenter[1],
Number(lat) ? lat : mapDefaultViewCenter[0],
], 'EPSG:4326', 'EPSG:3857'),
zoom: !zoom ? mapDefaultViewZoom : zoom
zoom: Number(mapDefaultViewZoom) ? mapDefaultViewZoom : zoom,
maxZoom
}),
});
};
this.map = new Map(mapOptions);
if (zoomControl) {
this.map.addControl(new Zoom({ zoomInTipLabel: 'Zoomer', zoomOutTipLabel: 'Dézoomer' }));
......@@ -557,7 +561,12 @@ const mapService = {
</div>
${author}
`;
const featureId = feature.getProperties ? feature.getProperties().feature_id || feature.getId() : feature.id; //* feature.id was used with leaflet, with ol feature.getId replace it, but keeping it as fallback can prevent regression
const featureId =
feature.getId() ?
feature.getId() :
feature.getProperties ?
feature.getProperties().feature_id :
feature.id;
return { html, feature_type, featureId };
},
......
......@@ -215,7 +215,6 @@ const feature = {
params: {
slug_type_signal: rootState['feature-type'].current_feature_type_slug,
slug_signal: featureId,
message: routeName === 'ajouter-signalement' ? 'Le signalement a été crée' : 'Le signalement a été mis à jour'
},
});
});
......@@ -232,7 +231,6 @@ const feature = {
for (const field of state.extra_forms) {
extraFormObject[field.name] = field.value;
}
//const feature = state.form || state.currentFeature;
return {
id: state.form.feature_id || state.currentFeature.feature_id,
type: 'Feature',
......