frontend: add map view

Signed-off-by: Nicolas Froger <nicolas@kektus.xyz>
This commit is contained in:
Nicolas Froger 2024-07-25 19:25:08 +02:00
commit 4e848fe952
No known key found for this signature in database
13 changed files with 765 additions and 13 deletions

View file

@ -1,13 +1,17 @@
<script setup>
import { RouterLink, RouterView } from 'vue-router'
import { Images, Map } from 'lucide-vue-next'
</script>
<template>
<div class="fixed w-full top-0 z-50 flex items-center backdrop-blur-md bg-black/10">
<div class="fixed w-full top-0 z-50 flex items-center backdrop-blur-md bg-black/10 gap-2 px-3">
<a href="https://www.kektus.fr"
><h1 id="kektus" class="font-bold select-none mx-6 my-3 text-2xl hover:text-3xl transition-all duration-300">kektus</h1></a
><h1 id="kektus" class="font-bold select-none mx-3 my-3 text-2xl hover:text-3xl transition-all duration-300">kektus</h1></a
>
<h1 class="font-light text-lg">carnet de voyage été 2024</h1>
<h1 class="font-light text-lg my-2 mr-3">carnet de voyage été 2024</h1>
<div class="flex-grow"></div>
<RouterLink :to="{name: 'home'}"><div class="p-2 bg-black/10 hover:bg-black/20 transition-colors duration-300 rounded-lg"><Images size="20" /></div></RouterLink>
<RouterLink to="/map"><div class="p-2 bg-black/10 hover:bg-black/20 transition-colors duration-300 rounded-lg"><Map size="20" /></div></RouterLink>
</div>
<RouterView />
</template>

View file

@ -0,0 +1,22 @@
<script setup>
import { TooltipRoot, useForwardPropsEmits } from "radix-vue";
const props = defineProps({
defaultOpen: { type: Boolean, required: false },
open: { type: Boolean, required: false },
delayDuration: { type: Number, required: false },
disableHoverableContent: { type: Boolean, required: false },
disableClosingTrigger: { type: Boolean, required: false },
disabled: { type: Boolean, required: false },
ignoreNonKeyboardFocus: { type: Boolean, required: false },
});
const emits = defineEmits(["update:open"]);
const forwarded = useForwardPropsEmits(props, emits);
</script>
<template>
<TooltipRoot v-bind="forwarded">
<slot />
</TooltipRoot>
</template>

View file

@ -0,0 +1,53 @@
<script setup>
import { computed } from "vue";
import { TooltipContent, TooltipPortal, useForwardPropsEmits } from "radix-vue";
import { cn } from "@/lib/utils";
defineOptions({
inheritAttrs: false,
});
const props = defineProps({
forceMount: { type: Boolean, required: false },
ariaLabel: { type: String, required: false },
asChild: { type: Boolean, required: false },
as: { type: null, required: false },
side: { type: null, required: false },
sideOffset: { type: Number, required: false, default: 4 },
align: { type: null, required: false },
alignOffset: { type: Number, required: false },
avoidCollisions: { type: Boolean, required: false },
collisionBoundary: { type: null, required: false },
collisionPadding: { type: [Number, Object], required: false },
arrowPadding: { type: Number, required: false },
sticky: { type: String, required: false },
hideWhenDetached: { type: Boolean, required: false },
class: { type: null, required: false },
});
const emits = defineEmits(["escapeKeyDown", "pointerDownOutside"]);
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props;
return delegated;
});
const forwarded = useForwardPropsEmits(delegatedProps, emits);
</script>
<template>
<TooltipPortal>
<TooltipContent
v-bind="{ ...forwarded, ...$attrs }"
:class="
cn(
'z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
props.class,
)
"
>
<slot />
</TooltipContent>
</TooltipPortal>
</template>

View file

@ -0,0 +1,18 @@
<script setup>
import { TooltipProvider } from "radix-vue";
const props = defineProps({
delayDuration: { type: Number, required: false },
skipDelayDuration: { type: Number, required: false },
disableHoverableContent: { type: Boolean, required: false },
disableClosingTrigger: { type: Boolean, required: false },
disabled: { type: Boolean, required: false },
ignoreNonKeyboardFocus: { type: Boolean, required: false },
});
</script>
<template>
<TooltipProvider v-bind="props">
<slot />
</TooltipProvider>
</template>

View file

@ -0,0 +1,14 @@
<script setup>
import { TooltipTrigger } from "radix-vue";
const props = defineProps({
asChild: { type: Boolean, required: false },
as: { type: null, required: false },
});
</script>
<template>
<TooltipTrigger v-bind="props">
<slot />
</TooltipTrigger>
</template>

View file

@ -0,0 +1,4 @@
export { default as Tooltip } from "./Tooltip.vue";
export { default as TooltipContent } from "./TooltipContent.vue";
export { default as TooltipTrigger } from "./TooltipTrigger.vue";
export { default as TooltipProvider } from "./TooltipProvider.vue";

View file

@ -9,10 +9,14 @@ import VueFullPage from 'vue-fullpage.js'
import App from './App.vue'
import router from './router'
import "vue3-openlayers/styles.css";
import OpenLayersMap from "vue3-openlayers";
const app = createApp(App)
app.use(VueFullPage)
app.use(createPinia())
app.use(router)
app.use(OpenLayersMap);
app.mount('#app')

View file

@ -1,18 +1,20 @@
import { createRouter, createWebHistory } from 'vue-router'
import PostsView from '@/views/PostsView.vue'
import CreatePostView from '@/views/CreatePostView.vue'
import AdminView from '@/views/AdminView.vue'
import LoginView from '@/views/LoginView.vue'
import { useAuthStore } from '@/stores/auth.js'
const CreatePostView = () => import('@/views/CreatePostView.vue')
const AdminView = () => import('@/views/AdminView.vue')
const LoginView = () => import('@/views/LoginView.vue')
const MapView = () => import('@/views/MapView.vue')
function authenticatedRoute(to) {
const authStore = useAuthStore()
if (!authStore.isAuth) {
return { name: 'admin_login', query: { redirect: to.fullPath } };
return { name: 'admin_login', query: { redirect: to.fullPath } }
}
return true;
return true
}
const router = createRouter({
@ -23,6 +25,11 @@ const router = createRouter({
name: 'home',
component: PostsView
},
{
path: '/map',
name: 'map',
component: MapView
},
{
path: '/admin/login',
name: 'admin_login',

View file

@ -21,7 +21,7 @@ import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert/index
import { useAuthStore } from '@/stores/auth.js'
import { API_BASE_URL, S3_BUCKET, S3_ENDPOINT } from '@/config.js'
const authStore = useAuthStore();
const authStore = useAuthStore()
const formSchema = toTypedSchema(z.object({
description: z.string().min(1),
@ -62,7 +62,7 @@ const onSubmit = form.handleSubmit(async (values) => {
method: 'POST',
headers: {
'Content-Type': 'application/json',
"X-admin-token": authStore.adminToken
'X-admin-token': authStore.adminToken
},
body: JSON.stringify({ filename: file.file.name })
})
@ -105,7 +105,7 @@ const onSubmit = form.handleSubmit(async (values) => {
method: 'POST',
headers: {
'Content-Type': 'application/json',
"X-admin-token": authStore.adminToken
'X-admin-token': authStore.adminToken
},
body: JSON.stringify({
description: values.description,

View file

@ -0,0 +1,81 @@
<script setup>
import { usePostsStore } from '@/stores/posts.js'
import { onMounted } from 'vue'
import { fromLonLat } from 'ol/proj.js'
import { Camera } from 'lucide-vue-next'
import { Tooltip, TooltipProvider, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip/index.js'
const postStore = usePostsStore()
onMounted(() => {
postStore.fetchPosts()
})
</script>
<template>
<div class="fixed h-full w-full">
<ol-map
:loadTilesWhileAnimating="true"
:loadTilesWhileInteracting="true"
class="h-full w-full"
>
<ol-view
ref="view"
:zoom="3.6"
:center="[ 652293.6169027708, 6425265.202945196 ]"
/>
<ol-tile-layer>
<ol-source-osm />
</ol-tile-layer>
<ol-scaleline-control />
<ol-zoom-control zoomInLabel="+" zoomOutLabel="-" />
<ol-overlay
v-for="post in postStore.posts" :key="post.id"
:position="fromLonLat([post.location.lon, post.location.lat])"
:autoPan="true"
>
<TooltipProvider>
<Tooltip :default-open="true">
<TooltipTrigger as-child>
<RouterLink :to="{ name: 'home', hash: '#post-' + post.id}">
<div
class="-translate-x-1/2 -translate-y-1/2 p-1 rounded-lg bg-indigo-950 hover:bg-indigo-900 transition-colors duration-200">
<Camera size="16" />
</div>
</RouterLink>
</TooltipTrigger>
<TooltipContent>
<p>{{ post.formatedDate }}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</ol-overlay>
<ol-vector-layer>
<ol-source-vector>
<ol-feature v-for="post in postStore.posts" :key="post.id">
<ol-geom-point :coordinates="fromLonLat([post.location.lon, post.location.lat])"></ol-geom-point>
<ol-style>
<ol-style-circle :radius="5">
<ol-style-fill color="black"></ol-style-fill>
<ol-style-stroke
color="black"
:width="2"
></ol-style-stroke>
</ol-style-circle>
</ol-style>
</ol-feature>
</ol-source-vector>
</ol-vector-layer>
</ol-map>
</div>
</template>
<style scoped>
.ol-zoom {
top: 70px !important;
}
</style>