Skip to content

Commit

Permalink
added cryptocities flower
Browse files Browse the repository at this point in the history
  • Loading branch information
onmax committed Sep 22, 2023
1 parent 7ef2b54 commit 0bea2a7
Show file tree
Hide file tree
Showing 24 changed files with 273 additions and 340 deletions.
24 changes: 13 additions & 11 deletions database/getters.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import type { BoundingBox, Cluster, ComputedClusterSet, DatabaseArgs, DatabaseAuthArgs, DatabaseStatistics, Location, Suggestion } from '../types/index.ts'
import { Category, Currency, DbReadFunction, Provider } from '../types/index.ts'
import type { FeatureCollection } from '@turf/helpers'
import type { BoundingBox, ComputedClusterSet, DatabaseArgs, DatabaseAuthArgs, Location, Suggestion } from '../types/index.ts'
import { Category, Cryptocity, Currency, DbReadFunction, Provider } from '../types/index.ts'
import { fetchDb } from './fetch.ts'

/**
* We hardcode the Currencies, Categories and Providers here, because they are rarely updated.
* We hardcode these values here, because they are rarely updated.
* Even if we update those values in the database, we always need to update UI regardless.
*/
export const CURRENCIES = Object.values(Currency)
export const CATEGORIES = Object.values(Category)
export const PROVIDERS = Object.values(Provider)
export const CRYPTOCITIES = Object.values(Cryptocity)

// Maximum number of rows from the database
const MAX_N_ROWS = 1000
Expand All @@ -27,7 +29,7 @@ export async function getLocations(dbArgs: DatabaseArgs | DatabaseAuthArgs, bbox
return locations.map(parseLocations)
}

export async function getLocation(dbArgs: DatabaseArgs, uuid: string, parseLocation: (l: Location) => Location): Promise<Location | undefined> {
export async function getLocation(dbArgs: DatabaseArgs | DatabaseAuthArgs, uuid: string, parseLocation: (l: Location) => Location): Promise<Location | undefined> {
const params = new URLSearchParams()
params.append('location_uuid', uuid)
const location = await fetchDb<Location>(DbReadFunction.GetLocation, dbArgs, params.toString())
Expand All @@ -38,21 +40,19 @@ export async function getLocation(dbArgs: DatabaseArgs, uuid: string, parseLocat
return parseLocation(location)
}

export async function searchLocations(dbArgs: DatabaseArgs, query: string) {
export async function searchLocations(dbArgs: DatabaseArgs | DatabaseAuthArgs, query: string) {
const params = new URLSearchParams()
params.append('p_query', query)
return await fetchDb<Omit<Suggestion, 'type'>[]>(DbReadFunction.SearchLocations, dbArgs, params.toString()) ?? []
}

type GetClustersReturn = ComputedClusterSet & { cryptocities: Cluster[] }
export async function getClusters(dbArgs: DatabaseArgs, bbox: BoundingBox, zoom: number, parseLocation: (l: Location) => Location = l => l): Promise<GetClustersReturn> {
export async function getClusters(dbArgs: DatabaseArgs | DatabaseAuthArgs, bbox: BoundingBox, zoom: number, parseLocation: (l: Location) => Location = l => l): Promise<ComputedClusterSet> {
const params = new URLSearchParams()
Object.entries(bbox).forEach(([key, value]) => params.append(key.toLocaleLowerCase(), value.toString()))
params.append('zoom_level', zoom.toString())
const res = await fetchDb<ComputedClusterSet>(DbReadFunction.GetLocationsClustersSet, dbArgs, params.toString())
return {
clusters: res?.clusters ?? [],
cryptocities: res?.cryptocities ?? [],
singles: res?.singles.map(parseLocation) ?? [],
}
}
Expand All @@ -61,10 +61,12 @@ export async function getClusters(dbArgs: DatabaseArgs, bbox: BoundingBox, zoom:
* The maximum zoom level at which the clusters are computed in the database.
* If the user zooms in more than this, the clusters will be computed in the client.
*/
export async function getClusterMaxZoom(dbArgs: DatabaseArgs): Promise<number> {
export async function getClusterMaxZoom(dbArgs: DatabaseArgs | DatabaseAuthArgs): Promise<number> {
return await fetchDb<number>(DbReadFunction.GetMaxZoom, dbArgs) ?? -1 // FIXME: Show error to user instead of using -1
}

export async function getStats(dbArgs: DatabaseArgs): Promise<DatabaseStatistics | undefined> {
return await fetchDb<DatabaseStatistics>(DbReadFunction.GetStats, dbArgs)
export async function getCryptocityPolygon(dbArgs: DatabaseArgs | DatabaseAuthArgs, city: Cryptocity): Promise<FeatureCollection | undefined> {
const params = new URLSearchParams()
params.append('city', city)
return await fetchDb<FeatureCollection>(DbReadFunction.GetCryptocityPolygon, dbArgs, params.toString())
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"@turf/boolean-point-in-polygon": "^6.5.0",
"@turf/boolean-within": "^6.5.0",
"@turf/helpers": "^6.5.0",
"@turf/intersect": "^6.5.0",
"@turf/points-within-polygon": "^6.5.0",
"@turf/union": "^6.5.0",
"@types/google.maps": "^3.54.0",
Expand All @@ -37,7 +38,7 @@
"@vueuse/math": "^10.4.1",
"@vueuse/router": "^10.4.1",
"pinia": "^2.1.6",
"radix-vue": "^0.1.33",
"radix-vue": "^0.2.3",
"supercluster": "^8.0.1",
"vue": "^3.3.4",
"vue-i18n": "^9.3.0",
Expand Down
19 changes: 15 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions shared/compute-cluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export function computeCluster(algorithm: Supercluster, locations: Location[], {
lng: c.geometry.coordinates[0],
lat: c.geometry.coordinates[1],
count,
diameter: Math.max(24, Math.min(48, 0.24 * count + 24)),

// Compute it lazily
get expansionZoom() { return algorithm.getClusterExpansionZoom(clusterId) },
Expand Down
13 changes: 13 additions & 0 deletions shared/geo-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { MultiPolygon } from '@turf/helpers'
import { featureCollection, multiPolygon, point } from '@turf/helpers'
import pointsWithinPolygon from '@turf/points-within-polygon'
import union from '@turf/union'
import intersect from '@turf/intersect'
import booleanWithin from '@turf/boolean-within'
import type { BoundingBox, Point } from '../types/index.ts'

Expand Down Expand Up @@ -70,3 +71,15 @@ export function addBBoxToArea(bbox: BoundingBox, multiPoly?: MultiPolygon) {
export function getItemsWithinBBox<T extends Point>(items: T[], bbox: BoundingBox) {
return pointsWithinPolygon(featureCollection(items.map(toPoint)), toMultiPolygon(bbox)).features.flatMap(f => f.properties)
}

/**
* Checks if a bounding box is intersecting another bounding box.
* Since bounding boxes can cross the antimeridian, we need to check if any of the polygons created by toPolygon
* is within the othe multipolygon
*/
export function bBoxesIntersect(bbox1: BoundingBox, bbox2: BoundingBox) {
const [polygon1, polygon2] = [bbox1, bbox2].map(toPolygon)
return polygon1.some(p1 => polygon2.some(p2 => intersect(p1, p2)))
}

export const distance = ({ lat: x1, lng: y1 }: Point, { lat: x2, lng: y2 }: Point) => Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)
9 changes: 6 additions & 3 deletions src/assets-dev/cryptocities-assets.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Cryptocity } from 'types'
import type { CryptocityContent } from 'types'
import type { CryptocityData } from 'types'
import { i18n } from '@/i18n/i18n-setup'

// Note that description is defined as a getter to be able to use the i18nKeyPassThrough, as the actual translation
// for providerLabel is happening in i18n-t in MapMarkers
export const cryptocitiesContent: Record<Exclude<Cryptocity, Cryptocity.None>, CryptocityContent> = {
export const cryptocitiesData: Record<Cryptocity, CryptocityData> = {
[Cryptocity.SanJose]: {
name: `Criptociudad ${Cryptocity.SanJose}`,
cryptocity: Cryptocity.SanJose,
name: 'Criptociudad San José',
get description() {
return [
i18n.t('The San Jose Cryptocity Initiative is part of the Cryptocity endeavour led by Nimiq and its partners.'),
Expand All @@ -15,5 +16,7 @@ export const cryptocitiesContent: Record<Exclude<Cryptocity, Cryptocity.None>, C
]
},
url: 'https://www.criptociudad.cr/',
centroid: { lat: 9.935, lng: -84.102 },
boundingBox: { neLat: 9.9720332, neLng: -84.0467977, swLat: 9.8998796, swLng: -84.1800327 },
},
}
5 changes: 2 additions & 3 deletions src/assets-dev/stories/locations.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { CATEGORIES } from 'database'
import { Category, Cryptocity, Currency, type Location, LocationLink, Provider } from 'types'
import { Category, Currency, type Location, LocationLink, Provider } from 'types'
import { providersAssets } from '../provider-assets'
import { translateCategory } from '@/translations'

type ExtraFields = Pick<Location, 'isAtm' | 'isDark' | 'isLight' | 'provider' | 'category' | 'category_label' | 'providerLabel' | 'providerTooltip' | 'theme' | 'bg' | 'hasBottomBanner' | 'sells' | 'url' | 'linkTo' | 'cryptocity'>
type ExtraFields = Pick<Location, 'isAtm' | 'isDark' | 'isLight' | 'provider' | 'category' | 'category_label' | 'providerLabel' | 'providerTooltip' | 'theme' | 'bg' | 'hasBottomBanner' | 'sells' | 'url' | 'linkTo'>
export function getExtra(provider: Provider, sells: Currency[] = [], linkTo: LocationLink = LocationLink.GMaps): ExtraFields {
const assets = providersAssets[provider]
if (!assets)
Expand Down Expand Up @@ -33,7 +33,6 @@ export function getExtra(provider: Provider, sells: Currency[] = [], linkTo: Loc
sells,
url,
linkTo,
cryptocity: Cryptocity.None,
}
}

Expand Down
13 changes: 6 additions & 7 deletions src/components/cards/cryptocity/BasicInfoCryptocity.vue
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
<script setup lang="ts">
import type { Component, PropType } from 'vue'
import type { CryptocityCluster } from 'types'
import CryptocityIcon from '@/components/icons/icon-cryptocity.vue'
defineProps({
cryptocity: {
type: Object as PropType<CryptocityCluster>,
required: true,
},
// cryptocity: {
// type: Object as PropType<CryptocityCluster>,
// required: true,
// },
icon: {
type: Object as PropType<Component>,
},
Expand All @@ -22,8 +21,8 @@ defineEmits({
<div class="flex items-center gap-x-2">
<CryptocityIcon class="w-[31px] h-[27px]" />
<div>
<h3 class="text-base text-space">{{ cryptocity.name }}</h3>
<span class="text-sm text-space/60">{{ $tc('{count} locations', cryptocity.count) }}</span>
<h3 class="text-base text-space">cryptocity.name</h3>
<span class="text-sm text-space/60">{{ $tc('{count} locations', -1) }}</span>
</div>
<component :is="icon" class="text-[#C9CAD3] p-1 w-6 h-6 ml-auto transition hover:bg-space/10 focus-visible:bg-space/10 rounded-full" data-cryptocity-trigger @click="$emit('iconClick', $event)" />
</div>
Expand Down
28 changes: 11 additions & 17 deletions src/components/cards/cryptocity/CryptocityCard.story.vue
Original file line number Diff line number Diff line change
@@ -1,25 +1,19 @@
<script setup lang="ts">
import type { CryptocityCluster } from 'types'
import { Cryptocity } from 'types'
import { ref } from 'vue'
import CryptocityCard from './CryptocityCard.vue'
import { cryptocitiesContent } from '@/assets-dev/cryptocities-assets'
// const cryptocity: CryptocityCluster = {
// ...cryptocitiesContent[Cryptocity.SanJose],
// count: 12,
// id: 1,
// lng: 1,
// lat: 1,
// expansionZoom: 4,
// city: Cryptocity.SanJose,
// }
const cryptocity: CryptocityCluster = {
...cryptocitiesContent[Cryptocity.SanJose],
count: 12,
id: 1,
lng: 1,
lat: 1,
expansionZoom: 4,
city: Cryptocity.SanJose,
}
const open = ref(false)
// const open = ref(false)
</script>

<template>
<Story title="Cryptocity Card" :layout="{ type: 'grid', width: '360px' }">
<CryptocityCard :cryptocity="cryptocity" :show-description="open" @icon-click="open = !open" />
<!-- <CryptocityCard :cryptocity="cryptocity" :show-description="open" @icon-click="open = !open" /> -->
</Story>
</template>
43 changes: 17 additions & 26 deletions src/components/cards/cryptocity/CryptocityCard.vue
Original file line number Diff line number Diff line change
@@ -1,18 +1,9 @@
<script setup lang="ts">
import type { CryptocityCluster } from 'types'
import type { PropType } from 'vue'
import { useBreakpoints } from '@vueuse/core'
import { screens } from 'tailwindcss-nimiq-theme'
import InfoIcon from '@/components/icons/icon-info.vue'
import ChevronDownIcon from '@/components/icons/icon-chevron-down.vue'
import BasicInfoCryptocity from '@/components/cards/cryptocity/BasicInfoCryptocity.vue'
import Button from '@/components/atoms/Button.vue'
defineProps({
cryptocity: {
type: Object as PropType<CryptocityCluster>,
required: true,
},
// cryptocity: {
// type: Object as PropType<CryptocityCluster>,
// required: true,
// },
showDescription: {
default: false,
type: Boolean,
Expand All @@ -23,9 +14,9 @@ defineEmits({
iconClick: (e: MouseEvent) => true,
})
const { smaller } = useBreakpoints(screens)
const DESKTOP_LAYOUT = 'md' // FIXME This is suppose to be the same value as in the tailwind config
const isMobile = smaller(DESKTOP_LAYOUT)
// const { smaller } = useBreakpoints(screens)
// const DESKTOP_LAYOUT = 'md' // FIXME This is suppose to be the same value as in the tailwind config
// const isMobile = smaller(DESKTOP_LAYOUT)
</script>

<template>
Expand All @@ -37,27 +28,27 @@ const isMobile = smaller(DESKTOP_LAYOUT)
@pointerdown.capture.stop.prevent
@dblclick.capture.stop.prevent
>
<BasicInfoCryptocity
:cryptocity="cryptocity"
:icon="isMobile ? InfoIcon : ChevronDownIcon"
:class="{
'[&_[data-cryptocity-trigger]]:rotate-180': showDescription,
}"
@icon-click.stop="$emit('iconClick', $event)"
/>
<div class="flex items-center gap-x-2">
<CryptocityIcon class="w-[31px] h-[27px]" />
<div>
<h3 class="text-base text-space">cryptocity.name</h3>
<span class="text-sm text-space/60">{{ $tc('{count} locations', -1) }}</span>
</div>
<!-- <component :is="icon" class="text-[#C9CAD3] p-1 w-6 h-6 ml-auto transition hover:bg-space/10 focus-visible:bg-space/10 rounded-full" data-cryptocity-trigger @click="$emit('iconClick', $event)" /> -->
</div>
<div
class="grid transition-[grid-template-rows] grid-cols-[mEin(290px,calc(100vh-48px))] duration-300" :class="{
'grid-rows-[0fr]': !showDescription,
'grid-rows-[1fr] mt-3': showDescription,
}"
>
<div class="overflow-hidden delay-[2s]" :class="{ '[&>*]:opacity-0': !showDescription, '[&>*]:opacity-100': showDescription }">
<p v-for="(p, i) in cryptocity.description" :key="i" class="pt-2 text-xs text-space/70">{{ p }}</p>
<!-- <p v-for="(p, i) in cryptocity.description" :key="i" class="pt-2 text-xs text-space/70">{{ p }}</p>
<Button :href="cryptocity.url" class="mt-2 !p-0 !h-auto" bg-color="transparent" size="sm" text-color="sky">
<template #label>
{{ cryptocity.url }}
</template>
</Button>
</Button> -->
</div>
</div>
</div>
Expand Down
13 changes: 13 additions & 0 deletions src/components/elements/Controls.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,19 @@ async function setBrowserPosition() {

<template>
<div class="flex flex-col gap-y-4">
<!-- <PopoverRoot v-if="showCryptocity">
<PopoverTrigger class="!w-8 !h-8 shadow bg-white rounded-full p-1.5" :aria-label="$t('Information about this Cryptocity')"><CryptocityIcon /></PopoverTrigger>
<PopoverPortal>
<PopoverContent
side="bottom" :side-offset="-32" class="will-change-[transform,opacity] animate-slideUpAndFade mr-6"
@close-auto-focus.prevent @interact-outside.prevent
>
<PopoverClose class="rounded-full outline-none cursor-default" :aria-label="$t('Close')"><CrossIcon /></PopoverClose>
<CryptocityCard :cryptocity="cryptocity!" :show-description="true" />
</PopoverContent>
</PopoverPortal>
</PopoverRoot> -->

<Button
v-if="browserPositionIsSupported"
class="!w-8 !h-8 shadow"
Expand Down
2 changes: 1 addition & 1 deletion src/components/elements/TheMapInstance.vue
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ const isDark = useDark()

<GoogleMap
ref="mapInstance" v-bind="$attrs" :api-key="GOOGLE_MAP_KEY" :language="i18n.locale" disable-default-ui :gesture-handling="mapGestureBehaviour"
:keyboard-shortcuts="false" :styles="googleMapStyles" :max-zoom="21" :min-zoom="3" :restriction="restriction" :clickable-icons="false"
:keyboard-shortcuts="false" :styles="googleMapStyles" :max-zoom="21" :min-zoom="3" :restriction="restriction" :clickable-icons="false" :libraries="['places', 'maps'] as unknown as ['places']"
@idle.once="() => { mapLoaded = true; setInitialMapPosition() }"
>
<MapMarkers />
Expand Down
Loading

0 comments on commit 0bea2a7

Please sign in to comment.