diff --git a/src/components/home/Post.tsx b/src/app/(default)/components/Post.tsx similarity index 90% rename from src/components/home/Post.tsx rename to src/app/(default)/components/Post.tsx index 7699adb02..177c28927 100644 --- a/src/components/home/Post.tsx +++ b/src/app/(default)/components/Post.tsx @@ -1,12 +1,12 @@ import Link from 'next/link' import React from 'react' -import { MediaType } from '../../js/types' +import { MediaType } from '../../../js/types' import { UserCircleIcon, TagIcon } from '@heroicons/react/24/outline' -import { getUploadDateSummary, urlResolver } from '../../js/utils' -import { ATagWrapper } from '../Utils' +import { getUploadDateSummary, urlResolver } from '../../../js/utils' +import { ATagWrapper } from '../../../components/Utils' export interface PostBodyProps { destUrl: string diff --git a/src/components/home/RecentMediaCard.tsx b/src/app/(default)/components/RecentMediaCard.tsx similarity index 85% rename from src/components/home/RecentMediaCard.tsx rename to src/app/(default)/components/RecentMediaCard.tsx index dd0e4d21d..ce18da532 100644 --- a/src/components/home/RecentMediaCard.tsx +++ b/src/app/(default)/components/RecentMediaCard.tsx @@ -2,13 +2,13 @@ import { useState } from 'react' import Image from 'next/image' import clx from 'classnames' -import Card from '../ui/Card/Card' -import TagList from '../media/TagList' -import { MediaWithTags } from '../../js/types' -import { getUploadDateSummary } from '../../js/utils' +import Card from '../../../components/ui/Card/Card' +import TagList from '../../../components/media/TagList' +import { MediaWithTags } from '../../../js/types' +import { getUploadDateSummary } from '../../../js/utils' import { PostHeader } from './Post' -import { resolver as urlResolver } from '../media/Tag' -import { ATagWrapper } from '../Utils' +import { resolver as urlResolver } from '../../../components/media/Tag' +import { ATagWrapper } from '../../../components/Utils' const MOBILE_IMAGE_MAX_WIDITH = 600 diff --git a/src/app/(default)/components/RecentTags.tsx b/src/app/(default)/components/RecentTags.tsx index ef51a2230..287624f6d 100644 --- a/src/app/(default)/components/RecentTags.tsx +++ b/src/app/(default)/components/RecentTags.tsx @@ -1,4 +1,4 @@ -import { RecentImageCard } from '@/components/home/RecentMediaCard' +import { RecentImageCard } from '@/app/(default)/components/RecentMediaCard' import { SectionContainer } from './ui/SectionContainer' import { getMediaForFeedSC } from '@/js/graphql/gql/serverApi' diff --git a/src/components/home/DenseAreas.tsx b/src/components/home/DenseAreas.tsx deleted file mode 100644 index e5b05047c..000000000 --- a/src/components/home/DenseAreas.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import FeatureCard from '../ui/FeatureCard' -import { AreaType } from '../../js/types' - -export interface ExploreProps { - areas: AreaType[] -} -export default function Explore ({ areas }: ExploreProps): JSX.Element { - return ( -
- {areas.map(area => )} -
- ) -} diff --git a/src/components/home/Map.tsx b/src/components/home/Map.tsx deleted file mode 100644 index b4b5df9c8..000000000 --- a/src/components/home/Map.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { useEffect, useState, useRef } from 'react' -import { featureCollection, Feature, Point } from '@turf/helpers' -import NProgress from 'nprogress' -import { debounce } from 'underscore' - -import useAutoSizing from '../../js/hooks/finder/useMapAutoSizing' -import BaseMap from '../maps/BaseMap' -import HeatmapLayer from '../maps/HeatmapLayer' -import { MarkerLayer2 } from '../maps/MarkerLayer' -import { geojsonifyCrag } from '../../js/stores/util' -import { getCragsWithinNicely } from '../../js/graphql/api' -import { XViewStateType } from '../../js/types' - -const mapElementId = 'global-map' -export default function Map (): JSX.Element { - const [viewstate, height, setViewState] = useAutoSizing({ geojson: null, elementId: mapElementId }) - - const [geojson, setData] = useState>>([]) - - const lastUpdatePosition = useRef({ longitude: 0, latitude: 0, zoom: 0 }) - - useEffect(() => { - if (viewstate.bbox[0] === 0 && viewstate.bbox[1] === 0) return - - const shouldFetchData = checkIfShouldFetchData(viewstate, lastUpdatePosition) - - if (shouldFetchData) { - lastUpdatePosition.current = { longitude: viewstate.longitude, latitude: viewstate.latitude, zoom: viewstate.zoom } - progressStart() - void getCragsWithinNicely({ bbox: viewstate.bbox, zoom: viewstate.zoom }).then( - data => { - if (data?.length > 0) { - setData(data.map(crag => geojsonifyCrag(crag, false))) - progressStop() - } - } - ) - } - }, [viewstate]) - - return ( -
- - - - - -
- ) -} - -const progressStart = debounce(NProgress.start, 500) - -const progressStop = debounce(NProgress.done, 500) - -function checkIfShouldFetchData (viewstate: XViewStateType, lastUpdatePosition: any): boolean { - return (Math.abs(viewstate.latitude - lastUpdatePosition.current.latitude) >= 0.1 || - Math.abs(viewstate.longitude - lastUpdatePosition.current.longitude) > 0.2 || - Math.abs(viewstate.zoom - lastUpdatePosition.current.zoom) > 1) -} diff --git a/src/components/home/RecentMedia.tsx b/src/components/home/RecentMedia.tsx deleted file mode 100644 index 955683103..000000000 --- a/src/components/home/RecentMedia.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { MediaWithTags } from '../../js/types' -import { RecentImageCard } from './RecentMediaCard' - -export interface RecentTagsProps { - recentMediaWithTags: MediaWithTags[] -} - -export default function RecentTags ({ - recentMediaWithTags -}: RecentTagsProps): JSX.Element { - return ( - <> -
- { - recentMediaWithTags.map(media => { - const { mediaUrl } = media - return ( -
- -
- ) - } - ) - } -
-
- All photos are copyrighted by their respective owners. All Rights - Reserved. -
- - ) -} diff --git a/src/components/maps/MarkerLayer.tsx b/src/components/maps/MarkerLayer.tsx index 203fe59b1..e2b615c49 100644 --- a/src/components/maps/MarkerLayer.tsx +++ b/src/components/maps/MarkerLayer.tsx @@ -56,62 +56,6 @@ const closeupLayerStyle: LayerProps = { } } -const closeupLayerStyleBright: LayerProps = { - ...closeupLayerStyle, - id: 'all-markers-bright', - minzoom: 11, - filter: ['==', 'leaf', true], - layout: { - ...closeupLayerStyle.layout, - 'text-size': ['interpolate', ['linear'], ['zoom'], 8, 14, 14, 15] - }, - paint: { - 'icon-color': '#ffffff', - 'text-halo-blur': 4, - 'text-halo-width': 4, - 'text-color': '#000000', - 'text-halo-color': '#eaeaea' - } -} - -const largeAreaLayerStyleBright: LayerProps = { - ...closeupLayerStyle, - id: 'area-markers-bright', - minzoom: 8, - maxzoom: 14, - filter: ['all', ['!=', 'leaf', true], ['>', 'totalClimbs', 30], ['<', 'totalClimbs', 500], ['>', 'density', 0.35]], - layout: { - ...closeupLayerStyle.layout, - 'text-size': ['interpolate', ['linear'], ['zoom'], 8, 14, 14, 15] - }, - paint: { - 'icon-color': '#ffffff', - 'text-halo-blur': 4, - 'text-halo-width': 4, - 'text-color': '#000000', - 'text-halo-color': '#eaeaea' - } -} - -const destinationLayerStyleBright: LayerProps = { - ...closeupLayerStyle, - id: 'destination-markers-bright', - minzoom: 5, - maxzoom: 8, - filter: ['all', ['!=', 'leaf', true], ['>=', 'totalClimbs', 300], ['>', 'density', 0.5]], - layout: { - ...closeupLayerStyle.layout, - 'text-size': ['interpolate', ['linear'], ['zoom'], 8, 14, 14, 15] - }, - paint: { - 'icon-color': '#ffffff', - 'text-halo-blur': 4, - 'text-halo-width': 4, - 'text-color': '#000000', - 'text-halo-color': '#eaeaea' - } -} - interface MarkerLayerProps { geojson: FeatureCollection | undefined } @@ -133,21 +77,6 @@ export default function MarkerLayer ({ geojson }: MarkerLayerProps): JSX.Element ) } -export function MarkerLayer2 ({ geojson }: MarkerLayerProps): JSX.Element | null { - if (geojson == null) return null - return ( - - - - - - ) -} - // Important! Pass these IDs to Mapbox GL so that onClick/onHover receives // the active Geojson object export const InteractiveLayerIDs = [layerStyle.id, closeupLayerStyle.id] as string[] diff --git a/src/components/ui/FeatureCard/FeatureCard.tsx b/src/components/ui/FeatureCard/FeatureCard.tsx deleted file mode 100644 index 2e76b5ef8..000000000 --- a/src/components/ui/FeatureCard/FeatureCard.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import React from 'react' -import Link from 'next/link' -import { shuffle } from 'underscore' - -import { AggregateType, AreaType, CountByDisciplineType, DisciplineStatsType } from '../../../js/types' -import { sanitizeName } from '../../../js/utils' -import { FeatureImage } from './FeatureImage' -import { CLIENT_CONFIG } from '../../../js/configs/clientConfig' - -function FeatureCard ({ area }: { area: AreaType }): JSX.Element | null { - const { areaName, pathTokens, aggregate, metadata, totalClimbs, media } = area - - if (media == null || media.length === 0) return null - - const imageUrl = `${CLIENT_CONFIG.CDN_BASE_URL}${shuffle(media)[0].mediaUrl}?h=300&q=90` - - const image = { - url: imageUrl, - license: 'All Rights Reserved', - creator: '', - license_url: '', - attribution: '' - } - - /** - * Turn count-by-discipline to tiny tags. - * Only consider crags with more 5 climbs for a given discipline. - * @param aggregateTypes T - * @returns - */ - function formatClimbingTypes (aggregate: AggregateType): JSX.Element { - const aggByDiscipline: CountByDisciplineType = aggregate.byDiscipline - return ( - <> - { - Object.keys(aggregate.byDiscipline).map((key: string) => { - // @ts-expect-error - if ((aggByDiscipline?.[key] as DisciplineStatsType)?.total > 5) { - return {key} - } - return null - }) - } - - ) - } - - const attribution = image.attribution ?? image.creator ?? '' - - return ( -
- - - -
-

- {sanitizeName(areaName)} -

-
{totalClimbs} Climbs
-
{formatClimbingTypes(aggregate)}
-
{pathTokens.slice(1).join(' / ')}
- {attribution !== '' &&
Image By: {attribution}
} -
- - -
- ) -} - -export default FeatureCard diff --git a/src/components/ui/FeatureCard/FeatureImage.tsx b/src/components/ui/FeatureCard/FeatureImage.tsx deleted file mode 100644 index 1ad27ed7f..000000000 --- a/src/components/ui/FeatureCard/FeatureImage.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import Image from 'next/image' -import { PhotoIcon } from '@heroicons/react/24/outline' -import { OpenverseImage } from '.' - -export function DefaultImage (): JSX.Element { - return ( -
- ) -} - -export function FeatureImage ({ image }: { image: OpenverseImage }): JSX.Element { - return ( -
-
{image.url == null - ? ( -
-
-
- ) - : } -
-
- ) -} diff --git a/src/components/ui/FeatureCard/index.ts b/src/components/ui/FeatureCard/index.ts deleted file mode 100644 index afcdada89..000000000 --- a/src/components/ui/FeatureCard/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -import FeatureCard from './FeatureCard' -export default FeatureCard - -export interface OpenverseImage { - creator: string | undefined - url: string - attribution: string | undefined - license: string - license_url: string -} - -export interface OpenverseResponse { - result_count: number - page_count: number - page_size: number - page: number - results: OpenverseImage[] -} diff --git a/src/components/ui/TabsTrigger.tsx b/src/components/ui/TabsTrigger.tsx deleted file mode 100644 index 85fc02099..000000000 --- a/src/components/ui/TabsTrigger.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import * as Tabs from '@radix-ui/react-tabs' -import classNames from 'classnames' - -interface TabsTriggerProps { - tabKey: string - activeKey: string - icon: JSX.Element - label: string - hidden?: boolean -} -export default function TabsTrigger ({ tabKey, activeKey, icon, label, hidden = false }: TabsTriggerProps): JSX.Element { - return ( - - ) -} diff --git a/src/js/graphql/api.ts b/src/js/graphql/api.ts index 9c2ea4b3e..efc52e1d6 100644 --- a/src/js/graphql/api.ts +++ b/src/js/graphql/api.ts @@ -1,11 +1,9 @@ import { gql } from '@apollo/client' -import AwesomeDebouncePromise from 'awesome-debounce-promise' -import { AreaType, ClimbType, TickType, MediaByUsers, CountrySummaryType, MediaWithTags } from '../types' +import { AreaType, ClimbType, TickType, CountrySummaryType, MediaWithTags } from '../types' import { graphqlClient } from './Client' -import { CORE_CRAG_FIELDS, QUERY_CRAGS_WITHIN, QUERY_TICKS_BY_USER_AND_CLIMB, QUERY_TICKS_BY_USER, QUERY_ALL_COUNTRIES } from './gql/fragments' -import { QUERY_MEDIA_FOR_FEED } from './gql/tags' +import { CORE_CRAG_FIELDS, QUERY_TICKS_BY_USER_AND_CLIMB, QUERY_TICKS_BY_USER, QUERY_ALL_COUNTRIES } from './gql/fragments' import { QUERY_USER_MEDIA } from './gql/users' import { QUERY_CLIMB_BY_ID } from './gql/climbById' @@ -85,58 +83,6 @@ export const getAreaByUUID = (uuid: string): AreaType | null => { return null } -export const getMediaForFeed = async (maxUsers: number, maxFiles: number): Promise => { - try { - const rs = await graphqlClient.query<{ getMediaForFeed: MediaByUsers[] }>({ - query: QUERY_MEDIA_FOR_FEED, - variables: { - maxUsers, - maxFiles - } - // fetchPolicy: 'network-only' - }) - - if (Array.isArray(rs.data?.getMediaForFeed)) { - return rs.data?.getMediaForFeed - } - console.log('WARNING: getMediaForFeed() returns non-array data') - return [] - } catch (e) { - console.log('####### getMediaForFeed() error', e) - } - return [] -} - -interface GetCragsWithinProps { - bbox: number[] - zoom: number -} -export const getCragsWithin = async ({ bbox, zoom }: GetCragsWithinProps): Promise => { - try { - const rs = await graphqlClient.query<{ cragsWithin: AreaType[] }>({ - query: QUERY_CRAGS_WITHIN, - variables: { - filter: { - bbox, - zoom - } - }, - notifyOnNetworkStatusChange: true - }) - - if (Array.isArray(rs.data?.cragsWithin)) { - return rs.data?.cragsWithin ?? [] - } - console.log('WARNING: cragsWithin() returns non-array data') - return [] - } catch (e) { - console.log('cragsWithin() error', e) - } - return [] -} - -export const getCragsWithinNicely = AwesomeDebouncePromise(getCragsWithin, 1000) - export const getTicksByUserAndClimb = async (climbId: string, userId: string): Promise => { try { const res = await graphqlClient.query<{ userTicksByClimbId: TickType[] }>({ diff --git a/src/pages/classic/index.tsx b/src/pages/classic/index.tsx deleted file mode 100644 index 144c01764..000000000 --- a/src/pages/classic/index.tsx +++ /dev/null @@ -1,236 +0,0 @@ -import { useState, useEffect } from 'react' -import type { NextPage, GetStaticProps } from 'next' -import dynamic from 'next/dynamic' -import { useRouter } from 'next/router' -import * as Tabs from '@radix-ui/react-tabs' -import { gql } from '@apollo/client' -import { TagIcon, LightBulbIcon, MapPinIcon, PencilIcon, ArrowTrendingUpIcon } from '@heroicons/react/24/outline' -import classNames from 'classnames' - -import Layout from '@/components/layout' -import SeoTags from '@/components/SeoTags' -import { graphqlClient } from '@/js/graphql/Client' -import { getMediaForFeed } from '@/js/graphql/api' -import { IndexResponseType, MediaWithTags } from '@/js/types' -import { ExploreProps } from '@/components/home/DenseAreas' -import TabsTrigger from '@/components/ui/TabsTrigger' -import RecentTaggedMedia from '@/components/home/RecentMedia' -import { FRAGMENT_MEDIA_WITH_TAGS } from '@/js/graphql/gql/tags' - -const allowedViews = ['explore', 'newTags', 'map', 'edit', 'pulse'] - -interface HomePageType { - exploreData: IndexResponseType - recentMediaWithTags: MediaWithTags[] -} - -const Home: NextPage = ({ exploreData, recentMediaWithTags }) => { - const router = useRouter() - const [activeTab, setTab] = useState('') - const { areas } = exploreData - - useEffect(() => { - if (activeTab !== '' && allowedViews.includes(activeTab)) { - if (activeTab === 'edit') { - void router.replace('/edit') - return - } - if (activeTab === 'pulse') { - void router.replace('/pulse') - return - } - const query = router.query - query.v = activeTab - const queryString = Object.keys(query).map((key) => { - return encodeURIComponent(key) + '=' + encodeURIComponent(query[key] as string) - }).join('&') - - void router.push(`/classic?${queryString}`, undefined, { shallow: true }) - } - }, [activeTab]) - - useEffect(() => { - if (router.isReady) { - const urlViewParam = router.query.v - let tabToSet = 'newTags' - if (typeof urlViewParam === 'string' && allowedViews.includes(urlViewParam)) { - tabToSet = urlViewParam - } - setTab(tabToSet) - } - }, [router.isReady, router.query]) - - return ( - <> - - -
- - - } - label='Edit' - /> - } - label='New tags' - /> - } - label='Popular' - /> - } - label='Map' - /> - } - label='Pulse' - /> - - - - - - - - - - - -
-
- - ) -} - -export const getStaticProps: GetStaticProps = async ({ params }) => { - const query = gql` - ${FRAGMENT_MEDIA_WITH_TAGS} - query UsaAreas( $filter: Filter) { - areas(filter: $filter, sort: { totalClimbs: -1 }) { - id - uuid - areaName - pathTokens - totalClimbs - density - aggregate { - byDiscipline { - sport { - total - } - trad { - total - } - boulder { - total - } - tr { - total - } - alpine { - total - } - mixed { - total - } - aid { - total - } - } - } - metadata { - lat - lng - areaId - } - media { - ... MediaWithTagsFields - } - } - }` - - const rs = await graphqlClient.query({ - query, - variables: { - filter: { - field_compare: [{ - field: 'totalClimbs', - num: 400, - comparison: 'gt' - }, { - field: 'density', - num: 0.5, - comparison: 'gt' - }] - } - } - }) - - const recentTagsByUsers = await getMediaForFeed(20, 3) - - const testAreaIds = Array.from(new Set((process.env.NEXT_PUBLIC_TEST_AREA_IDS ?? '').split(','))) - - const mediaWithTags = recentTagsByUsers.flatMap(entry => entry.mediaWithTags) - - const recentTags = mediaWithTags.filter(tag => { - return tag.entityTags.some(entityTag => { - return testAreaIds.every(testId => { - const regex = new RegExp(testId, 'g') - return !regex.test(entityTag.ancestors) - }) - }) - }) - return { - props: { - exploreData: rs.data, - recentMediaWithTags: recentTags - }, - revalidate: 60 - } -} -export default Home - -const DynamicDenseAreas = dynamic( - async () => - await import('@/components/home/DenseAreas').then( - module => module.default), { ssr: false } -) - -const DynamicMap = dynamic( - async () => - await import('@/components/home/Map').then( - module => module.default), { ssr: false } -)