Ask for confirmation for nodes outside of community bounds.
This commit is contained in:
parent
9387df8dd3
commit
4c6556de3f
6 changed files with 222 additions and 14 deletions
|
@ -19,6 +19,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"fork-awesome": "^1.2.0",
|
||||
"geolib": "^3.3.3",
|
||||
"leaflet": "^1.8.0",
|
||||
"pinia": "^2.0.21",
|
||||
"sparkson": "^1.3.6",
|
||||
|
|
36
frontend/src/components/FloatingIcon.vue
Normal file
36
frontend/src/components/FloatingIcon.vue
Normal file
|
@ -0,0 +1,36 @@
|
|||
<script setup lang="ts">
|
||||
import type { ComponentVariant } from "@/types";
|
||||
|
||||
interface Props {
|
||||
icon: string;
|
||||
variant: ComponentVariant;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span :class="['floating-icon', props.variant]">
|
||||
<i :class="['fa', `fa-${props.icon}`]" aria-hidden="true" />
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../scss/variables";
|
||||
|
||||
.floating-icon {
|
||||
display: block;
|
||||
float: left;
|
||||
margin: 0 0.5em 0.25em 0;
|
||||
|
||||
i {
|
||||
font-size: 3em;
|
||||
}
|
||||
}
|
||||
|
||||
@each $variant, $color in $variant-colors {
|
||||
.floating-icon.#{$variant} {
|
||||
color: $color;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -133,6 +133,14 @@ function centerOnCoordinates() {
|
|||
function renderMap() {
|
||||
const { layers, defaultLayers } = getLayers();
|
||||
createMap(defaultLayers, layers);
|
||||
if (
|
||||
map &&
|
||||
configStore.getConfig.otherCommunityInfo.showBorderForDebugging
|
||||
) {
|
||||
new L.Polygon(
|
||||
configStore.getConfig.otherCommunityInfo.localCommunityPolygon
|
||||
).addTo(map);
|
||||
}
|
||||
centerOnCoordinates();
|
||||
updateMarker();
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
ComponentVariant,
|
||||
hasOwnProperty,
|
||||
} from "@/types";
|
||||
import FloatingIcon from "@/components/FloatingIcon.vue";
|
||||
import ErrorCard from "@/components/ErrorCard.vue";
|
||||
import ButtonGroup from "@/components/form/ButtonGroup.vue";
|
||||
import ValidationForm from "@/components/form/ValidationForm.vue";
|
||||
|
@ -27,8 +28,12 @@ import { route, RouteName } from "@/router";
|
|||
import RouteButton from "@/components/form/RouteButton.vue";
|
||||
import { ApiError } from "@/utils/Api";
|
||||
import NodeCoordinatesInput from "@/components/nodes/NodeCoordinatesInput.vue";
|
||||
import OutsideOfCommunityConfirmationForm from "@/components/nodes/OutsideOfCommunityConfirmationForm.vue";
|
||||
import CheckboxInput from "@/components/form/CheckboxInput.vue";
|
||||
import InfoCard from "@/components/InfoCard.vue";
|
||||
import { isPointInPolygon } from "geolib";
|
||||
import { parseToFloat } from "@/utils/Numbers";
|
||||
import { forConstraint } from "../../shared/validation/validator";
|
||||
|
||||
const configStore = useConfigStore();
|
||||
const nodeStore = useNodeStore();
|
||||
|
@ -63,6 +68,9 @@ const nicknameModel = ref("" as Nickname);
|
|||
const emailModel = ref("" as EmailAddress);
|
||||
const monitoringModel = ref(false);
|
||||
|
||||
const showOutsideOfCommunityForm = ref(false);
|
||||
const confirmedOutsideOfCommunity = ref(false);
|
||||
|
||||
onMounted(() => {
|
||||
if (props.hostname) {
|
||||
hostnameModel.value = props.hostname;
|
||||
|
@ -75,6 +83,30 @@ onMounted(() => {
|
|||
}
|
||||
});
|
||||
|
||||
function isOutsideCommunity(): boolean {
|
||||
if (!forConstraint(CONSTRAINTS.node.coords, false)(coordsModel.value)) {
|
||||
return false;
|
||||
}
|
||||
const [lat, lng] = coordsModel.value.split(" ").map(parseToFloat);
|
||||
|
||||
return !isPointInPolygon(
|
||||
{ lat, lng },
|
||||
configStore.getConfig.otherCommunityInfo.localCommunityPolygon
|
||||
);
|
||||
}
|
||||
|
||||
async function onConfirmOutsideOfCommunity() {
|
||||
showOutsideOfCommunityForm.value = false;
|
||||
confirmedOutsideOfCommunity.value = true;
|
||||
|
||||
await onSubmit();
|
||||
}
|
||||
|
||||
async function onCancelOutsideOfCommunity() {
|
||||
showOutsideOfCommunityForm.value = false;
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
|
||||
async function onSubmit() {
|
||||
generalError.value = false;
|
||||
conflictErrorMessage.value = undefined;
|
||||
|
@ -82,6 +114,18 @@ async function onSubmit() {
|
|||
// Make sure to re-render error message to trigger scrolling into view.
|
||||
await nextTick();
|
||||
|
||||
if (
|
||||
configStore.getConfig.otherCommunityInfo.showInfo &&
|
||||
isOutsideCommunity() &&
|
||||
!confirmedOutsideOfCommunity.value
|
||||
) {
|
||||
showOutsideOfCommunityForm.value = true;
|
||||
} else {
|
||||
await createNode();
|
||||
}
|
||||
}
|
||||
|
||||
async function createNode(): Promise<void> {
|
||||
try {
|
||||
const node = await nodeStore.create({
|
||||
hostname: hostnameModel.value,
|
||||
|
@ -115,7 +159,17 @@ async function onSubmit() {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<ValidationForm novalidate="" ref="form" @submit="onSubmit">
|
||||
<OutsideOfCommunityConfirmationForm
|
||||
v-if="showOutsideOfCommunityForm"
|
||||
@confirm="onConfirmOutsideOfCommunity"
|
||||
@cancel="onCancelOutsideOfCommunity"
|
||||
/>
|
||||
<ValidationForm
|
||||
v-if="!showOutsideOfCommunityForm"
|
||||
novalidate=""
|
||||
ref="form"
|
||||
@submit="onSubmit"
|
||||
>
|
||||
<h2>Neuen Knoten anmelden</h2>
|
||||
|
||||
<div>
|
||||
|
@ -216,7 +270,10 @@ async function onSubmit() {
|
|||
<fieldset v-if="configStore.getConfig.monitoring.enabled">
|
||||
<h3>Möchtest Du automatisiert Status-E-Mails bekommen?</h3>
|
||||
|
||||
<i class="monitoring-icon fa fa-heartbeat" aria-hidden="true" />
|
||||
<FloatingIcon
|
||||
icon="heartbeat"
|
||||
:variant="ComponentVariant.PRIMARY"
|
||||
/>
|
||||
|
||||
<p class="help-block">
|
||||
Du kannst Dich automatisiert benachrichtigen lassen, sobald
|
||||
|
@ -266,8 +323,6 @@ async function onSubmit() {
|
|||
</InfoCard>
|
||||
</fieldset>
|
||||
|
||||
<h1>TODO: Check community bounds</h1>
|
||||
|
||||
<ButtonGroup
|
||||
:align="ComponentAlignment.RIGHT"
|
||||
:button-size="ButtonSize.SMALL"
|
||||
|
@ -294,13 +349,4 @@ async function onSubmit() {
|
|||
</ValidationForm>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../scss/variables";
|
||||
|
||||
.monitoring-icon {
|
||||
float: left;
|
||||
margin: 0 0.25em 0.125em 0;
|
||||
font-size: 3em;
|
||||
color: $variant-color-primary;
|
||||
}
|
||||
</style>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
<script setup lang="ts">
|
||||
import { useConfigStore } from "@/stores/config";
|
||||
import ButtonGroup from "@/components/form/ButtonGroup.vue";
|
||||
import { ButtonSize, ComponentAlignment, ComponentVariant } from "@/types";
|
||||
import ActionButton from "@/components/form/ActionButton.vue";
|
||||
import ValidationForm from "@/components/form/ValidationForm.vue";
|
||||
import FloatingIcon from "@/components/FloatingIcon.vue";
|
||||
import { onMounted } from "vue";
|
||||
|
||||
const configStore = useConfigStore();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "cancel"): void;
|
||||
(e: "confirm"): void;
|
||||
}>();
|
||||
|
||||
onMounted(() => {
|
||||
window.scrollTo(0, 0);
|
||||
});
|
||||
|
||||
function onCancel() {
|
||||
emit("cancel");
|
||||
}
|
||||
|
||||
function onSubmit() {
|
||||
emit("confirm");
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ValidationForm class="outside-of-community-form" @submit="onSubmit">
|
||||
<h2>
|
||||
Dein Router steht außerhalb des
|
||||
{{ configStore.getConfig.community.name }} Gebiets
|
||||
</h2>
|
||||
|
||||
<p>
|
||||
<strong
|
||||
>Erst mal großartig, dass du Freifunk machen möchtest!</strong
|
||||
>
|
||||
</p>
|
||||
|
||||
<FloatingIcon icon="community" :variant="ComponentVariant.SECONDARY" />
|
||||
|
||||
<p>
|
||||
Freifunk steht u.a. für Denzentralität. Deshalb wäre es klasse wenn
|
||||
Du Dich einer Community in Deiner Nähe anschließt oder vielleicht
|
||||
sogar
|
||||
<a
|
||||
href="https://freifunk.net/wie-mache-ich-mit/lokale-gruppe-gruenden/"
|
||||
target="_blank"
|
||||
>Deine eigene gründest</a
|
||||
>. Da aller Anfang schwer ist, macht es für eben diesen vielleicht
|
||||
Sinn sich erst mal einer
|
||||
<a
|
||||
href="https://freifunk.net/wie-mache-ich-mit/community-finden/"
|
||||
target="_blank"
|
||||
>nahegelegen Gruppe</a
|
||||
>
|
||||
anzuschließen. Auch wir von
|
||||
{{ configStore.getConfig.community.name }} stehen Dir natürlich
|
||||
gerne mit Rat und Tat zur Seite und mit den existierenden
|
||||
Hilfsmitteln ist die technische Hürde auch nicht mehr ganz so
|
||||
gigantisch.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Es gibt auch technische Gründe, die gegen eine Erweiterung über die
|
||||
Grenzen von
|
||||
{{ configStore.getConfig.community.name }} hinaus sprechen. Das
|
||||
Problem mit Mesh-Netzen heute ist, dass sie nicht besonders gut
|
||||
skalieren und mit jedem weiteren Router die Leistung nach unten
|
||||
geht.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Im Moment kommen ständig neue Freifunk-Communities dazu. Welche
|
||||
Communities es schon gibt, siehts Du
|
||||
<a
|
||||
href="https://freifunk.net/wie-mache-ich-mit/community-finden/"
|
||||
target="_blank"
|
||||
>in dieser Übersicht</a
|
||||
>.
|
||||
</p>
|
||||
|
||||
<ButtonGroup
|
||||
:align="ComponentAlignment.CENTER"
|
||||
:button-size="ButtonSize.MEDIUM"
|
||||
>
|
||||
<ActionButton
|
||||
type="reset"
|
||||
icon="times"
|
||||
:variant="ComponentVariant.SECONDARY"
|
||||
:size="ButtonSize.SMALL"
|
||||
@click="onCancel"
|
||||
>
|
||||
Abbrechen
|
||||
</ActionButton>
|
||||
|
||||
<ActionButton
|
||||
type="submit"
|
||||
icon="dot-circle-o"
|
||||
:variant="ComponentVariant.INFO"
|
||||
:size="ButtonSize.SMALL"
|
||||
>
|
||||
Knoten dennoch anmelden
|
||||
</ActionButton>
|
||||
</ButtonGroup>
|
||||
</ValidationForm>
|
||||
</template>
|
||||
|
||||
<style lang="scss"></style>
|
|
@ -1291,6 +1291,11 @@ functional-red-black-tree@^1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
|
||||
integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
|
||||
|
||||
geolib@^3.3.3:
|
||||
version "3.3.3"
|
||||
resolved "https://registry.yarnpkg.com/geolib/-/geolib-3.3.3.tgz#17f5a0dcdc0b051bd631b66f7131d2c14c54a15b"
|
||||
integrity sha512-YO704pzdB/8QQekQuDmFD5uv5RAwAf4rOUPdcMhdEOz+HoPWD0sC7Qqdwb+LAvwIjXVRawx0QgZlocKYh8PFOQ==
|
||||
|
||||
get-func-name@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41"
|
||||
|
|
Loading…
Reference in a new issue