Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Follow up fixes on geodata column and map view #5248

Merged
merged 45 commits into from Mar 16, 2023
Merged
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
7cdac26
add two buttons to geodata column to open location in google maps and…
flisowna Feb 27, 2023
46c11b3
show open location on an external map only if the location is given
flisowna Feb 27, 2023
cf6151d
add popup in the map shared view, popup opens on click
flisowna Feb 27, 2023
84bb372
filter content in popup from null entries
flisowna Feb 27, 2023
58beed0
make urls clickable in popups and text selectable
flisowna Feb 27, 2023
0f372c4
delete id entry from popup
flisowna Feb 27, 2023
87f6363
WIP popupcontent adjustments
flisowna Mar 1, 2023
0b8c26f
geo map: work on popup
spaudanjo Mar 1, 2023
095b623
geo map: work on popup
spaudanjo Mar 1, 2023
f2abd5d
geo map: work on popup
spaudanjo Mar 1, 2023
3c19e8f
geo map: work on popup
spaudanjo Mar 1, 2023
855a5d0
geo map: work on popup
spaudanjo Mar 1, 2023
b9a6374
geo map: work on popup
spaudanjo Mar 2, 2023
5044cca
geo map: work on popup
spaudanjo Mar 2, 2023
39a8835
cleanup
spaudanjo Mar 3, 2023
4d4fe52
Merge branch 'develop' into map-marker-popups-for-shared-views
spaudanjo Mar 3, 2023
29e308b
fix follow-up issue of geodata layout
flisowna Mar 3, 2023
d4d6140
open geodata type column only in expanded form or grid view
flisowna Mar 3, 2023
b7697ee
Merge branch 'follow-up-geodata' into map-marker-popups-for-shared-views
spaudanjo Mar 9, 2023
f955c09
Merge branch 'develop' into map-marker-popups-for-shared-views
spaudanjo Mar 9, 2023
19b14d9
shared map view issue debugging - WIP
spaudanjo Mar 10, 2023
b7fc4ff
shared map view issue debugging - WIP
spaudanjo Mar 10, 2023
744b82b
Merge branch 'geodata-follow-up-fixes' into map-marker-popups-for-sha…
spaudanjo Mar 13, 2023
b166569
Merge branch 'develop' into geodata-follow-up-fixes
spaudanjo Mar 13, 2023
3e23a45
Revert "shared map view issue debugging - WIP"
spaudanjo Mar 13, 2023
9b95cd6
Revert "shared map view issue debugging - WIP"
spaudanjo Mar 13, 2023
b0cad5f
shared map view issue debugging - WIP
spaudanjo Mar 13, 2023
4de7f6f
Revert "shared map view issue debugging - WIP"
spaudanjo Mar 13, 2023
b732fcc
fix shared map view issue - WIP - fix type issue
spaudanjo Mar 13, 2023
fd5cb3f
fix shared map view issue - shared map view works now
spaudanjo Mar 13, 2023
bf3872e
fix shared map view issue - fetch up to 1000 map markers also on shar…
spaudanjo Mar 13, 2023
5b7267e
geo data: update db type mappings
spaudanjo Mar 13, 2023
3eb3727
geo data col: change wording and adapt layout of buttons
spaudanjo Mar 13, 2023
6baa3cc
geo data col: change wording and adapt layout of buttons
spaudanjo Mar 13, 2023
7276478
Merge branch 'develop' into geodata-follow-up-fixes
spaudanjo Mar 14, 2023
0be1d01
code cleanup
spaudanjo Mar 14, 2023
ce8cb69
code cleanup
spaudanjo Mar 14, 2023
a6b0a09
code cleanup
spaudanjo Mar 14, 2023
9ef951e
code cleanup
spaudanjo Mar 14, 2023
f281035
code cleanup
spaudanjo Mar 14, 2023
2c40e2a
code cleanup
spaudanjo Mar 14, 2023
65231b5
don't show 'Mapped by' dropdown on public map view
spaudanjo Mar 15, 2023
2409250
geo data support: udate docs
spaudanjo Mar 15, 2023
119395d
make cell container relative
spaudanjo Mar 15, 2023
96be86e
Merge branch 'develop' into geodata-follow-up-fixes
spaudanjo Mar 15, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/nc-gui/components.d.ts
Expand Up @@ -104,6 +104,7 @@ declare module '@vue/runtime-core' {
MaterialSymbolsFileCopyOutline: typeof import('~icons/material-symbols/file-copy-outline')['default']
MaterialSymbolsKeyboardReturn: typeof import('~icons/material-symbols/keyboard-return')['default']
MaterialSymbolsLightModeOutline: typeof import('~icons/material-symbols/light-mode-outline')['default']
MaterialSymbolsMobileFriendly: typeof import('~icons/material-symbols/mobile-friendly')['default']
MaterialSymbolsRocketLaunchOutline: typeof import('~icons/material-symbols/rocket-launch-outline')['default']
MaterialSymbolsSendOutline: typeof import('~icons/material-symbols/send-outline')['default']
MaterialSymbolsTranslate: typeof import('~icons/material-symbols/translate')['default']
Expand Down
40 changes: 31 additions & 9 deletions packages/nc-gui/components/cell/GeoData.vue
@@ -1,6 +1,8 @@
<script lang="ts" setup>
import type { GeoLocationType } from 'nocodb-sdk'
import { Modal as AModal, latLongToJoinedString, useVModel } from '#imports'
import GpsFixedIcon from '~icons/mdi/gps-fixed'
import OpenInNewIcon from '~icons/mdi/open-in-new'
wingkwong marked this conversation as resolved.
Show resolved Hide resolved

interface Props {
modelValue?: string | null
Expand Down Expand Up @@ -49,14 +51,14 @@ const clear = () => {

const onClickSetCurrentLocation = () => {
isLoading = true
const onSuccess = (position) => {
const onSuccess: PositionCallback = (position: GeolocationPosition) => {
const crd = position.coords
formState.latitude = crd.latitude
formState.longitude = crd.longitude
formState.latitude = `${crd.latitude}`
formState.longitude = `${crd.longitude}`
isLoading = false
}

const onError = (err) => {
const onError: PositionErrorCallback = (err) => {
console.error(`ERROR(${err.code}): ${err.message}`)
isLoading = false
}
Expand All @@ -68,13 +70,25 @@ const onClickSetCurrentLocation = () => {
}
navigator.geolocation.getCurrentPosition(onSuccess, onError, options)
}

const openInGoogleMaps = () => {
const [latitude, longitude] = (vModel.value || '').split(';')
const url = `https://www.google.com/maps/search/?api=1&query=${latitude},${longitude}`
window.open(url, '_blank')
}

const openInOSM = () => {
const [latitude, longitude] = (vModel.value || '').split(';')
const url = `https://www.openstreetmap.org/?mlat=${latitude}&mlon=${longitude}#map=15/${latitude}/${longitude}`
window.open(url, '_blank')
}
</script>

<template>
<a-dropdown :is="isExpanded ? AModal : 'div'" v-model:visible="isExpanded" trigger="click">
<div
v-if="!isLocationSet"
class="group cursor-pointer flex gap-1 items-center mx-auto max-w-32 justify-center active:(ring ring-accent ring-opacity-100) rounded border-1 p-1 shadow-sm hover:(bg-primary bg-opacity-10) dark:(!bg-slate-500)"
class="group cursor-pointer flex gap-1 items-center mx-auto max-w-64 justify-center active:(ring ring-accent ring-opacity-100) rounded border-1 p-1 shadow-sm hover:(bg-primary bg-opacity-10) dark:(!bg-slate-500)"
>
<div class="flex items-center gap-2" data-testid="nc-geo-data-set-location-button">
<MdiMapMarker class="transform dark:(!text-white) group-hover:(!text-accent scale-120) text-gray-500 text-[0.75rem]" />
Expand All @@ -85,7 +99,7 @@ const onClickSetCurrentLocation = () => {
</div>
<div v-else data-testid="nc-geo-data-lat-long-set">{{ latLongStr }}</div>
<template #overlay>
<a-form :model="formState" class="flex flex-col" @finish="handleFinish">
<a-form :model="formState" class="flex flex-col w-max-64" @finish="handleFinish">
<a-form-item>
<div class="flex mt-4 items-center mx-2">
<div class="mr-2">{{ $t('labels.lat') }}:</div>
Expand Down Expand Up @@ -122,13 +136,21 @@ const onClickSetCurrentLocation = () => {
</div>
</a-form-item>
<a-form-item>
<div class="flex items-center mr-2">
<div class="mr-2 flex flex-col items-end gap-1 text-left">
<MdiReload v-if="isLoading" :class="{ 'animate-infinite animate-spin text-gray-500': isLoading }" />
<a-button class="ml-2" @click="onClickSetCurrentLocation">{{ $t('labels.yourLocation') }}</a-button>
<a-button class="ml-2" @click="onClickSetCurrentLocation"
><GpsFixedIcon class="mr-2" />{{ $t('labels.currentLocation') }}</a-button
wingkwong marked this conversation as resolved.
Show resolved Hide resolved
>
</div>
</a-form-item>
<a-form-item v-if="vModel">
<div class="mr-2 flex flex-row items-end gap-1 text-left">
<a-button @click="openInOSM"><OpenInNewIcon class="mr-2" />{{ $t('activity.map.openInOpenStreetMap') }}</a-button>
<a-button @click="openInGoogleMaps"><OpenInNewIcon class="mr-2" />{{ $t('activity.map.openInGoogleMaps') }}</a-button>
wingkwong marked this conversation as resolved.
Show resolved Hide resolved
</div>
</a-form-item>
<a-form-item>
<div class="ml-auto mr-2">
<div class="ml-auto mr-2 w-auto">
<a-button type="text" @click="clear">{{ $t('general.cancel') }}</a-button>
<a-button type="primary" html-type="submit" data-testid="nc-geo-data-save">{{ $t('general.submit') }}</a-button>
</div>
Expand Down
27 changes: 23 additions & 4 deletions packages/nc-gui/components/smartsheet/Map.vue
Expand Up @@ -4,10 +4,14 @@ import L, { LatLng } from 'leaflet'
import 'leaflet.markercluster'
import { ViewTypes } from 'nocodb-sdk'
import { IsPublicInj, OpenNewRecordFormHookInj, latLongToJoinedString, onMounted, provide, ref } from '#imports'
import type { Row as RowType } from '~/lib'
import type { Row, Row as RowType } from '~/lib'
wingkwong marked this conversation as resolved.
Show resolved Hide resolved

const route = useRoute()

const popupIsOpen = ref(false)
const popUpRow = ref<Row>()
const fields = inject(FieldsInj, ref([]))

const router = useRouter()

const reloadViewDataHook = inject(ReloadViewDataHookInj)
Expand Down Expand Up @@ -90,7 +94,12 @@ const addMarker = (lat: number, long: number, row: RowType) => {
const newMarker = L.marker([lat, long], {
alt: `${lat}, ${long}`,
}).on('click', () => {
expandForm(row)
if (newMarker && isPublic.value) {
popUpRow.value = row
popupIsOpen.value = true
} else {
expandForm(row)
}
})
markersClusterGroupRef.value?.addLayer(newMarker)
}
Expand Down Expand Up @@ -188,9 +197,7 @@ watch([formattedData, mapMetaData, markersClusterGroupRef], () => {
if (primaryGeoDataValue == null) {
return
}

const [lat, long] = primaryGeoDataValue.split(';').map(parseFloat)

addMarker(lat, long, row)
})
})
Expand All @@ -206,6 +213,10 @@ const count = computed(() => paginationData.value.totalRows)
</script>

<template>
<a-modal v-model:visible="popupIsOpen" :footer="null" centered :closable="false" @close="popupIsOpen = false">
<LazySmartsheetSharedMapMarkerPopup v-if="popUpRow" :fields="fields" :row="popUpRow"></LazySmartsheetSharedMapMarkerPopup>
</a-modal>

<div class="flex flex-col h-full w-full no-underline" data-testid="nc-map-wrapper">
<div id="mapContainer" ref="mapContainerRef" class="w-full h-screen">
<a-tooltip placement="bottom" class="h-2 w-auto max-w-fit-content absolute top-3 right-3 p-2 z-500 cursor-default">
Expand Down Expand Up @@ -259,8 +270,16 @@ const count = computed(() => paginationData.value.totalRows)
.no-underline a {
text-decoration: none !important;
}

.leaflet-popup-content-wrapper {
max-height: 255px;
overflow: scroll;
}

.popup-content {
user-select: text;
display: flex;
gap: 10px;
flex-direction: column;
}
</style>
182 changes: 182 additions & 0 deletions packages/nc-gui/components/smartsheet/SharedMapMarkerPopup.vue
@@ -0,0 +1,182 @@
<script lang="ts" setup>
wingkwong marked this conversation as resolved.
Show resolved Hide resolved
import type { ColumnType, TableType } from 'nocodb-sdk'
import type { Ref } from 'vue'

import { ViewTypes, isVirtualCol } from 'nocodb-sdk'
import {
ActiveViewInj,
ChangePageInj,
FieldsInj,
IsFormInj,
IsGalleryInj,
IsGridInj,
MetaInj,
PaginationDataInj,
ReloadRowDataHookInj,
ReloadViewDataHookInj,
ReloadViewMetaHookInj,
computed,
inject,
isImage,
isLTAR,
nextTick,
onMounted,
provide,
ref,
useAttachment,
useViewData,
} from '#imports'
import type { Row } from '~/lib'

interface Attachment {
url: string
}

const props = defineProps<{
fields: ColumnType[]
row: Row
}>()

const meta = inject(MetaInj, ref())
const view = inject(ActiveViewInj, ref())
const reloadViewMetaHook = inject(ReloadViewMetaHookInj)
const reloadViewDataHook = inject(ReloadViewDataHookInj)

const { loadData, paginationData, loadGalleryData, galleryData, changePage } = useViewData(meta, view)

provide(IsFormInj, ref(false))
provide(IsGalleryInj, ref(true))
provide(IsGridInj, ref(false))
provide(PaginationDataInj, paginationData)
provide(ChangePageInj, changePage)

const fields = inject(FieldsInj, ref([]))

const { getPossibleAttachmentSrc } = useAttachment()

const fieldsWithoutCover = computed(() => fields.value.filter((f) => f.id !== galleryData.value?.fk_cover_image_col_id))

const coverImageColumn: any = $(
computed(() =>
meta.value?.columnsById
? meta.value.columnsById[galleryData.value?.fk_cover_image_col_id as keyof typeof meta.value.columnsById]
: {},
),
)

const isRowEmpty = (record: any, col: any) => {
const val = record.row[col.title]
if (!val) return true

return Array.isArray(val) && val.length === 0
}

const attachments = (record: any): Attachment[] => {
try {
if (coverImageColumn?.title && record.row[coverImageColumn.title]) {
return typeof record.row[coverImageColumn.title] === 'string'
? JSON.parse(record.row[coverImageColumn.title])
: record.row[coverImageColumn.title]
}
return []
} catch (e) {
return []
}
}

const reloadAttachments = ref(false)

reloadViewMetaHook?.on(async () => {
await loadGalleryData()

reloadAttachments.value = true

nextTick(() => {
reloadAttachments.value = false
})
})
reloadViewDataHook?.on(async () => {
await loadData()
})

onMounted(async () => {
await loadData()

await loadGalleryData()
})

provide(ReloadRowDataHookInj, reloadViewDataHook)

watch(view, async (nextView) => {
if (nextView?.type === ViewTypes.GALLERY) {
await loadData()
await loadGalleryData()
}
})

const currentRow = toRef(props, 'row')

const { row } = useProvideSmartsheetRowStore(meta as Ref<TableType>, currentRow)
</script>

<template>
<LazySmartsheetRow :row="row">
<a-card
hoverable
class="!rounded-lg h-full overflow-hidden break-all max-w-[450px]"
:data-testid="`nc-gallery-card-${row.row.id}`"
>
<template v-if="galleryData?.fk_cover_image_col_id" #cover>
<a-carousel v-if="!reloadAttachments && attachments(row).length" autoplay class="gallery-carousel" arrows>
<template #customPaging>
<a>
<div class="pt-[12px]">
<div></div>
</div>
</a>
</template>

<template #prevArrow>
<div style="z-index: 1"></div>
</template>

<template #nextArrow>
<div style="z-index: 1"></div>
</template>

<template v-for="(attachment, index) in attachments(row)">
<LazyCellAttachmentImage
v-if="isImage(attachment.title, attachment.mimetype ?? attachment.type)"
:key="`carousel-${row.row.id}-${index}`"
class="h-52 object-contain"
:srcs="getPossibleAttachmentSrc(attachment)"
/>
</template>
</a-carousel>

<MdiFileImageBox v-else class="w-full h-48 my-4 text-cool-gray-200" />
</template>

<div v-for="col in fieldsWithoutCover" :key="`record-${row.row.id}-${col.id}`">
<div
v-if="!isRowEmpty(row, col) || isLTAR(col.uidt)"
class="flex flex-col space-y-1 px-4 mb-6 bg-gray-50 rounded-lg w-full"
>
<div class="flex flex-row w-full justify-start border-b-1 border-gray-100 py-2.5">
<div class="w-full text-gray-600">
<LazySmartsheetHeaderVirtualCell v-if="isVirtualCol(col)" :column="col" :hide-menu="true" />

<LazySmartsheetHeaderCell v-else :column="col" :hide-menu="true" />
</div>
</div>

<div class="flex flex-row w-full pb-3 pt-2 pl-2 items-center justify-start">
<LazySmartsheetVirtualCell v-if="isVirtualCol(col)" v-model="row.row[col.title]" :column="col" :row="row" />

<LazySmartsheetCell v-else v-model="row.row[col.title]" :column="col" :edit-enabled="false" :read-only="true" />
</div>
</div>
</div>
</a-card>
</LazySmartsheetRow>
</template>
13 changes: 8 additions & 5 deletions packages/nc-gui/composables/useMapViewDataStore.ts
Expand Up @@ -22,7 +22,7 @@ const formatData = (list: Record<string, any>[]) =>

const [useProvideMapViewStore, useMapViewStore] = useInjectionState(
(
meta: Ref<TableType | undefined>,
meta: Ref<MapType | undefined>,
viewMeta: Ref<ViewType | MapType | undefined> | ComputedRef<(ViewType & { id: string }) | undefined>,
shared = false,
where?: ComputedRef<string | undefined>,
Expand All @@ -31,6 +31,8 @@ const [useProvideMapViewStore, useMapViewStore] = useInjectionState(
throw new Error('Table meta is not available')
}

const defaultPageSize = 1000

const formattedData = ref<Row[]>([])

const { api } = useApi()
Expand All @@ -45,14 +47,12 @@ const [useProvideMapViewStore, useMapViewStore] = useInjectionState(

const { sorts, nestedFilters } = useSmartsheetStoreOrThrow()

const { fetchSharedViewData } = useSharedView()
const { sharedView, fetchSharedViewData } = useSharedView(defaultPageSize)

const mapMetaData = ref<MapType>({})

const geoDataFieldColumn = ref<ColumnType | undefined>()

const defaultPageSize = 1000

const paginationData = ref<PaginatedType>({ page: 1, pageSize: defaultPageSize })

const queryParams = computed(() => ({
Expand All @@ -72,7 +72,10 @@ const [useProvideMapViewStore, useMapViewStore] = useInjectionState(

async function loadMapMeta() {
if (!viewMeta?.value?.id || !meta?.value?.columns) return
mapMetaData.value = await $api.dbView.mapRead(viewMeta.value.id)
mapMetaData.value = isPublic.value ? (sharedView.value?.view as MapType) : await $api.dbView.mapRead(viewMeta.value.id)

// if (!viewMeta?.value?.id || !meta?.value?.columns) return
// mapMetaData.value = await $api.dbView.mapRead(viewMeta.value.id)
wingkwong marked this conversation as resolved.
Show resolved Hide resolved
geoDataFieldColumn.value =
(meta.value.columns as ColumnType[]).filter((f) => f.id === mapMetaData.value.fk_geo_data_col_id)[0] || {}
}
Expand Down