tethys.backend/resources/js/Pages/Map.vue
Arno Kaimbacher 4229001572
Some checks failed
build.yaml / Enhance Map Zoom Control and Improve Map Page Layout (push) Failing after 0s
Enhance Map Zoom Control and Improve Map Page Layout
- Refactored zoom control component for better accessibility and styling.
- Added hover effects and improved button states for zoom in/out buttons.
- Updated map page layout with enhanced dataset card design and responsive styles.
- Introduced empty state for no datasets found and improved results header.
- Added icons for dataset cards and improved author display.
2025-11-05 13:15:23 +01:00

521 lines
12 KiB
Vue

<script setup lang="ts">
import { Head } from '@inertiajs/vue3';
import { ref, Ref } from 'vue';
import { mdiChartTimelineVariant, mdiGithub, mdiMapMarker, mdiCalendar, mdiLockOpenVariant } from '@mdi/js';
import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
import SectionMain from '@/Components/SectionMain.vue';
import BaseButton from '@/Components/BaseButton.vue';
import SectionTitleLineWithButton from '@/Components/SectionTitleLineWithButton.vue';
import { MapOptions } from '@/Components/Map/MapOptions';
import SearchMap from '@/Components/Map/SearchMap.vue';
import { OpensearchDocument } from '@/Dataset';
const datasets: Ref<OpensearchDocument[]> = ref([]);
const openAccessLicences: Array<string> = ['CC-BY-4.0', 'CC-BY-SA-4.0'];
const mapOptions: MapOptions = {
center: [48.208174, 16.373819],
zoom: 3,
zoomControl: false,
attributionControl: false,
};
</script>
<template>
<LayoutAuthenticated :showAsideMenu="false">
<Head title="Map" />
<SectionMain>
<SectionTitleLineWithButton v-bind:icon="mdiChartTimelineVariant" title="Tethys Map" main>
<BaseButton
href="https://gitea.geosphere.at/geolba/tethys"
target="_blank"
:icon="mdiGithub"
label="Star on GeoSphere Forgejo"
color="contrast"
rounded-full
small
/>
</SectionTitleLineWithButton>
<!-- Map Component with enhanced styling -->
<div class="map-wrapper">
<SearchMap :datasets="datasets" :map-options="mapOptions"></SearchMap>
</div>
<!-- Results Header -->
<div v-if="datasets.length > 0" class="results-header">
<h2 class="results-title">
<span class="results-count">{{ datasets.length }}</span>
{{ datasets.length === 1 ? 'Dataset' : 'Datasets' }} Found
</h2>
<p class="results-subtitle">Click on any card to view details</p>
</div>
<!-- Enhanced Results Grid -->
<div class="results-grid">
<div
v-for="(dataset, index) in datasets"
:key="dataset.id"
class="dataset-card"
:style="{ animationDelay: `${index * 50}ms` }"
>
<!-- Card Header with Icon -->
<div class="card-header">
<div class="card-icon">
<svg class="icon" viewBox="0 0 24 24">
<path :d="mdiMapMarker" />
</svg>
</div>
<span class="card-type">{{ dataset.doctype }}</span>
</div>
<!-- Card Content -->
<div class="card-content">
<h3 class="card-title">
{{ dataset.title_output }}
</h3>
<p class="card-abstract">
{{ dataset.abstract_output }}
</p>
</div>
<!-- Authors Section -->
<div v-if="dataset.author && dataset.author.length > 0" class="card-authors">
<div class="author-label">Authors:</div>
<div class="author-list">
<span v-for="(author, idx) in dataset.author.slice(0, 3)" :key="idx" class="author-tag">
{{ author }}
</span>
<span v-if="dataset.author.length > 3" class="author-more"> +{{ dataset.author.length - 3 }} more </span>
</div>
</div>
<!-- Card Footer with Metadata -->
<div class="card-footer">
<div class="metadata-tags">
<span class="tag tag-year">
<svg class="tag-icon" viewBox="0 0 24 24">
<path :d="mdiCalendar" />
</svg>
{{ dataset.year }}
</span>
<span class="tag tag-language">
{{ dataset.language?.toUpperCase() }}
</span>
<span v-if="openAccessLicences.includes(dataset.licence)" class="tag tag-open-access">
<svg class="tag-icon" viewBox="0 0 24 24">
<path :d="mdiLockOpenVariant" />
</svg>
Open Access
</span>
</div>
</div>
<!-- Hover Effect Overlay -->
<div class="card-overlay"></div>
</div>
</div>
<!-- Empty State -->
<div v-if="datasets.length === 0" class="empty-state">
<div class="empty-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z" />
<circle cx="12" cy="10" r="3" />
</svg>
</div>
<h3 class="empty-title">No datasets selected</h3>
<p class="empty-description">Draw a rectangle on the map to search for datasets in that area</p>
</div>
</SectionMain>
</LayoutAuthenticated>
</template>
<style scoped>
/* Map Wrapper */
.map-wrapper {
margin-top: 1.5rem;
margin-bottom: 2rem;
position: relative;
border-radius: 1rem;
overflow: hidden;
box-shadow:
0 4px 6px -1px rgba(0, 0, 0, 0.1),
0 2px 4px -1px rgba(0, 0, 0, 0.06);
transition: box-shadow 0.3s ease;
}
.map-wrapper:hover {
box-shadow:
0 10px 15px -3px rgba(0, 0, 0, 0.1),
0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
/* Results Header */
.results-header {
margin-bottom: 1.5rem;
padding-bottom: 1rem;
border-bottom: 2px solid #e5e7eb;
}
.dark .results-header {
border-bottom-color: #374151;
}
.results-title {
font-size: 1.5rem;
font-weight: 700;
color: #1f2937;
margin-bottom: 0.25rem;
}
.dark .results-title {
color: #f9fafb;
}
.results-count {
display: inline-block;
padding: 0.25rem 0.75rem;
background: linear-gradient(135deg, #65dc21 0%, #357c06 100%);
color: white;
border-radius: 0.5rem;
font-size: 1.25rem;
margin-right: 0.5rem;
}
.results-subtitle {
color: #6b7280;
font-size: 0.875rem;
}
.dark .results-subtitle {
color: #9ca3af;
}
/* Results Grid */
.results-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1.5rem;
margin-top: 2rem;
}
/* Dataset Card */
.dataset-card {
background: white;
border-radius: 1rem;
overflow: hidden;
box-shadow:
0 1px 3px 0 rgba(0, 0, 0, 0.1),
0 1px 2px 0 rgba(0, 0, 0, 0.06);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
cursor: pointer;
animation: fadeInUp 0.5s ease-out forwards;
opacity: 0;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.dark .dataset-card {
background: #1f2937;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.3);
}
.dataset-card:hover {
transform: translateY(-4px);
box-shadow:
0 20px 25px -5px rgba(0, 0, 0, 0.1),
0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
.dark .dataset-card:hover {
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.5);
}
/* Card Header */
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 1.5rem;
background: linear-gradient(135deg, #65dc21 0%, #357c06 100%);
color: white;
}
.card-icon {
width: 2rem;
height: 2rem;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.2);
border-radius: 0.5rem;
}
.icon {
width: 1.25rem;
height: 1.25rem;
fill: currentColor;
}
.card-type {
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
background: rgba(255, 255, 255, 0.2);
padding: 0.25rem 0.75rem;
border-radius: 9999px;
}
/* Card Content */
.card-content {
padding: 1.5rem;
}
.card-title {
font-size: 1.125rem;
font-weight: 600;
color: #1f2937;
margin-bottom: 0.75rem;
line-height: 1.4;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.dark .card-title {
color: #f9fafb;
}
.card-abstract {
color: #6b7280;
font-size: 0.875rem;
line-height: 1.6;
display: -webkit-box;
-webkit-line-clamp: 3;
line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
.dark .card-abstract {
color: #9ca3af;
}
/* Authors Section */
.card-authors {
padding: 0 1.5rem 1rem;
border-top: 1px solid #f3f4f6;
}
.dark .card-authors {
border-top-color: #374151;
}
.author-label {
font-size: 0.75rem;
font-weight: 600;
color: #6b7280;
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 0.5rem;
}
.dark .author-label {
color: #9ca3af;
}
.author-list {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.author-tag {
font-size: 0.75rem;
color: #4b5563;
background: #f3f4f6;
padding: 0.25rem 0.625rem;
border-radius: 0.375rem;
white-space: nowrap;
}
.dark .author-tag {
color: #d1d5db;
background: #374151;
}
.author-more {
font-size: 0.75rem;
color: #6b7280;
font-style: italic;
}
.dark .author-more {
color: #9ca3af;
}
/* Card Footer */
.card-footer {
padding: 1rem 1.5rem;
background: #f9fafb;
border-top: 1px solid #f3f4f6;
}
.dark .card-footer {
background: #111827;
border-top-color: #374151;
}
.metadata-tags {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.tag {
display: inline-flex;
align-items: center;
gap: 0.25rem;
font-size: 0.75rem;
font-weight: 600;
padding: 0.375rem 0.75rem;
border-radius: 9999px;
transition: transform 0.2s ease;
}
.tag:hover {
transform: scale(1.05);
}
.tag-icon {
width: 0.875rem;
height: 0.875rem;
fill: currentColor;
}
.tag-year {
background: #dbeafe;
color: #1e40af;
}
.dark .tag-year {
background: #1e3a8a;
color: #93c5fd;
}
.tag-language {
background: #fce7f3;
color: #9f1239;
}
.dark .tag-language {
background: #831843;
color: #fbcfe8;
}
.tag-open-access {
background: #d1fae5;
color: #065f46;
}
.dark .tag-open-access {
background: #064e3b;
color: #6ee7b7;
}
/* Card Overlay */
.card-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(101, 220, 33, 0.1) 0%, rgba(53, 124, 6, 0.1) 100%);
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
}
.dataset-card:hover .card-overlay {
opacity: 1;
}
/* Empty State */
.empty-state {
text-align: center;
padding: 4rem 2rem;
background: white;
border-radius: 1rem;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
}
.dark .empty-state {
background: #1f2937;
}
.empty-icon {
width: 4rem;
height: 4rem;
margin: 0 auto 1.5rem;
color: #d1d5db;
}
.dark .empty-icon {
color: #4b5563;
}
.empty-icon svg {
width: 100%;
height: 100%;
}
.empty-title {
font-size: 1.25rem;
font-weight: 600;
color: #1f2937;
margin-bottom: 0.5rem;
}
.dark .empty-title {
color: #f9fafb;
}
.empty-description {
color: #6b7280;
font-size: 0.875rem;
}
.dark .empty-description {
color: #9ca3af;
}
/* Responsive Design */
@media (max-width: 640px) {
.results-grid {
grid-template-columns: 1fr;
}
.results-title {
font-size: 1.25rem;
}
.card-content {
padding: 1rem;
}
}
</style>