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

GSLUX-756: Display feature info in info panel #171

Open
wants to merge 29 commits into
base: main
Choose a base branch
from

Conversation

tkohr
Copy link
Contributor

@tkohr tkohr commented Nov 15, 2024

JIRA issue

https://jira.camptocamp.com/browse/GSLUX-756

Description

The goal of this PR is to set a base for the feature info. It now displays the feature info of the default template.

Remaining to-dos for separate PRs remain:

  • Migrate all other templates and potentially corresponding logic from the QueryController
  • Migrate the modal currently commented in feature-info.vue (originating from info.html)

Permalink with all layers to test default-template.html: http://localhost:5173/theme/main?version=3&lang=fr&X=702429&Y=6396653&zoom=16&rotation=0&features=&layers=655-2842-808-1713-1714-269-302-1813&opacities=1-0-1-1-1-1-1-1&time=--------------&bgLayer=basemap_2015_global

Base automatically changed from GSLUX-754-info-panel to main November 18, 2024 10:00
Copy link
Contributor

GitHub Pages links: * Luxembourg-geoportail: https://geoportail-luxembourg.github.io/luxembourg-geoportail/GSLUX-756-feature-info/

note: with z-20 it appears over sidepanel on mobile
aka isQuerying, hiddenContent, infoOpen in v3
to distinguish pan, longpress and featureInfo from feature draw/edit
which should already be handled by draw/edit logic
make featureInfoService a singleton at the same time
to display cursor pointer on layers
…dio link and ordering

fix panel overflow, clean dom, add util to sanitize url along the way
also use DefaultTemplate as fallback for templates not yet migrated
does not work well with multiple profiles yet
</button></a
>
<span
v-if="isLink(entry['value']) && entry['key'] == 'f_AudioURL'"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

=== 'f_AudioURL'?

"
:src="`${getTrustedUrl(entry['value'])}`"
title="water level graph"
></iframe>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have somewhere (in the doc, or in code comment) the corresponding layers to test each cases please?

<profile-feature-info :feature="feature" />
</div>
<div v-if="layers.has_profile">
<a
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not very important bu this would be button instead of a link I guess, as there is no href (for accessibility?)

import { defineProps } from 'vue'
defineProps({
layers: {
type: Object,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am expecting an array of layers here, or am i missing something? Can we have a better typing than Object?

@@ -0,0 +1,152 @@
import { useTranslation } from 'i18next-vue'
import { FeatureJSON } from '../../../services/info/feature-info.model'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@/services

}[] {
return Object.entries(attributes)
.filter(([key]) => key !== 'showProfile')
.map(([key, value]) => ({ key: prefix + key, value }))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have done the sorting here (instead of inside the template)

)
}

export function isEmpty(value: string | undefined | null): boolean {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isEmptyString maybe?

ok = true
}

return ok
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you have time it oculd be great to refactor this huge test into small ones with named cases (eg. storage-rules-version-heper.ts)

return sanitizeUrl(urlEn ?? urlFr)
case 'lb':
return sanitizeUrl(urlLb ?? urlFr)
default:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe regroup case 'fr' and default

@@ -15,6 +15,7 @@ import FullscreenControl from '../map-controls/fullscreen-control.vue'
import ZoomControl from '../map-controls/zoom-control.vue'
import ZoomToExtentControl from '../map-controls/zoom-to-extent-control.vue'
import useDraw from '@/composables/draw/draw.composable'
import useFeatureInfo from '../../composables/info/feature-info.composable'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@/composables

(featureMatch &&
(layersOpen.value || myMapsOpen.value) &&
drawnFeatures.value.length > 0) ||
(featureMatch && layers.value.length === 0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

both conditions have featureMatch, right? can be simplified

}

const tempTime = new Date().getTime()
if (tempTime - pointerUpTime.value <= 499) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this one is throttle? you can find one in the utils.ts

if (timeoutId.value !== null) {
clearTimeout(timeoutId.value)
}
})()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason why this is a self called function? (same question for event above, is it necessary?)

if (fid.value) {
;(async () => {
await getFeatureInfoById(fid.value as string)
})()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason why using this synthax?

try {
if (layersList.length > 0) {
setLoading(true)
map.getViewport().style.cursor = 'wait'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the style cursor going back to default point in this function? I don't see where. Maybe the cursor style can be handled in a watch on loading ref.

setInfoPanelHidden(true)
}

const node = findById(splittedFid[0])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can splittedFid[0] be declared as a const? like const fId = splittedFid[0] if i understood well

reset()
}
}
} catch (error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it looks like you can merge these two try/catch ? because you are doing the same reset() and nothing more in both cases

]

setLoading(true)
map.getViewport().style.cursor = 'wait'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it looks like this can be done in a watcher


const url = new URL(INFO_SERVICE_URL)
Object.keys(params).forEach(key =>
url.searchParams.append(key, (params as Record<string, any>)[key])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can use existing fetchApi() (api.service.ts) you won't need to parse the params object like this.

}
}

async function singleclickEvent(evt: MapBrowserEvent<any>): Promise<void> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if i read the function name i don't know it will trigger an api call, either make it clear it the naming that they will be a call, or extract the api call into a new function (eg. existing services in src/services/api/ folder)

lastHighlightedFeatures.value,
false
)
reset()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think instead of this sequential handling you could have use more watchers, eg:

on click event => trigger call api and set the feature data ref
have a watcher on feature data ref => on change, trigger reset if the data is invalid, or trigger display if valid

item['layerLabel'] = layerLabel[item.layer]
let found = false
for (let iLayer = 0; iLayer < responses.value.length; iLayer++) {
if (responses.value[iLayer].layer == item.layer) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

===

data.forEach(item => {
item['layerLabel'] = layerLabel[item.layer]
let found = false
for (let iLayer = 0; iLayer < responses.value.length; iLayer++) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you don't like array functions anymore? ^^ I guess it is a copy past from v3 ;)

hitTolerance: 5,
}
)
return selected.length > 0 ? true : false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return selected.length > 0 ? true : false
return selected.length > 0

onUnmounted(() => {
clearContent()
featureInfoLayerService.clearFeatures()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This clearFeatures is done 3 times in the composable, maybe it would be better to keep this one in the composable also (you can add onunmouted in composables).

Also, why not doing a reset() here?

]

const geometryType = feature.getGeometry()?.getType()
return geometryType == 'Point' || geometryType == 'MultiPoint'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

===

if (fit && extent) {
const fitOptions: FitOptions = {
size: this.map.getSize() as Size,
maxZoom: 17,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a maxzoom in map store that can be set by the config theme, I don't know if this maxZoom in store should be taken into account. (Can be done later)

@AlitaBernachot
Copy link
Contributor

AlitaBernachot commented Dec 4, 2024

Thanks for this huge work!

Tested on github pages, working great, some minor things i have noticed:

  • Width when clickin on direct link
    image

  • maybe improve line height between lines (not between label and values like in prod wich is weird) (1st image github pages, 2nd production)
    image
    image

  • exports button height is not the same as in prod (+ it could be great to add some margin between buttons and the graph, but this is my opinion, i let you decide)
    image

)"
:key="entry.key"
>
<span
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is already a div wrapper above, i think you can get rid of this span (plus it is more logic to seperate lines with a div than with a span, not very important but this is html semantic)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants