Skip to content

Commit

Permalink
Merge pull request #266 from VoidShake/feature/advanced-location-edit
Browse files Browse the repository at this point in the history
Advanced Location Creating/Editing
  • Loading branch information
PssbleTrngle authored Jul 30, 2024
2 parents 27ca0cf + 8f34510 commit d8e45de
Show file tree
Hide file tree
Showing 23 changed files with 1,975 additions and 6,840 deletions.
4 changes: 3 additions & 1 deletion components/Menu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
</template>

<script lang="ts" setup>
import type { MenuButton, MenuOptions } from '~/composables/useMenu'
import type { MenuButton, MenuOptions } from '~/composables/useMenu';
const menu = useMenu()
Expand Down Expand Up @@ -38,6 +38,8 @@ async function click(button: MenuButton) {
top: 0;
left: 0;
min-width: 200px;
@apply bg-solid-700;
button {
Expand Down
14 changes: 9 additions & 5 deletions components/action/Button.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
<template>
<Teleport to="#action-buttons">
<ActionButtonStyle @click="$emit('click')">
<slot />
</ActionButtonStyle>
</Teleport>
<!-- This ClientOnly can be removed once nuxt has support for other teleport targets in SSR: -->
<!-- https://nuxt.com/docs/api/components/teleports -->
<ClientOnly>
<Teleport to="#action-buttons">
<ActionButtonStyle @click="$emit('click')">
<slot />
</ActionButtonStyle>
</Teleport>
</ClientOnly>
</template>

<script lang="ts" setup>
Expand Down
2 changes: 1 addition & 1 deletion components/dialog/CreatePlace.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<DialogBase title="Add Marker">
<FormCreatePlace :initial="{ pos }" @saved="closeDialog" />
<FormCreatePlace :initial="{ pos }" hide-map @saved="closeDialog" />
</DialogBase>
</template>

Expand Down
80 changes: 80 additions & 0 deletions components/form/CreateArea.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<template>
<section>
<FormKit v-slot="{ value, state: { valid } }" type="form" :actions="false" :errors="errors">
<FormKit name="world" validation="required" type="hidden" :value="initial?.pos?.world ?? 'overworld'" />

<FormKit name="name" :value="initial?.name" validation="required" label="Name" type="text" />

<div class="grid grid-flow-col gap-4">
<FormKit name="minY" label="Min-Y" type="number" step="1" :value="-64" />
<FormKit name="maxY" label="Max-Y" type="number" step="1" :value="320" />
</div>

<InputArea :initial="initial?.points" />

<div id="buttons">
<slot name="buttons" :valid="valid" :value="value">
<FormKit v-if="!onlyDraft" type="submit" :disabled="!valid" @click.prevent="save(value, false)" />
<FormKit type="submit" :disabled="!valid" :classes="{ input: 'bg-solid-600' }"

Check warning on line 18 in components/form/CreateArea.vue

View workflow job for this annotation

GitHub Actions / lint

Expected a linebreak before this attribute
@click.prevent="save(value, true)">

Check warning on line 19 in components/form/CreateArea.vue

View workflow job for this annotation

GitHub Actions / lint

Expected 1 line break before closing bracket, but no line breaks found
Save as Draft
</FormKit>
</slot>
</div>
</FormKit>
</section>
</template>

<script lang="ts" setup>
import {
CreateAreaDocument,
CreateAreaDraftDocument,
Permission,
type AbstractArea,
type CreateAreaDraftMutation,
type CreateAreaInput,
type CreateAreaMutation,
} from '~~/graphql/generated';
const { hasPermission } = useSession()
const { query } = useRoute()
const onlyDraft = computed(() => {
if ('draft' in query) return true
return !hasPermission(Permission.CreateLocation)
})
const emit = defineEmits<{
(e: 'saved', data: CreateAreaMutation | CreateAreaDraftMutation): void
}>()
defineProps<{
updateId?: number
initial?: Partial<AbstractArea>
}>()
const refetchQueries = ['getArea', 'getAreas']
const { mutate: createArea, error } = useMutation(CreateAreaDocument, { refetchQueries })
const { mutate: createAreaDraft, error: draftError } = useMutation(CreateAreaDraftDocument, { refetchQueries })
const errors = computed(() =>
[error, draftError]
.map(it => it.value)
.filter(notNull)
.flatMap(extractMessages),
)
async function save(input: CreateAreaInput, draft: boolean) {
const create = draft ? createAreaDraft : createArea
const response = await create({ input })
const data = response?.data
if (data) emit('saved', data)
}
</script>

<style lang="scss" scoped>
form {
#buttons {
@apply flex gap-2;
}
}
</style>
11 changes: 5 additions & 6 deletions components/form/CreatePlace.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,15 @@
<FormKit v-slot="{ value, state: { valid } }" type="form" :actions="false" :errors="errors">
<FormKit name="world" validation="required" type="hidden" :value="initial?.pos?.world ?? 'overworld'" />

<InputPos :initial="initial?.pos" />

<FormKit name="name" :value="initial?.name" validation="required" label="Name" type="text" />

<InputPos :initial="initial?.pos" :show-map="!hideMap" />

<div id="buttons">
<slot name="buttons" :valid="valid" :value="value">
<FormKit v-if="!onlyDraft" type="submit" :disabled="!valid" @click.prevent="save(value, false)" />
<FormKit
type="submit" :disabled="!valid" :classes="{ input: 'bg-solid-600' }"
@click.prevent="save(value, true)"
>
<FormKit type="submit" :disabled="!valid" :classes="{ input: 'bg-solid-600' }"

Check warning on line 13 in components/form/CreatePlace.vue

View workflow job for this annotation

GitHub Actions / lint

Expected a linebreak before this attribute
@click.prevent="save(value, true)">

Check warning on line 14 in components/form/CreatePlace.vue

View workflow job for this annotation

GitHub Actions / lint

Expected 1 line break before closing bracket, but no line breaks found
Save as Draft
</FormKit>
</slot>
Expand Down Expand Up @@ -49,6 +47,7 @@ const emit = defineEmits<{
defineProps<{
updateId?: number
initial?: DeepPartial<AbstractPlace>
hideMap?: boolean
}>()
const refetchQueries = ['getPlace', 'getPlaces']
Expand Down
70 changes: 70 additions & 0 deletions components/input/Area.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<template>
<FormKit v-model="points" name="points" placeholder="optional" label="Max-Y" type="hidden" />
<MapView id="map" :zoom="8" @click="addPoint">
<l-polygon :lat-lngs="latLngs" color="#41b782" :fill="true" :fill-opacity="0.5" fill-color="#41b782" />
<MapDraggableMarker v-for="point, i in points" :key="i" :pos="point" @dragend="updatePoint(i, $event)" />
</MapView>
</template>

<script lang="ts" setup>
import { LPolygon } from '@vue-leaflet/vue-leaflet';
import { minBy } from 'lodash-es';
import type { FlatPoint, PosFragment } from '~/graphql/generated';
const context = useMap()
const props = defineProps<{
initial?: FlatPoint[]
}>()
const points = ref<FlatPoint[]>(props.initial ?? [])
const latLngs = computed(() => points.value.map(it => toMapPos(context.value!.map, it)))
function distance(a: FlatPoint, b: FlatPoint) {
return Math.sqrt((a.x - b.x) ** 2 + (a.z - b.z) ** 2)
}
function findClosest(point: FlatPoint, between: FlatPoint[]) {
const min = minBy(between, it => distance(point, it))
return min && between.indexOf(min)
}
function addPoint(pos: PosFragment) {
const { x, z } = roundPos(pos)
const current = points.value
const closest = findClosest({ x, z }, current)
if (notNull(closest)) {
const neighbours = [current[(closest - 1 + current.length) % current.length], current[(closest + 1) % current.length]]
const closestNeighbour = findClosest({ x, z }, neighbours)!
const newPoints = [...current]
if (closestNeighbour === 0) {
newPoints.splice(closest, 0, { x, z })
} else {
newPoints.splice((closest + 1) % current.length, 0, { x, z })
}
points.value = newPoints
} else {
points.value = [...points.value, { x, z }]
}
}
function updatePoint(index: number, { x, z }: PosFragment) {
const newPoints = [...points.value]
newPoints[index] = roundPos({ x, z })
points.value = newPoints.map(it => {
const { x, z } = roundPos(it)
return { x, z }
})
}
</script>

<style lang="scss" scoped>
#map {
width: 100%;
aspect-ratio: 1 / 1;
max-height: 500px;
}
</style>
34 changes: 31 additions & 3 deletions components/input/Pos.vue
Original file line number Diff line number Diff line change
@@ -1,17 +1,45 @@
<template>
<div class="grid grid-flow-col gap-4">
<FormKit name="x" validation="required" label="X" type="number" step="1" :value="floored?.x" />
<FormKit name="x" validation="required" label="X" type="number" step="1" v-model="xRef" />

Check warning on line 3 in components/input/Pos.vue

View workflow job for this annotation

GitHub Actions / lint

Attribute "v-model" should go before "step"
<FormKit name="y" placeholder="optional" label="Y" type="number" step="1" :value="floored?.y" />
<FormKit name="z" validation="required" label="Z" type="number" step="1" :value="floored?.z" />
<FormKit name="z" validation="required" label="Z" type="number" step="1" v-model="zRef" />

Check warning on line 5 in components/input/Pos.vue

View workflow job for this annotation

GitHub Actions / lint

Attribute "v-model" should go before "step"
</div>
<MapView v-if="showMap" id="map" :zoom="8" :center="marker" @click="moveTo">
<MapDraggableMarker v-if="marker" :pos="marker" @dragend="moveTo" />
</MapView>
</template>

<script lang="ts" setup>
import type { Point } from '~~/graphql/generated';
import { type Point, type PosFragment } from '~~/graphql/generated';
const props = defineProps<{
initial?: Partial<Point>
showMap?: boolean
}>()
const floored = computed(() => props.initial && roundPos(props.initial))
const xRef = ref<number | undefined>(floored.value?.x)
const zRef = ref<number | undefined>(floored.value?.z)
const marker = computed(() => {
const x = xRef?.value
const z = zRef?.value
if (notNull(x) && notNull(z)) return { x, z } as PosFragment
return undefined
})
function moveTo(pos: PosFragment) {
const { x, z } = roundPos(pos)
xRef.value = x
zRef.value = z
}
</script>

<style lang="scss" scoped>
#map {
width: 100%;
aspect-ratio: 1 / 1;
max-height: 500px;
}
</style>
6 changes: 4 additions & 2 deletions components/location/Page.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<slot name="title" />
</h1>

<ActionLink :to="`${$route.path}/edit`">
<ActionLink v-if="hasPermission(Permission.CreateLocation)" :to="`${$route.path}/edit`">
<PencilIcon />
</ActionLink>

Expand All @@ -18,7 +18,9 @@

<script lang="ts" setup>
import { PencilIcon } from '@heroicons/vue/24/solid';
import type { AreaFragment, PlaceDraftFragment, PlaceFragment } from '~~/graphql/generated';
import { Permission, type AreaFragment, type PlaceDraftFragment, type PlaceFragment } from '~~/graphql/generated';
const { hasPermission } = useSession()
const props = defineProps<{
location: PlaceFragment | PlaceDraftFragment | AreaFragment
Expand Down
5 changes: 2 additions & 3 deletions components/map/AreaMarker.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
<template>
<l-polygon :lat-lngs="latLngs" color="#41b782" :fill="true" :fill-opacity="0.5" fill-color="#41b782" />
<l-polygon :lat-lngs="latLngs" color="#41b782" :fill="true" :fill-opacity="0.2" fill-color="#41b782" />
</template>

<script lang="ts" setup>
import { LPolygon } from '@vue-leaflet/vue-leaflet';
import { LatLng } from 'leaflet';
import type { MapAreaFragment } from '~/graphql/generated';
const context = useMap()
Expand All @@ -13,5 +12,5 @@ const props = defineProps<{
area: MapAreaFragment
}>()
const latLngs = computed(() => props.area.points.map(it => new LatLng(...toMapPos(context.value!.map, it))))
const latLngs = computed(() => props.area.points.map(it => toMapPos(context.value!.map, it)))
</script>
25 changes: 25 additions & 0 deletions components/map/DraggableMarker.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<template>
<l-marker :lat-lng="latLng" draggable @dragend="dragEnd" />
</template>

<script lang="ts" setup>
import { LMarker } from '@vue-leaflet/vue-leaflet';
import type { DragEndEvent } from 'leaflet';
import type { FlatPoint, PosFragment } from '~/graphql/generated';
const context = useMap()
const props = defineProps<{
pos: PosFragment | FlatPoint
}>()
const emit = defineEmits<{
(event: 'dragend', payload: PosFragment): void
}>()
function dragEnd(event: DragEndEvent) {
emit('dragend', toWorldPos(context.value!.map, event.target._latlng))
}
const latLng = computed(() => toMapPos(context.value!.map, props.pos))
</script>
6 changes: 5 additions & 1 deletion components/map/Interactive.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
<template>
<MapView @click="closeMenu" @contextmenu="mapMenu">
<DialogCreatePlace v-if="selected?.action == 'add-marker'" :pos="selected.pos" @close="selected = null" />
<template #dialogs>
<DialogCreatePlace v-if="selected?.action == 'add-marker'" :pos="selected.pos" @close="selected = null" />
</template>

<MapLocations />
</MapView>
</template>

Expand Down
10 changes: 8 additions & 2 deletions components/map/Leaflet.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,20 @@
<l-map :zoom="zoom ?? 0" :center="initialCenter" zoom-animation fade-animation :crs="crs"

Check warning on line 2 in components/map/Leaflet.vue

View workflow job for this annotation

GitHub Actions / lint

Expected a linebreak before this attribute
:min-zoom="context?.minZoom!" :max-zoom="context?.maxZoom!" :max-native-zoom="context?.maxNativeZoom"
:options="options" @ready="ready">

Check warning on line 4 in components/map/Leaflet.vue

View workflow job for this annotation

GitHub Actions / lint

Expected 1 line break before closing bracket, but no line breaks found
<slot />
<MapTiles />
<MapLocations />
</l-map>
</template>

<script lang="ts" setup>
import { LMap } from '@vue-leaflet/vue-leaflet';
import { CRS, Map, type LeafletMouseEvent } from 'leaflet';
import { CRS, type Bounds, type LeafletMouseEvent, type Map } from 'leaflet';
import type { PosFragment } from '~/graphql/generated';
const props = defineProps<{
center?: PosFragment
zoom?: number
bounds?: Bounds
disableControls?: boolean
}>()
Expand All @@ -23,9 +24,14 @@ const emit = defineEmits<{
(e: 'contextmenu', pos: PosFragment, event: LeafletMouseEvent): void
}>()
const leaflet = ref<Map>()
defineExpose({ leaflet })
function ready(map: Map) {
map.on('contextmenu', e => emitWithPos('contextmenu', e))
map.on('click', e => emitWithPos('click', e))
leaflet.value = map
}
function emitWithPos(e: 'click' | 'contextmenu', event: LeafletMouseEvent | PointerEvent) {
Expand Down
Loading

0 comments on commit d8e45de

Please sign in to comment.