frontend: add map view
Signed-off-by: Nicolas Froger <nicolas@kektus.xyz>
This commit is contained in:
parent
208fb1fbc7
commit
4e848fe952
13 changed files with 765 additions and 13 deletions
|
|
@ -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>
|
||||
|
|
|
|||
22
summer2024-frontend/src/components/ui/tooltip/Tooltip.vue
Normal file
22
summer2024-frontend/src/components/ui/tooltip/Tooltip.vue
Normal 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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
4
summer2024-frontend/src/components/ui/tooltip/index.js
Normal file
4
summer2024-frontend/src/components/ui/tooltip/index.js
Normal 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";
|
||||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
81
summer2024-frontend/src/views/MapView.vue
Normal file
81
summer2024-frontend/src/views/MapView.vue
Normal 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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue