Skip to content

Commit

Permalink
Merge pull request #426 from AgenceBio/feature/cut-border
Browse files Browse the repository at this point in the history
feat: Ajout de la division d'une parcelle et de la découpe des bordures
  • Loading branch information
GregoireDucharme authored Dec 17, 2024
2 parents 5d368bf + 82aee5c commit 535475c
Show file tree
Hide file tree
Showing 10 changed files with 9,150 additions and 164 deletions.
6,440 changes: 6,389 additions & 51 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,17 @@
"@algolia/autocomplete-theme-classic": "^1.8.3",
"@gouvfr/dsfr": "1.11.2",
"@sentry/vue": "^8.33.1",
"@turf/area": "^7.1.0",
"@turf/bbox": "^6.5.0",
"@turf/bbox-polygon": "^6.5.0",
"@turf/centroid": "^6.5.0",
"@turf/difference": "^6.5.0",
"@turf/helpers": "^6.5.0",
"@turf/intersect": "^6.5.0",
"@turf/line-split": "^7.1.0",
"@turf/nearest-point-on-line": "^7.1.0",
"@turf/point-to-line-distance": "^7.1.0",
"@turf/turf": "^7.1.0",
"@turf/union": "^6.5.0",
"@unhead/vue": "^1.0.6",
"@vueuse/core": "^9.6.0",
Expand All @@ -42,7 +47,9 @@
"geometric": "^2.5.4",
"maplibre-gl": "^4.3.2",
"pinia": "^2.0.26",
"polygon-splitter": "^0.0.11",
"proj4": "^2.11.0",
"remixicon": "^4.5.0",
"reproject": "^1.2.7",
"terra-draw": "^0.0.1-alpha.49",
"vue": "^3.3.11",
Expand Down
1 change: 1 addition & 0 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ useHead({ title, titleTemplate: "%s — CartoBio" });
@import "@gouvfr/dsfr/dsfr.css";
@import "@gouvfr/dsfr/utility/icons/icons.css";
@import "@/styles/variables.css";
@import "remixicon/fonts/remixicon.css";
a[aria-disabled] {
--text-action-high-blue-france: gray;
Expand Down
13 changes: 13 additions & 0 deletions src/cartobio-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,19 @@ export async function submitNewParcelle({ recordId }, feature) {
return data;
}

/**
* Add a new plot without id to a feature collection
*
* @returns {Promise<NormalizedRecord>}
*/
export async function divideNewParcelle(recordId, featureId, features) {
const { data } = await apiClient.post(`/v2/audits/${recordId}/parcelles/${featureId}/`, {
features,
});

return data;
}

/**
* @param {string} userToken
* @returns {Promise<CartoBioUser>}
Expand Down
201 changes: 201 additions & 0 deletions src/components/map/CustomControlsParcelles.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
<script setup>
import { useWindowWidth } from "@/composables/useWindowWidth";
import { ref } from "vue";
const emit = defineEmits(["editContour", "divide", "cutBorders", "mesure", "delete", "undo", "redo"]);
const props = defineProps({
mode: {
type: String,
required: true,
default: "modif",
},
canUndo: {
type: Boolean,
required: true,
},
canRedo: {
type: Boolean,
required: true,
},
canDelete: {
type: Boolean,
required: true,
},
mesureActive: {
type: Boolean,
required: true,
},
});
const currentMode = ref(props.mode);
const windowWidth = useWindowWidth();
const selectModif = () => {
emit("editContour");
currentMode.value = "modif";
};
const selectDivid = () => {
emit("divide");
currentMode.value = "divid";
};
const selectCutBorders = () => {
emit("cutBorders");
currentMode.value = "cutBorder";
};
const selectMesure = () => {
emit("mesure");
};
const supprimer = () => {
emit("delete");
};
const undo = () => {
emit("undo");
};
const redo = () => {
emit("redo");
};
</script>

<template>
<Teleport to=".maplibregl-ctrl-top-left">
<div class="command-modif-parcellaire maplibregl-ctrl">
<div class="left-buttons">
<button
class="fr-btn fr-btn--tertiary"
:class="currentMode === 'modif' ? 'selected-button' : ''"
type="button"
title="Modifier"
aria-label="Modifier"
@click="selectModif()"
>
<span class="ri-shape-line" aria-hidden="true" style="font-size: 1.2em"></span>
<span v-if="windowWidth > 780" class="button-text">Modifier</span>
</button>
<button
class="fr-btn fr-btn--tertiary"
:class="currentMode == 'divid' ? 'selected-button' : ''"
type="button"
title="Diviser"
aria-label="Diviser"
@click="selectDivid()"
>
<span class="ri-scissors-cut-line" aria-hidden="true" style="font-size: 1.2em"></span>
<span v-if="windowWidth > 780" class="button-text">Diviser</span>
</button>

<button
class="fr-btn fr-btn--tertiary"
type="button"
:class="currentMode === 'cutBorder' ? 'selected-button' : ''"
title="Découper les bordures"
aria-label="Découper les bordures"
@click="selectCutBorders()"
>
<span class="fr-icon-crop-line" aria-hidden="true"></span>
<span v-if="windowWidth > 780" class="button-text">{{
windowWidth > 1280 ? "Découper les bordures" : "Bordure"
}}</span>
</button>
<div class="separator"></div>
</div>

<div class="right-buttons">
<button
type="button"
title="Mesurer"
:class="mesureActive ? 'selected-button' : ''"
@click="selectMesure()"
aria-label="Mesurer"
>
<span class="ri-ruler-line" aria-hidden="true" style="font-size: 1.5em"></span>
</button>
<div class="separator"></div>
<button type="button" title="Supprimer" aria-label="Supprimer" :disabled="!canDelete" @click="supprimer()">
<span class="fr-icon-delete-line" aria-hidden="true" style="font-size: 1.2em"></span>
</button>
<button type="button" title="Annuler" aria-label="Annuler" :disabled="!canUndo" @click="undo()">
<span class="fr-icon-arrow-go-back-line" aria-hidden="true" style="font-size: 1.2em"></span>
</button>
<button type="button" title="Rétablir" aria-label="Rétablir" :disabled="!canRedo" @click="redo()">
<span class="fr-icon-arrow-go-forward-line" aria-hidden="true" style="font-size: 1.2em"></span>
</button>
</div>
</div>
</Teleport>
</template>

<style>
.command-modif-parcellaire {
display: inline-flex;
justify-content: space-between;
padding: 6px 4px;
box-shadow: 0 2px 4px 2px rgba(0, 0, 0, 0.1);
background-color: #fff;
}
.left-buttons {
display: flex;
align-items: center;
gap: 2px;
}
.right-buttons {
display: flex;
align-items: center;
gap: 2px;
margin-left: 8px;
}
.separator {
width: 1px;
height: 24px;
background-color: #ddd;
margin: 0 4px;
}
.left-buttons button:not(:disabled),
.right-buttons button:not(:disabled) {
color: #000091;
}
.left-buttons button.fr-btn,
.right-buttons button.fr-btn {
border: none;
box-shadow: none;
}
.right-buttons button {
padding: 8px;
}
button:hover {
background-color: #f5f5f5;
}
.button-text {
margin-left: 4px;
color: #000091;
font-size: 0.875em;
}
@media (min-width: 780px) {
.maplibregl-ctrl-top-left {
margin-left: 9% !important;
margin-top: 20px !important;
z-index: 1000 !important;
}
}
.selected-button {
background-color: #ececfe !important;
}
.maplibregl-ctrl-top-left .maplibregl-ctrl {
margin: 0px !important;
}
</style>
13 changes: 13 additions & 0 deletions src/components/map/MapContainer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,19 @@ describe("MapContainer", () => {
expect(attributionSpy).toHaveProperty("0.onAdd");
expect(attributionSpy).toHaveProperty("1", "bottom-right");
});

test("we display navigation control and scale", async () => {
const wrapper = mount(MapContainer, {
props: { showScale: true, bounds: [] },
});

expect(NavigationControl).toHaveBeenCalledOnce();
expect(ScaleControl).toHaveBeenCalledOnce();

const attributionSpy = wrapper.vm.map.addControl.mock.calls.at(1);
expect(attributionSpy).toHaveProperty("0.onAdd");
expect(attributionSpy).toHaveProperty("1", "bottom-right");
});
});

describe("LayerSelector", () => {
Expand Down
23 changes: 22 additions & 1 deletion src/components/map/MapContainer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ const props = defineProps({
type: Boolean,
default: false,
},
showScale: {
type: Boolean,
default: false,
},
bounds: {
type: Array,
required: true,
Expand Down Expand Up @@ -99,7 +103,9 @@ onMounted(() => {
},
"bottom-right",
);
}
if (props.showAttribution || props.showScale) {
map.value.addControl(new ScaleControl({ maxWidth: 80, unit: "metric" }), "bottom-right");
}
Expand Down Expand Up @@ -148,6 +154,12 @@ defineExpose({
.maplibregl-ctrl-bottom-left {
z-index: 10; /* has to be above maplibregl-ctrl-bottom-right to overlap it */
bottom: 12px;
left: 12px;
display: flex;
width: 67%;
justify-content: space-between;
align-items: flex-end;
}
.maplibregl-ctrl-bottom-right {
Expand All @@ -157,11 +169,13 @@ defineExpose({
"null null custom-controls"
"null null group-controls"
"attribution scale scale";
padding: 0;
bottom: 12px;
right: 12px;
.maplibregl-ctrl {
margin: 0;
justify-self: end;
}
.maplibregl-ctrl-attrib {
Expand All @@ -185,6 +199,13 @@ defineExpose({
}
}
.maplibregl-ctrl-top-left {
padding: 0;
background-color: hsla(0, 0%, 100%, 0.9);
font-size: 0.75rem;
margin-right: 1rem;
}
.legend > * {
pointer-events: initial;
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/records/Table/FeatureGroup.vue
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@
type="button"
class="fr-btn fr-btn--tertiary-no-outline fr-icon-geometry fr-text--sm"
>
Modifier le contour
Modifier la parcelle
</router-link>
</li>
<li v-else>
Expand All @@ -151,7 +151,7 @@
disabled
class="fr-btn fr-btn--tertiary-no-outline fr-icon-geometry fr-text--sm"
>
Modifier le contour
Modifier la parcelle
</button>
</li>
<li>
Expand Down
12 changes: 12 additions & 0 deletions src/composables/useWindowWidth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { computed, onMounted, onUnmounted, ref } from "vue";

export const useWindowWidth = () => {
const windowWidth = ref(window.innerWidth);
const width = computed(() => windowWidth.value);

const onWidthChange = () => (windowWidth.value = window.innerWidth);
onMounted(() => window.addEventListener("resize", onWidthChange));
onUnmounted(() => window.removeEventListener("resize", onWidthChange));

return width;
};
Loading

0 comments on commit 535475c

Please sign in to comment.