Skip to content

Commit

Permalink
chore: improve typings + add object ordering
Browse files Browse the repository at this point in the history
  • Loading branch information
wazolab committed Jan 22, 2025
1 parent 8b335d2 commit f7e8e93
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 87 deletions.
10 changes: 5 additions & 5 deletions src/components/LoCha/LoChaDiff.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ const diffTags = computed(() => {
return
let before, after
if (selectedFeatures.value[0].properties?.is_created) {
if (selectedFeatures.value[0].properties.is_created) {
after = selectedFeatures.value[0]
}
else if (selectedFeatures.value[0].properties?.is_deleted) {
else if (selectedFeatures.value[0].properties.is_deleted) {
before = selectedFeatures.value[0]
}
else {
Expand All @@ -21,11 +21,11 @@ const diffTags = computed(() => {
}
const diff = []
const allKeys = new Set([...Object.keys(before?.properties?.tags || {}), ...Object.keys(after?.properties?.tags || {})])
const allKeys = new Set([...Object.keys(before?.properties.tags || {}), ...Object.keys(after?.properties.tags || {})])
for (const key of allKeys) {
const beforeValue = before?.properties?.tags[key] || null
const afterValue = after?.properties?.tags[key] || null
const beforeValue = before?.properties.tags[key] || null
const afterValue = after?.properties.tags[key] || null
let status = ''
if (beforeValue === afterValue) {
Expand Down
3 changes: 2 additions & 1 deletion src/components/LoCha/LoChaList.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<script setup lang="ts">
import type { IFeature } from '@/composables/useApi'
import LoChaObject from '@/components/LoCha/LoChaObject.vue'
defineProps<{
features: GeoJSON.Feature[]
features: IFeature[]
title: string
}>()
</script>
Expand Down
50 changes: 23 additions & 27 deletions src/components/LoCha/LoChaObject.vue
Original file line number Diff line number Diff line change
@@ -1,36 +1,31 @@
<script setup lang="ts">
import type { IFeature } from '@/composables/useApi'
import { loChaColors, loChaStatus, useLoCha } from '@/composables/useLoCha'
import { computed } from 'vue'
const props = defineProps<{
feature: GeoJSON.Feature
feature: IFeature
}>()
// TODO: move as much as possible into useLoCha composable
if (props.feature.id === undefined)
throw new Error(`Feature ID is missing.`)
if (!props.feature.properties)
throw new Error(`Feature ${props.feature.id} has no properties.`)
const status = computed(() => {
if (props.feature.properties!.is_created) {
if (props.feature.properties.is_created) {
return loChaStatus.create
}
if (props.feature.properties!.is_deleted) {
if (props.feature.properties.is_deleted) {
return loChaStatus.delete
}
if (props.feature.properties!.is_before) {
if (props.feature.properties.is_before) {
return loChaStatus.updateBefore
}
return loChaStatus.updateAfter
})
const name = computed(() => {
const content = props.feature.properties!.tags.name || '-'
const content = props.feature.properties.tags.name || '-'
switch (status.value) {
case 'create':
Expand All @@ -51,9 +46,6 @@ const isSelected = computed(() => {
if (!selectedLink.value)
return false
if (typeof props.feature.id !== 'number')
throw new Error(`Feature ${props.feature.id} ID has wrong type: ${typeof props.feature.id}. Should be a number.`)
return [selectedLink.value.before, selectedLink.value.after].includes(props.feature.id)
})
Expand All @@ -63,64 +55,68 @@ const style = computed(() => ({
const { showLink, resetLink } = useLoCha()
// TODO: Move to useLoCha composable
function handleClick(id: string | number) {
if (typeof id !== 'number')
throw new Error(`Feature ${id} ID has wrong type: ${typeof id}. Should be a number.`)
function handleClick(id: number) {
isSelected.value
? resetLink()
: showLink(id, status.value)
}
</script>

<template>
<article :style="style" class="locha-object" @click="handleClick(feature.id!)">
<article :style="style" class="locha-object" @click="handleClick(feature.id)">
<header>
<h3>
{{ name }}
<a
:href="`https://www.openstreetmap.org/${feature.properties.objtype}/${feature.properties.id}/history`"
title="OSM History"
:href="`https://www.openstreetmap.org/${feature.properties!.objtype}/${feature.properties!.id}/history`"
target="_blank"
@click.stop
>
{{ `${feature.properties!.objtype[0]}${feature.properties!.id}` }}
{{ `${feature.properties.objtype[0]}${feature.properties.id}` }}
</a>
</h3>
<span>👤{{ feature.properties!.username }}</span>
<a
:href="`https://www.openstreetmap.org/user/${feature.properties.username}`"
:title="`View ${feature.properties.username} OSM profile`"
target="_blank"
@click.stop
>
👤{{ feature.properties.username }}
</a>
</header>
<div v-show="isSelected" class="actions">
<a
:href="`https://www.openstreetmap.org/${feature.properties.objtype}/${feature.properties.id}/history`"
type="button"
title="Edit in OSM iD"
:href="`https://www.openstreetmap.org/${feature.properties!.objtype}/${feature.properties!.id}/history`"
target="_blank"
@click.stop
>
OSM iD
</a>
<a
:href="`http://127.0.0.1:8111/load_object?objects=${feature.properties.objtype[0]}${feature.properties.id}`"
type="button"
title="Edit in JOSM"
:href="`http://127.0.0.1:8111/load_object?objects=${feature.properties!.objtype[0]}${feature.properties!.id}`"
target="hidden_josm_target"
@click.stop
>
JOSM
</a>
<a
:href="`https://osmlab.github.io/osm-deep-history/#/${feature.properties.objtype}/${feature.properties.id}`"
type="button"
title="OSM Deep History"
:href="`https://osmlab.github.io/osm-deep-history/#/${feature.properties!.objtype}/${feature.properties!.id}`"
target="_blank"
@click.stop
>
Deep H
</a>
<a
:href="`https://pewu.github.io/osm-history/#/${feature.properties.objtype}/${feature.properties.id}`"
type="button"
title="OSM History Viewer"
:href="`https://pewu.github.io/osm-history/#/${feature.properties!.objtype}/${feature.properties!.id}`"
target="_blank"
@click.stop
>
Expand All @@ -147,7 +143,7 @@ h3 {
font-weight: 400;
}
span {
h3 + a {
font-size: 0.75em;
color: #333333;
}
Expand Down
30 changes: 23 additions & 7 deletions src/composables/useApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ interface ApiComposable {
setError: (err: Error) => void
}

type ObjectType = 'node' | 'way' | 'relation'

type ActionType = 'accept' | 'reject'

type Action = ['diff' | ActionType | string | null]
Expand Down Expand Up @@ -73,11 +75,30 @@ export interface ApiLink {
diff_tags?: Actions
}

export interface IFeature extends GeoJSON.Feature {
id: number
properties: {
objtype: ObjectType
id: number
geom_distance: number
deleted: boolean
version: number
username: string
created: string
tags: Record<string, string>
is_before?: boolean
is_after?: boolean
is_created?: boolean
is_deleted?: boolean
}
}

/**
* Interface representing the API response.
* Extends `GeoJSON.FeatureCollection` to represent geographic data and includes additional metadata.
*/
export interface ApiResponse extends GeoJSON.FeatureCollection {
features: IFeature[]
metadata: {
links: ApiLink[]
changesets: Changeset[]
Expand Down Expand Up @@ -166,12 +187,6 @@ export function useApiConfig(): ApiComposable {
*/
function transformFeatures(data: ApiResponse): ApiResponse['features'] {
return data.features.map((feature) => {
if (feature.id === undefined)
throw new Error(`Feature ID is missing.`)

if (typeof feature.id !== 'number')
throw new Error(`Feature ${feature.id} ID has wrong type: ${typeof feature.id}. Should be a number.`)

// TODO: As of today it find only the first occurence of the feature ID
// Need to find them all because we have a many-to-many relation with links
const link = data.metadata.links.find(({ before, after }) => before === feature.id || after === feature.id)
Expand Down Expand Up @@ -221,7 +236,8 @@ export function useApiConfig(): ApiComposable {
}

return feature
}).sort((a, b) => area(b) - area(a))
})
.sort((a, b) => area(b) - area(a)) // Sort by area surface in order to have bigger geometries before smaller ones.
}

function setError(err: Error): void {
Expand Down
65 changes: 39 additions & 26 deletions src/composables/useLoCha.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ApiLink, ApiResponse } from '@/composables/useApi'
import type { ApiLink, ApiResponse, IFeature } from '@/composables/useApi'
import type { ComputedRef, Ref } from 'vue'
import { computed, ref } from 'vue'

Expand Down Expand Up @@ -49,23 +49,23 @@ export const loChaStatus = Object.fromEntries(
* which includes various references and computed values to track the features and metadata.
*/
export interface LoCha {
afterFeatures: Ref<GeoJSON.Feature[]>
beforeFeatures: Ref<GeoJSON.Feature[]>
afterFeatures: Ref<IFeature[]>
beforeFeatures: Ref<IFeature[]>
featureCount: ComputedRef<number | undefined>
showLink: (id: number, status: Status) => void
linkCount: ComputedRef<number | undefined>
loCha: Ref<ApiResponse | undefined>
selectedLink: Ref<ApiLink | undefined>
selectedFeatures: ComputedRef<GeoJSON.Feature[] | undefined>
selectedFeatures: ComputedRef<IFeature[] | undefined>
setLoCha: (loCha: ApiResponse) => void
resetLink: () => void
getStatus: (feature: GeoJSON.Feature) => Status
getStatus: (feature: IFeature) => Status
}

// Internal state variables
const loCha = ref<ApiResponse>()
const afterFeatures = ref<GeoJSON.Feature[]>([])
const beforeFeatures = ref<GeoJSON.Feature[]>([])
const afterFeatures = ref<IFeature[]>([])
const beforeFeatures = ref<IFeature[]>([])
const selectedLink = ref<ApiLink>()

/**
Expand All @@ -90,8 +90,8 @@ export function useLoCha(): LoCha {
if (!selectedLink.value)
return

let after: GeoJSON.Feature | undefined
let before: GeoJSON.Feature | undefined
let after: IFeature | undefined
let before: IFeature | undefined

if (selectedLink.value.after)
after = _getFeature(selectedLink.value.after)
Expand Down Expand Up @@ -125,10 +125,7 @@ export function useLoCha(): LoCha {
selectedLink.value = undefined
}

function getStatus(feature: GeoJSON.Feature): Status {
if (!feature.properties)
throw new Error(`Feature ${feature.id} has no properties.`)

function getStatus(feature: IFeature): Status {
if (feature.properties.is_created) {
return loChaStatus.create
}
Expand All @@ -144,7 +141,7 @@ export function useLoCha(): LoCha {
return loChaStatus.updateAfter
}

function _getFeature(id: number): GeoJSON.Feature | undefined {
function _getFeature(id: number): IFeature | undefined {
return loCha.value?.features.find(feature => feature.id === id)
}

Expand All @@ -162,22 +159,38 @@ export function useLoCha(): LoCha {
if (!featureCount.value)
return

loCha.value.features.forEach((feature) => {
if (!feature.properties)
return
const sortByObjType = (feature: IFeature): number => {
switch (feature.properties.objtype) {
case 'node': return 1
case 'way': return 2
case 'relation': return 3
default: return 4
}
}

if (feature.properties.is_before)
beforeFeatures.value.push(feature)
const sortByActionType = (feature: IFeature): number => {
const { is_before, is_after, is_created, is_deleted } = feature.properties
if (is_before || is_after)
return 1
if (is_created)
return 2
if (is_deleted)
return 3
return 4
}

if (feature.properties.is_deleted)
beforeFeatures.value.push(feature)
loCha.value.features
.sort((a, b) => sortByObjType(a) - sortByObjType(b))
.sort((a, b) => sortByActionType(a) - sortByActionType(b))
.forEach((feature) => {
const { is_before, is_after, is_created, is_deleted } = feature.properties

if (feature.properties.is_after)
afterFeatures.value.push(feature)
if (is_before || is_deleted)
beforeFeatures.value.push(feature)

if (feature.properties.is_created)
afterFeatures.value.push(feature)
})
if (is_after || is_created)
afterFeatures.value.push(feature)
})
}

function _getLink(id: number, status: Status): ApiLink | undefined {
Expand Down
Loading

0 comments on commit f7e8e93

Please sign in to comment.