Skip to content

Commit

Permalink
Fixes to activity page
Browse files Browse the repository at this point in the history
- add location coordinates to location links
- correctly group locations with only coordinates
- use translation keys
- align placemark icon to top of text
  • Loading branch information
wbazant committed Dec 26, 2024
1 parent 91aab55 commit 82d7b87
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 87 deletions.
34 changes: 21 additions & 13 deletions public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,10 @@
"layouts": {
"application": {
"menu": {
"data": "The data",
"in_the_press": "In the press",
"the_project": "The project",
"sharing_the_harvest": "Sharing the harvest"
"the_data": "The data",
"sharing_the_harvest": "Sharing the harvest",
"in_the_press": "In the press"
}
}
},
Expand Down Expand Up @@ -198,16 +198,6 @@
"already_authenticated": "You are already signed in."
}
},
"layouts": {
"application": {
"menu": {
"the_project": "The project",
"the_data": "The data",
"sharing_the_harvest": "Sharing the harvest",
"in_the_press": "In the press"
}
}
},
"locations": {
"infowindow": {
"fruiting": ["Flowers", "Unripe fruit", "Ripe fruit"],
Expand Down Expand Up @@ -239,5 +229,23 @@
"problems": {
"problem_type": "Problem type",
"description_subtext": "Any information that might help us evaluate the problem"
},
"changes": {
"type": {
"added": "added",
"edited": "edited",
"grafted": "grafted",
"visited": "visited"
},
"change_in_city": "{{type}} in {{city}}",
"recent_changes": "Recent changes"
},
"time": {
"last_24_hours": "Last 24 hours",
"days": {
"other": "{{count}} days",
"one": "{{count}} day"
},
"time_ago": "{{time}} ago"
}
}
42 changes: 25 additions & 17 deletions public/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,10 @@
"layouts": {
"application": {
"menu": {
"data": "Les données",
"in_the_press": "Dans la presse",
"the_project": "Le projet",
"sharing_the_harvest": "Partager la récolte"
"the_data": "Les données",
"sharing_the_harvest": "Partager la récolte",
"in_the_press": "Dans la presse"
}
}
},
Expand Down Expand Up @@ -152,7 +152,7 @@
"forgot_password": "Réinitialiser votre mot de passe",
"resend_confirmation_instructions": "Renvoyer les instructions de validation",
"password_confirmation": "Confirmation du mot de passe",
"bio": "À propos de vous",
"bio": "Parlez-nous de vous",
"send_password_instructions": "Envoyer les instructions de réinitialisation de mot de passe",
"new_password": "Nouveau mot de passe",
"new_password_confirmation": "Confirmation du nouveau mot de passe",
Expand All @@ -175,8 +175,8 @@
},
"devise": {
"registrations": {
"signed_up": "Bienvenue ! Votre inscription a bien été enregistrée.",
"update_needs_confirmation": "Votre compte a bien été mis à jour mais nous devons vérifier votre nouvelle adresse e-mail. Veuillez consulter votre messagerie et cliquer sur le lien pour confirmer votre nouvelle adresse.",
"signed_up": "Bienvenue ! Votre inscription a bien été enregistrée.",
"update_needs_confirmation": "Votre compte a bien été mis à jour mais nous devons vérifier votre nouvelle adresse e-mail. Veuillez consulter votre messagerie et cliquer sur le lien pour confirmer votre nouvelle adresse.",
"updated": "Votre compte a bien été mis à jour."
},
"confirmations": {
Expand All @@ -186,23 +186,13 @@
},
"passwords": {
"send_instructions": "Vous allez recevoir un email dans quelques instants avec les instructions pour réinitialiser votre mot de passe.",
"no_token": "Vous ne pouvez pas accéder à cette page sans passer par un e-mail de réinitialisation de mot de passe. Si vous venez en effet d’un tel email, assurez-vous d’utiliser l’URL complète.",
"no_token": "Vous ne pouvez pas accéder à cette page sans passer par un e-mail de réinitialisation de mot de passe. Si ce mail ne fonctionne pas, assurez-vous d’utiliser l’URL complète.",
"updated_not_active": "Votre mot de passe a été changé avec succès."
},
"failure": {
"already_authenticated": "Vous êtes déjà connecté."
}
},
"layouts": {
"application": {
"menu": {
"the_project": "Le projet",
"the_data": "Les données",
"sharing_the_harvest": "Partager la récolte",
"in_the_press": "Dans la presse"
}
}
},
"locations": {
"infowindow": {
"fruiting": ["Fleurs", "Fruits pas mûrs", "Fruits mûrs"],
Expand Down Expand Up @@ -234,5 +224,23 @@
"problems": {
"problem_type": "Type de problème",
"description_subtext": "Toute information pouvant nous aider à évaluer le problème"
},
"changes": {
"type": {
"added": "ajouté",
"edited": "modifié",
"grafted": "greffé",
"visited": "visité"
},
"change_in_city": "{{type}} à {{city}}",
"recent_changes": "Modifications récentes"
},
"time": {
"last_24_hours": "Dernières 24 heures",
"days": {
"other": "{{count}} jours",
"one": "{{count}} jour"
},
"time_ago": "Il y a {{time}}"
}
}
2 changes: 1 addition & 1 deletion scripts/translation_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def is_source_file(self):
def extract_translation_keys(self):
with open(self.file_path, 'r') as file:
content = file.read()
pattern = r"\bt\(['\"`](.+?)['\"`]\)"
pattern = r"\bt\(['\"`](.+?)['\"`]"

# Strip dynamic parts like ${i} from translation keys
self.keys = [ re.sub(r'\.\${[^}]+}$', '', key) for key in re.findall(pattern, content)]
Expand Down
6 changes: 4 additions & 2 deletions src/components/activity/ActivityPage.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useEffect, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import Skeleton from 'react-loading-skeleton'
import { useDispatch, useSelector } from 'react-redux'
import styled from 'styled-components'
Expand Down Expand Up @@ -36,6 +37,7 @@ const ActivityPage = () => {
const dispatch = useDispatch()

const loadMoreRef = useRef()
const { t } = useTranslation()

const { typesAccess } = useSelector((state) => state.type)
const { locationChanges, isLoading } = useSelector((state) => state.activity)
Expand Down Expand Up @@ -83,10 +85,10 @@ const ActivityPage = () => {
return (
<PageScrollWrapper>
<PageTemplate from="Settings">
<h1>Recent Changes</h1>
<h1>{t('changes.recent_changes')}</h1>
{locationChanges.length > 0 &&
groupedData.map((period) => (
<ChangesPeriod key={period.periodName} period={period} />
<ChangesPeriod key={period.daysAgo} period={period} />
))}
<div ref={loadMoreRef}></div>
{isLoading && (
Expand Down
68 changes: 44 additions & 24 deletions src/components/activity/ChangesPeriod.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { Map } from '@styled-icons/boxicons-regular'
import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { useDispatch } from 'react-redux'
import { Link } from 'react-router-dom'
import styled from 'styled-components'

import { MIN_LOCATION_ZOOM } from '../../constants/map'
import { setAnchorElementId } from '../../redux/activitySlice'
import { viewToString } from '../../utils/appUrl'
import { useIsDesktop } from '../../utils/useBreakpoint'

const AuthorLink = styled(Link)`
Expand Down Expand Up @@ -65,11 +68,14 @@ const LocationTypesList = ({ locations, onClickLink }) => (
}
return [curr]
}, [])

return (
<React.Fragment key={`${loc.locationId}-${idx}`}>
<LocationLink
to={`/locations/${loc.locationId}`}
to={`/locations/${loc.locationId}/${viewToString(
loc.coordinates.latitude,
loc.coordinates.longitude,
MIN_LOCATION_ZOOM,
)}`}
onClick={onClickLink}
>
{typesWithSeparators}
Expand All @@ -87,29 +93,29 @@ const ActivityText = styled.span`

const ActivityTextComponent = ({
location,
coordinates,
author,
userId,
type,
interactionType,
onClickLink,
}) => {
const locationParts = [location.city, location.state, location.country]
const hasLocationInfo = locationParts.filter(Boolean).length > 0
const hasCoordinates = coordinates.latitude && coordinates.longitude
const { t } = useTranslation()

return (
<ActivityText>
{type} in{' '}
{hasLocationInfo
? locationParts.filter(Boolean).join(', ')
: hasCoordinates && (
<>
<Map size="1em" />
{` ${coordinates.latitude.toFixed(4)}, ${coordinates.longitude.toFixed(
4,
)}`}
</>
)}
{t('changes.change_in_city', {
type: t(`changes.type.${interactionType}`),
city: '',
})}
{hasLocationInfo ? (
locationParts.filter(Boolean).join(', ')
) : (
<>
<Map style={{ verticalAlign: 'text-top' }} size="1em" />
{location.coordinatesGrid}
</>
)}
{author && ' — '}
{author && (
<>
Expand All @@ -126,23 +132,37 @@ const ActivityTextComponent = ({
)
}

const formatPeriodName = (daysAgo, t) => {
if (daysAgo === 0) {
return t('time.last_24_hours')
} else if (daysAgo === 1) {
const time = t('time.days.one', { count: daysAgo })
return t('time.time_ago', { time })
} else {
const time = t('time.days.other', { count: daysAgo })
return t('time.time_ago', { time })
}
}

const ChangesPeriod = ({ period }) => {
const dispatch = useDispatch()
const isDesktop = useIsDesktop()
const onClickLink = useCallback(
() => dispatch(setAnchorElementId(period.periodName)),
[dispatch, period.periodName],
() => dispatch(setAnchorElementId(period.daysAgo.toString())),
[dispatch, period.daysAgo],
)
const { t } = useTranslation()

return (
<div id={period.periodName}>
<h3>{period.periodName}</h3>
<div id={period.daysAgo}>
<h3>{formatPeriodName(period.daysAgo, t)}</h3>
<ListChanges>
{period.activities.map((activity, index) => (
<ListItem key={index} isDesktop={isDesktop}>
{['added', 'edited', 'visited'].map((type) => {
const locations = activity[type]
{['added', 'edited', 'visited'].map((interactionType) => {
const locations = activity[interactionType]
return locations.length > 0 ? (
<p key={type}>
<p key={interactionType}>
<LocationTypesList
locations={locations}
onClickLink={onClickLink}
Expand All @@ -152,7 +172,7 @@ const ChangesPeriod = ({ period }) => {
coordinates={activity.coordinates}
author={activity.author}
userId={activity.userId}
type={type}
interactionType={interactionType}
onClickLink={onClickLink}
/>
</p>
Expand Down
14 changes: 8 additions & 6 deletions src/utils/appUrl.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
export const viewToString = (lat, lng, zoom) => {
// 7 decimal places gives precision to 1 cm
// Normalize longitude to -180 to 180 range
const normalizedLng = ((lng + 540) % 360) - 180
return `@${lat.toFixed(7)},${normalizedLng.toFixed(7)},${zoom}z`
}

export const parseCurrentUrl = () => {
const { pathname } = new URL(window.location.href)
const stateIndex = pathname.indexOf('/@')
Expand Down Expand Up @@ -58,10 +65,5 @@ export const currentPathWithView = (view) => {

const path = stateIndex === -1 ? pathname : pathname.substring(0, stateIndex)

// 7 decimal places gives precision to 1 cm
// Normalize longitude to -180 to 180 range
const normalizedLng = ((view.center.lng + 540) % 360) - 180
return `${path}/@${view.center.lat.toFixed(7)},${normalizedLng.toFixed(7)},${
view.zoom
}z`
return `${path}/${viewToString(view.center.lat, view.center.lng, view.zoom)}`
}
Loading

0 comments on commit 82d7b87

Please sign in to comment.