From b195ae013fd807e3f678bb68c2272f05407d28cf Mon Sep 17 00:00:00 2001 From: secondl1ght Date: Tue, 12 Dec 2023 23:15:02 -0700 Subject: [PATCH] improve loading comm and tagger pages --- src/components/LatestTagger.svelte | 2 +- src/components/ProfileStat.svelte | 8 +- src/lib/types.ts | 41 +- src/routes/activity/+page.svelte | 25 +- src/routes/community/[area]/+layout.svelte | 41 - src/routes/community/[area]/+page.svelte | 1349 +++++++++++--------- src/routes/tagger/[id]/+layout.svelte | 26 - src/routes/tagger/[id]/+page.svelte | 629 +++++---- 8 files changed, 1165 insertions(+), 956 deletions(-) delete mode 100644 src/routes/community/[area]/+layout.svelte delete mode 100644 src/routes/tagger/[id]/+layout.svelte diff --git a/src/components/LatestTagger.svelte b/src/components/LatestTagger.svelte index 92015378..804f6356 100644 --- a/src/components/LatestTagger.svelte +++ b/src/components/LatestTagger.svelte @@ -5,7 +5,7 @@ export let location: string; export let action: EventType; - export let user: User | ''; + export let user: User | undefined = undefined; export let time: string; export let latest: boolean; export let merchantId: string; diff --git a/src/components/ProfileStat.svelte b/src/components/ProfileStat.svelte index 4d1c034e..6d15ebbc 100644 --- a/src/components/ProfileStat.svelte +++ b/src/components/ProfileStat.svelte @@ -2,9 +2,9 @@ import tippy from 'tippy.js'; export let title: string; - export let stat: number; - export let percent: string; - export let border: string; + export let stat: number | undefined; + export let percent: string | undefined = undefined; + export let border: string | undefined = undefined; export let tooltip: undefined | string = undefined; let tooltipElement: HTMLButtonElement; @@ -27,7 +27,7 @@
- {#if stat >= 0} + {#if stat !== undefined} {stat} {#if percent} ({percent}%) diff --git a/src/lib/types.ts b/src/lib/types.ts index 5603a755..eb1521b2 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -15,7 +15,7 @@ export type Area = { export type AreaTags = { type: 'community' | 'country'; name: string; - continent: 'africa' | 'asia' | 'europe' | 'north-america' | 'oceania' | 'south-america'; + continent: Continents; url_alias: string; geo_json: GeoJSON; ['icon:square']: string; @@ -50,6 +50,14 @@ export type AreaTags = { ['box:west']?: string; }; +export type Continents = + | 'africa' + | 'asia' + | 'europe' + | 'north-america' + | 'oceania' + | 'south-america'; + export type Element = { id: string; osm_json: ElementOSM; @@ -94,25 +102,27 @@ export type Report = { id: number; area_id: string; date: string; - tags: { - total_elements: number; - total_elements_onchain: number; - total_elements_lightning: number; - total_elements_lightning_contactless: number; - total_atms: number; - up_to_date_elements: number; - up_to_date_percent: number; - outdated_elements: number; - legacy_elements: number; - avg_verification_date: string; - grade: Grade; - }; + tags: ReportTags; created_at: string; updated_at: string; deleted_at: string; }; -export type Grade = 0 | 1 | 2 | 3 | 4 | 5; +export type ReportTags = { + total_elements: number; + total_elements_onchain: number; + total_elements_lightning: number; + total_elements_lightning_contactless: number; + total_atms: number; + up_to_date_elements: number; + up_to_date_percent: number; + outdated_elements: number; + legacy_elements: number; + avg_verification_date: string; + grade: Grade; +}; + +export type Grade = 1 | 2 | 3 | 4 | 5; export type User = { id: number; @@ -203,6 +213,7 @@ export enum TipType { export interface ActivityEvent extends Event { location: string; merchantId: string; + tagger?: User; } // misc diff --git a/src/routes/activity/+page.svelte b/src/routes/activity/+page.svelte index 85d5bd99..50bb81a3 100644 --- a/src/routes/activity/+page.svelte +++ b/src/routes/activity/+page.svelte @@ -30,6 +30,15 @@ let elementsLoading: boolean; let supertaggers: ActivityEvent[]; + const findUser = (tagger: Event) => { + let foundUser = $users.find((user) => user.id == tagger['user_id']); + if (foundUser) { + return foundUser; + } else { + return undefined; + } + }; + const supertaggerSync = ( status: boolean, users: User[], @@ -53,10 +62,13 @@ ? elementMatch['osm_json'].tags.name : undefined; + let tagger = findUser(event); + supertaggers.push({ ...event, location: location || 'Unnamed element', - merchantId: elementMatch.id + merchantId: elementMatch.id, + tagger }); } }); @@ -69,15 +81,6 @@ $: supertaggerSync($syncStatus, $users, $events, $elements); $: latestTaggers = supertaggers && supertaggers.length && !elementsLoading ? true : false; - - const findUser = (tagger: ActivityEvent) => { - let foundUser = $users.find((user) => user.id == tagger['user_id']); - if (foundUser) { - return foundUser; - } else { - return ''; - } - }; @@ -131,7 +134,7 @@ - import { page } from '$app/stores'; - import { LoadingSplash } from '$lib/comp'; - import { - areaError, - areas, - elementError, - elements, - eventError, - events, - reportError, - reports, - userError, - users - } from '$lib/store'; - import { errToast } from '$lib/utils'; - - // alert for user errors - $: $userError && errToast($userError); - // alert for event errors - $: $eventError && errToast($eventError); - // alert for element errors - $: $elementError && errToast($elementError); - // alert for area errors - $: $areaError && errToast($areaError); - // alert for report errors - $: $reportError && errToast($reportError); - - - - {$page.data.name} - BTC Map Community - - - - - -{#if $users && $users.length && $events && $events.length && $elements && $elements.length && $areas && $areas.length && $reports && $reports.length} - -{:else} - -{/if} diff --git a/src/routes/community/[area]/+page.svelte b/src/routes/community/[area]/+page.svelte index 5e7b7baf..0b00a951 100644 --- a/src/routes/community/[area]/+page.svelte +++ b/src/routes/community/[area]/+page.svelte @@ -3,6 +3,7 @@ import { browser } from '$app/environment'; import { goto } from '$app/navigation'; + import { page } from '$app/stores'; import { Footer, Header, @@ -31,8 +32,30 @@ toggleMapButtons, verifiedArr } from '$lib/map/setup'; - import { areas, elements, events, reports, theme, users } from '$lib/store'; - import { TipType, type ActivityEvent, type BaseMaps, type User } from '$lib/types.js'; + import { + areaError, + areas, + elementError, + elements, + eventError, + events, + reportError, + reports, + theme, + userError, + users + } from '$lib/store'; + import { + TipType, + type ActivityEvent, + type BaseMaps, + type Continents, + type DomEventType, + type Event, + type Grade, + type Leaflet, + type User + } from '$lib/types.js'; import { detectTheme, errToast, updateChartThemes } from '$lib/utils'; // @ts-expect-error import rewind from '@mapbox/geojson-rewind'; @@ -42,553 +65,462 @@ import { onDestroy, onMount } from 'svelte'; import tippy from 'tippy.js'; - const communityFound = $areas.find( - (area) => - area.id == data.id && - area.tags.type === 'community' && - ((area.tags['box:east'] && - area.tags['box:north'] && - area.tags['box:south'] && - area.tags['box:west']) || - area.tags.geo_json) && - area.tags.name && - area.tags['icon:square'] && - area.tags.continent && - Object.keys(area.tags).find((key) => key.includes('contact')) - ); - - if (!communityFound) { - console.log('Could not find community, please try again or contact BTC Map.'); - goto('/404'); - } - - let communityReports = $reports - .filter((report) => report.area_id === data.id) - .sort((a, b) => Date.parse(b['created_at']) - Date.parse(a['created_at'])); - - if (!communityReports.length) { - console.log( - 'Could not find any community reports, please try again tomorrow or contact BTC Map.' + // alert for user errors + $: $userError && errToast($userError); + // alert for event errors + $: $eventError && errToast($eventError); + // alert for element errors + $: $elementError && errToast($elementError); + // alert for area errors + $: $areaError && errToast($areaError); + // alert for report errors + $: $reportError && errToast($reportError); + + let initialRenderComplete = false; + let dataInitialized = false; + + const initializeData = () => { + if (dataInitialized) return; + + const communityFound = $areas.find( + (area) => + area.id == data.id && + area.tags.type === 'community' && + ((area.tags['box:east'] && + area.tags['box:north'] && + area.tags['box:south'] && + area.tags['box:west']) || + area.tags.geo_json) && + area.tags.name && + area.tags['icon:square'] && + area.tags.continent && + Object.keys(area.tags).find((key) => key.includes('contact')) ); - goto('/404'); - } - - const community = communityFound?.tags; - - const ticketTypes = ['Add', 'Verify']; - let showType = 'Add'; - - const tickets = data.tickets; - const ticketError = tickets === 'error' ? true : false; - - $: ticketError && errToast('Could not load open tickets, please try again or contact BTC Map.'); - const add = - tickets && tickets.length && !ticketError - ? tickets.filter((issue: any) => - issue.labels.find((label: any) => label.name === 'location-submission') - ) - : []; - const verify = - tickets && tickets.length && !ticketError - ? tickets.filter((issue: any) => - issue.labels.find((label: any) => label.name === 'verify-submission') - ) - : []; - - const totalTickets = add.length + verify.length; - - let avatar = community?.['icon:square']; - let name = data.name; - let org = community?.organization; - let sponsor = community?.sponsor; - let continent = community?.continent; - let website = community?.['contact:website']; - let email = community?.['contact:email']; - let nostr = community?.['contact:nostr']; - let twitter = community?.['contact:twitter']; - let secondTwitter = community?.['contact:second_twitter']; - let meetup = community?.['contact:meetup']; - let eventbrite = community?.['contact:eventbrite']; - let telegram = community?.['contact:telegram']; - let discord = community?.['contact:discord']; - let youtube = community?.['contact:youtube']; - let github = community?.['contact:github']; - let reddit = community?.['contact:reddit']; - let instagram = community?.['contact:instagram']; - let whatsapp = community?.['contact:whatsapp']; - let facebook = community?.['contact:facebook']; - let linkedin = community?.['contact:linkedin']; - let rss = community?.['contact:rss']; - let signal = community?.['contact:signal']; - $: lightning = - (community?.['tips:lightning_address'] && { - destination: community?.['tips:lightning_address'], - type: TipType.Address - }) || - (community?.['tips:url'] && { destination: community?.['tips:url'], type: TipType.Url }); - - let latestReport = communityReports[0].tags; - let total = latestReport.total_elements || 0; - let upToDate = latestReport.up_to_date_elements || 0; - let outdated = latestReport.outdated_elements || 0; - let legacy = latestReport.legacy_elements || 0; - let grade = latestReport.grade || 0; + if (!communityFound) { + console.log('Could not find community, please try again or contact BTC Map.'); + goto('/404'); + return; + } - let gradeTooltip: HTMLButtonElement; + const communityReports = $reports + .filter((report) => report.area_id === data.id) + .sort((a, b) => Date.parse(b['created_at']) - Date.parse(a['created_at'])); - $: gradeTooltip && - tippy([gradeTooltip], { - content: ` - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Up-To-DateGrade
95-100%5 Star
75-95%4 Star
50-75%3 Star
25-50%2 Star
0-25%1 Star
`, - allowHTML: true - }); + if (!communityReports.length) { + console.log( + 'Could not find any community reports, please try again tomorrow or contact BTC Map.' + ); + goto('/404'); + return; + } - let upToDatePercent = new Intl.NumberFormat('en-US').format( - Number((upToDate / (total / 100)).toFixed(0)) - ); + const community = communityFound.tags; + + avatar = community['icon:square']; + org = community.organization; + sponsor = community.sponsor; + continent = community.continent; + website = community['contact:website']; + email = community['contact:email']; + nostr = community['contact:nostr']; + twitter = community['contact:twitter']; + secondTwitter = community['contact:second_twitter']; + meetup = community['contact:meetup']; + eventbrite = community['contact:eventbrite']; + telegram = community['contact:telegram']; + discord = community['contact:discord']; + youtube = community['contact:youtube']; + github = community['contact:github']; + reddit = community['contact:reddit']; + instagram = community['contact:instagram']; + whatsapp = community['contact:whatsapp']; + facebook = community['contact:facebook']; + linkedin = community['contact:linkedin']; + rss = community['contact:rss']; + signal = community['contact:signal']; + + if (community['tips:lightning_address']) { + lightning = { + destination: community['tips:lightning_address'], + type: TipType.Address + }; + } else if (community['tips:url']) { + lightning = { destination: community['tips:url'], type: TipType.Url }; + } - let outdatedPercent = new Intl.NumberFormat('en-US').format( - Number((outdated / (total / 100)).toFixed(0)) - ); + const latestReport = communityReports[0].tags; + total = latestReport.total_elements; + upToDate = latestReport.up_to_date_elements; + outdated = latestReport.outdated_elements; + legacy = latestReport.legacy_elements; + grade = latestReport.grade; - let legacyPercent = new Intl.NumberFormat('en-US').format( - Number((legacy / (total / 100)).toFixed(0)) - ); + upToDatePercent = new Intl.NumberFormat('en-US').format( + Number((upToDate / (total / 100)).toFixed(0)) + ); - let updatedChartCanvas: HTMLCanvasElement; - // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars - let updatedChart; + outdatedPercent = new Intl.NumberFormat('en-US').format( + Number((outdated / (total / 100)).toFixed(0)) + ); - let loading = true; + legacyPercent = new Intl.NumberFormat('en-US').format( + Number((legacy / (total / 100)).toFixed(0)) + ); - let rewoundPoly = community?.geo_json ? rewind(community.geo_json, true) : undefined; + const rewoundPoly = rewind(community.geo_json, true); - // filter elements within community - let filteredElements = $elements.filter((element) => { - let lat = latCalc(element['osm_json']); - let long = longCalc(element['osm_json']); + // filter elements within community + const filteredElements = $elements.filter((element) => { + let lat = latCalc(element['osm_json']); + let long = longCalc(element['osm_json']); - if (community?.geo_json) { - if (geoContains(rewoundPoly, [long, lat])) { + if (community.geo_json) { + if (geoContains(rewoundPoly, [long, lat])) { + return true; + } else { + return false; + } + } else if ( + lat >= Number(community['box:south']) && + lat <= Number(community['box:north']) && + long >= Number(community['box:west']) && + long <= Number(community['box:east']) + ) { return true; } else { return false; } - } else if ( - lat >= Number(community?.['box:south']) && - lat <= Number(community?.['box:north']) && - long >= Number(community?.['box:west']) && - long <= Number(community?.['box:east']) - ) { - return true; - } else { - return false; - } - }); - - let hideArrow = false; - let activityDiv; - let eventElements: ActivityEvent[] = []; - - let communityEvents = $events.filter((event) => - filteredElements.find((element) => element.id === event.element_id) - ); + }); - communityEvents.sort((a, b) => Date.parse(b['created_at']) - Date.parse(a['created_at'])); + const communityEvents = $events.filter((event) => + filteredElements.find((element) => element.id === event.element_id) + ); - communityEvents.forEach((event) => { - let elementMatch = filteredElements.find((element) => element.id === event['element_id']); + communityEvents.sort((a, b) => Date.parse(b['created_at']) - Date.parse(a['created_at'])); - if (elementMatch) { - let location = - elementMatch['osm_json'].tags && elementMatch['osm_json'].tags.name - ? elementMatch['osm_json'].tags.name - : undefined; + const findUser = (tagger: Event) => { + let foundUser = $users.find((user) => user.id == tagger['user_id']); - eventElements.push({ - ...event, - location: location || 'Unnamed element', - merchantId: elementMatch.id - }); - } - }); + if (foundUser) { + if (!taggers.find((tagger) => tagger.id === foundUser?.id)) { + taggers.push(foundUser); + } - let eventCount = 50; - $: eventElementsPaginated = eventElements.slice(0, eventCount); + return foundUser; + } else { + return undefined; + } + }; - loading = false; + communityEvents.forEach((event) => { + let elementMatch = filteredElements.find((element) => element.id === event['element_id']); - let taggers: User[]; - $: taggers = []; + if (elementMatch) { + let location = elementMatch['osm_json'].tags?.name || undefined; - const findUser = (tagger: ActivityEvent) => { - let foundUser = $users.find((user) => user.id == tagger['user_id']); + let tagger = findUser(event); - if (foundUser) { - if (!taggers.find((tagger) => tagger.id === foundUser?.id)) { - taggers.push(foundUser); + eventElements.push({ + ...event, + location: location || 'Unnamed element', + merchantId: elementMatch.id, + tagger + }); } + }); - return foundUser; - } else { - return ''; - } - }; - - let mapElement: HTMLDivElement; - let map: Map; - let mapLoaded = false; - - let baseMaps: BaseMaps; + eventElements = eventElements; + taggers = taggers; - let chartsLoading = true; - let upToDateChartCanvas: HTMLCanvasElement; - let upToDateChart: Chart<'line', number[], string>; - let totalChartCanvas: HTMLCanvasElement; - let totalChart: Chart<'line', number[], string>; - let legacyChartCanvas: HTMLCanvasElement; - let legacyChart: Chart<'line', number[], string>; - let paymentMethodChartCanvas: HTMLCanvasElement; - let paymentMethodChart: Chart<'line', number[], string>; + const populateCharts = () => { + const chartsReports = [...communityReports].sort( + (a, b) => Date.parse(a['created_at']) - Date.parse(b['created_at']) + ); - let chartsReports = [...communityReports].sort( - (a, b) => Date.parse(a['created_at']) - Date.parse(b['created_at']) - ); - - const populateCharts = () => { - const theme = detectTheme(); - - updatedChart = new Chart(updatedChartCanvas, { - type: 'pie', - data: { - labels: ['Up-To-Date', 'Outdated'], - datasets: [ - { - label: 'Locations', - data: [upToDate, outdated], - backgroundColor: ['rgb(16, 183, 145)', 'rgb(235, 87, 87)'], - hoverOffset: 4 - } - ] - }, - options: { - maintainAspectRatio: false, - plugins: { - legend: { - labels: { - font: { - weight: '600' + const theme = detectTheme(); + + updatedChart = new Chart(updatedChartCanvas, { + type: 'pie', + data: { + labels: ['Up-To-Date', 'Outdated'], + datasets: [ + { + label: 'Locations', + data: [upToDate, outdated], + backgroundColor: ['rgb(16, 183, 145)', 'rgb(235, 87, 87)'], + hoverOffset: 4 + } + ] + }, + options: { + maintainAspectRatio: false, + plugins: { + legend: { + labels: { + font: { + weight: '600' + } } } } } - } - }); + }); - let percents = chartsReports.filter((report) => report.tags.up_to_date_percent); - - upToDateChart = new Chart(upToDateChartCanvas, { - type: 'line', - data: { - labels: percents.map(({ date }) => date), - datasets: [ - { - label: 'Up-To-Date Percent', - data: percents.map(({ tags: { up_to_date_percent } }) => up_to_date_percent), - fill: { - target: 'origin', - above: 'rgba(11, 144, 114, 0.2)' - }, - borderColor: 'rgb(11, 144, 114)', - tension: 0.1 - } - ] - }, - options: { - maintainAspectRatio: false, - plugins: { - legend: { - labels: { - font: { - weight: '600' - } + let percents = chartsReports.filter((report) => report.tags.up_to_date_percent); + + upToDateChart = new Chart(upToDateChartCanvas, { + type: 'line', + data: { + labels: percents.map(({ date }) => date), + datasets: [ + { + label: 'Up-To-Date Percent', + data: percents.map(({ tags: { up_to_date_percent } }) => up_to_date_percent), + fill: { + target: 'origin', + above: 'rgba(11, 144, 114, 0.2)' + }, + borderColor: 'rgb(11, 144, 114)', + tension: 0.1 } - } + ] }, - scales: { - x: { - ticks: { - maxTicksLimit: 5, - font: { - weight: '600' + options: { + maintainAspectRatio: false, + plugins: { + legend: { + labels: { + font: { + weight: '600' + } } - }, - grid: { - color: theme === 'dark' ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.1)' } }, - y: { - min: 0, - max: 100, - ticks: { - precision: 0, - font: { - weight: '600' + scales: { + x: { + ticks: { + maxTicksLimit: 5, + font: { + weight: '600' + } + }, + grid: { + color: theme === 'dark' ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.1)' } }, - grid: { - color: theme === 'dark' ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.1)' + y: { + min: 0, + max: 100, + ticks: { + precision: 0, + font: { + weight: '600' + } + }, + grid: { + color: theme === 'dark' ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.1)' + } } } } - } - }); + }); - totalChart = new Chart(totalChartCanvas, { - type: 'line', - data: { - labels: chartsReports.map(({ date }) => date), - datasets: [ - { - label: 'Total Locations', - data: chartsReports.map(({ tags: { total_elements } }) => total_elements), - fill: { - target: 'origin', - above: 'rgba(0, 153, 175, 0.2)' - }, - borderColor: 'rgb(0, 153, 175)', - tension: 0.1 - } - ] - }, - options: { - maintainAspectRatio: false, - plugins: { - legend: { - labels: { - font: { - weight: '600' - } + totalChart = new Chart(totalChartCanvas, { + type: 'line', + data: { + labels: chartsReports.map(({ date }) => date), + datasets: [ + { + label: 'Total Locations', + data: chartsReports.map(({ tags: { total_elements } }) => total_elements), + fill: { + target: 'origin', + above: 'rgba(0, 153, 175, 0.2)' + }, + borderColor: 'rgb(0, 153, 175)', + tension: 0.1 } - } + ] }, - scales: { - x: { - ticks: { - maxTicksLimit: 5, - font: { - weight: '600' + options: { + maintainAspectRatio: false, + plugins: { + legend: { + labels: { + font: { + weight: '600' + } } - }, - grid: { - color: theme === 'dark' ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.1)' } }, - y: { - min: 0, - grace: '5%', - ticks: { - precision: 0, - font: { - weight: '600' + scales: { + x: { + ticks: { + maxTicksLimit: 5, + font: { + weight: '600' + } + }, + grid: { + color: theme === 'dark' ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.1)' } }, - grid: { - color: theme === 'dark' ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.1)' + y: { + min: 0, + grace: '5%', + ticks: { + precision: 0, + font: { + weight: '600' + } + }, + grid: { + color: theme === 'dark' ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.1)' + } } } } - } - }); + }); - legacyChart = new Chart(legacyChartCanvas, { - type: 'line', - data: { - labels: chartsReports.map(({ date }) => date), - datasets: [ - { - label: 'Legacy Locations', - data: chartsReports.map(({ tags: { legacy_elements } }) => legacy_elements), - fill: { - target: 'origin', - above: 'rgba(235, 87, 87, 0.2)' - }, - borderColor: 'rgb(235, 87, 87)', - tension: 0.1 - } - ] - }, - options: { - maintainAspectRatio: false, - plugins: { - legend: { - labels: { - font: { - weight: '600' - } + legacyChart = new Chart(legacyChartCanvas, { + type: 'line', + data: { + labels: chartsReports.map(({ date }) => date), + datasets: [ + { + label: 'Legacy Locations', + data: chartsReports.map(({ tags: { legacy_elements } }) => legacy_elements), + fill: { + target: 'origin', + above: 'rgba(235, 87, 87, 0.2)' + }, + borderColor: 'rgb(235, 87, 87)', + tension: 0.1 } - } + ] }, - scales: { - x: { - ticks: { - maxTicksLimit: 5, - font: { - weight: '600' + options: { + maintainAspectRatio: false, + plugins: { + legend: { + labels: { + font: { + weight: '600' + } } - }, - grid: { - color: theme === 'dark' ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.1)' } }, - y: { - min: 0, - grace: '5%', - ticks: { - precision: 0, - font: { - weight: '600' + scales: { + x: { + ticks: { + maxTicksLimit: 5, + font: { + weight: '600' + } + }, + grid: { + color: theme === 'dark' ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.1)' } }, - grid: { - color: theme === 'dark' ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.1)' + y: { + min: 0, + grace: '5%', + ticks: { + precision: 0, + font: { + weight: '600' + } + }, + grid: { + color: theme === 'dark' ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.1)' + } } } } - } - }); + }); - paymentMethodChart = new Chart(paymentMethodChartCanvas, { - type: 'line', - data: { - labels: chartsReports.map(({ date }) => date), - datasets: [ - { - label: 'On-chain', - data: chartsReports.map( - ({ tags: { total_elements_onchain } }) => total_elements_onchain - ), - fill: false, - borderColor: 'rgb(247, 147, 26)', - tension: 0.1 - }, - { - label: 'Lightning', - data: chartsReports.map( - ({ tags: { total_elements_lightning } }) => total_elements_lightning - ), - fill: false, - borderColor: 'rgb(249, 193, 50)', - tension: 0.1 - }, - { - label: 'Contactless', - data: chartsReports.map( - ({ tags: { total_elements_lightning_contactless } }) => - total_elements_lightning_contactless - ), - fill: false, - borderColor: 'rgb(102, 16, 242)', - tension: 0.1 - } - ] - }, - options: { - maintainAspectRatio: false, - plugins: { - legend: { - labels: { - font: { - weight: '600' - } + paymentMethodChart = new Chart(paymentMethodChartCanvas, { + type: 'line', + data: { + labels: chartsReports.map(({ date }) => date), + datasets: [ + { + label: 'On-chain', + data: chartsReports.map( + ({ tags: { total_elements_onchain } }) => total_elements_onchain + ), + fill: false, + borderColor: 'rgb(247, 147, 26)', + tension: 0.1 + }, + { + label: 'Lightning', + data: chartsReports.map( + ({ tags: { total_elements_lightning } }) => total_elements_lightning + ), + fill: false, + borderColor: 'rgb(249, 193, 50)', + tension: 0.1 + }, + { + label: 'Contactless', + data: chartsReports.map( + ({ tags: { total_elements_lightning_contactless } }) => + total_elements_lightning_contactless + ), + fill: false, + borderColor: 'rgb(102, 16, 242)', + tension: 0.1 } - } + ] }, - scales: { - x: { - ticks: { - maxTicksLimit: 5, - font: { - weight: '600' + options: { + maintainAspectRatio: false, + plugins: { + legend: { + labels: { + font: { + weight: '600' + } } - }, - grid: { - color: theme === 'dark' ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.1)' } }, - y: { - min: 0, - grace: '5%', - ticks: { - precision: 0, - font: { - weight: '600' + scales: { + x: { + ticks: { + maxTicksLimit: 5, + font: { + weight: '600' + } + }, + grid: { + color: theme === 'dark' ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.1)' } }, - grid: { - color: theme === 'dark' ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.1)' + y: { + min: 0, + grace: '5%', + ticks: { + precision: 0, + font: { + weight: '600' + } + }, + grid: { + color: theme === 'dark' ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.1)' + } } } } - } - }); + }); - chartsLoading = false; - }; + chartsLoading = false; + }; - $: $theme !== undefined && - chartsLoading === false && - updateChartThemes([upToDateChart, totalChart, legacyChart, paymentMethodChart]); - - onMount(async () => { - if (browser) { - // setup charts - updatedChartCanvas.getContext('2d'); - upToDateChartCanvas.getContext('2d'); - totalChartCanvas.getContext('2d'); - legacyChartCanvas.getContext('2d'); - paymentMethodChartCanvas.getContext('2d'); - populateCharts(); - - //import packages - const leaflet = await import('leaflet'); - // @ts-expect-error - const DomEvent = await import('leaflet/src/dom/DomEvent'); - /* eslint-disable no-unused-vars, @typescript-eslint/no-unused-vars */ - const leafletMarkerCluster = await import('leaflet.markercluster'); - const leafletFeaturegroupSubgroup = await import('leaflet.featuregroup.subgroup'); - const leafletLocateControl = await import('leaflet.locatecontrol'); - /* eslint-enable no-unused-vars, @typescript-eslint/no-unused-vars */ + populateCharts(); + const populateMap = () => { // add map map = leaflet.map(mapElement, { attributionControl: false }); @@ -628,7 +560,7 @@ let verifiedDate = calcVerifiedDate(); // add community area poly to map - if (community?.geo_json) { + if (community.geo_json) { leaflet.geoJSON(community.geo_json, { style: { fill: false } }).addTo(map); } @@ -690,25 +622,200 @@ map.fitBounds( // @ts-expect-error - community?.geo_json + community.geo_json ? leaflet.geoJSON(community.geo_json).getBounds() : [ - [community?.['box:south'], community?.['box:west']], - [community?.['box:north'], community?.['box:east']] + [community['box:south'], community['box:west']], + [community['box:north'], community['box:east']] ] ); mapLoaded = true; + }; + + populateMap(); + + dataInitialized = true; + }; + + $: $users && + $users.length && + $events && + $events.length && + $elements && + $elements.length && + $areas && + $areas.length && + $reports && + $reports.length && + initialRenderComplete && + !dataInitialized && + initializeData(); + + const ticketTypes = ['Add', 'Verify']; + let showType = 'Add'; + + const tickets = data.tickets; + const ticketError = tickets === 'error' ? true : false; + + $: ticketError && errToast('Could not load open tickets, please try again or contact BTC Map.'); + + const add = + tickets && tickets.length && !ticketError + ? tickets.filter((issue: any) => + issue.labels.find((label: any) => label.name === 'location-submission') + ) + : []; + const verify = + tickets && tickets.length && !ticketError + ? tickets.filter((issue: any) => + issue.labels.find((label: any) => label.name === 'verify-submission') + ) + : []; + + const totalTickets = add.length + verify.length; + + let avatar: string; + const name = data.name; + let org: string | undefined; + let sponsor: boolean | undefined; + let continent: Continents; + let website: string | undefined; + let email: string | undefined; + let nostr: string | undefined; + let twitter: string | undefined; + let secondTwitter: string | undefined; + let meetup: string | undefined; + let eventbrite: string | undefined; + let telegram: string | undefined; + let discord: string | undefined; + let youtube: string | undefined; + let github: string | undefined; + let reddit: string | undefined; + let instagram: string | undefined; + let whatsapp: string | undefined; + let facebook: string | undefined; + let linkedin: string | undefined; + let rss: string | undefined; + let signal: string | undefined; + let lightning: { destination: string; type: TipType } | undefined; + + let total: number | undefined; + let upToDate: number | undefined; + let outdated: number | undefined; + let legacy: number | undefined; + let grade: Grade; + + let gradeTooltip: HTMLButtonElement; + + $: gradeTooltip && + tippy([gradeTooltip], { + content: ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Up-To-DateGrade
95-100%5 Star
75-95%4 Star
50-75%3 Star
25-50%2 Star
0-25%1 Star
`, + allowHTML: true + }); + + let upToDatePercent: string | undefined; + let outdatedPercent: string | undefined; + let legacyPercent: string | undefined; + + let updatedChartCanvas: HTMLCanvasElement; + // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars + let updatedChart; + + let hideArrow = false; + let activityDiv: HTMLDivElement; + let eventElements: ActivityEvent[] = []; + + let eventCount = 50; + $: eventElementsPaginated = eventElements.slice(0, eventCount); + + let taggers: User[] = []; + let taggerCount = 50; + $: taggersPaginated = taggers.slice(0, taggerCount); + let taggerDiv: HTMLDivElement; + + let mapElement: HTMLDivElement; + let map: Map; + let mapLoaded = false; + + let baseMaps: BaseMaps; + + let chartsLoading = true; + let upToDateChartCanvas: HTMLCanvasElement; + let upToDateChart: Chart<'line', number[], string>; + let totalChartCanvas: HTMLCanvasElement; + let totalChart: Chart<'line', number[], string>; + let legacyChartCanvas: HTMLCanvasElement; + let legacyChart: Chart<'line', number[], string>; + let paymentMethodChartCanvas: HTMLCanvasElement; + let paymentMethodChart: Chart<'line', number[], string>; + + $: $theme !== undefined && + !chartsLoading && + updateChartThemes([upToDateChart, totalChart, legacyChart, paymentMethodChart]); + + let leaflet: Leaflet; + let DomEvent: DomEventType; + + onMount(async () => { + if (browser) { + // setup charts + updatedChartCanvas.getContext('2d'); + upToDateChartCanvas.getContext('2d'); + totalChartCanvas.getContext('2d'); + legacyChartCanvas.getContext('2d'); + paymentMethodChartCanvas.getContext('2d'); + + //import packages + leaflet = await import('leaflet'); + // @ts-expect-error + DomEvent = await import('leaflet/src/dom/DomEvent'); + /* eslint-disable no-unused-vars, @typescript-eslint/no-unused-vars */ + const leafletMarkerCluster = await import('leaflet.markercluster'); + const leafletFeaturegroupSubgroup = await import('leaflet.featuregroup.subgroup'); + const leafletLocateControl = await import('leaflet.locatecontrol'); + /* eslint-enable no-unused-vars, @typescript-eslint/no-unused-vars */ + + initialRenderComplete = true; } }); - $: $theme !== undefined && mapLoaded === true && toggleMapButtons(); + $: $theme !== undefined && mapLoaded && toggleMapButtons(); const closePopup = () => { map.closePopup(); }; - $: $theme !== undefined && mapLoaded === true && closePopup(); + $: $theme !== undefined && mapLoaded && closePopup(); const toggleTheme = () => { if ($theme === 'dark') { @@ -720,7 +827,7 @@ } }; - $: $theme !== undefined && mapLoaded === true && toggleTheme(); + $: $theme !== undefined && mapLoaded && toggleTheme(); onDestroy(async () => { if (map) { @@ -731,6 +838,11 @@ + {$page.data.name} - BTC Map Community + + + + {#if lightning && lightning.type === 'address'} @@ -751,14 +863,18 @@
- avatar + {#if avatar} + avatar + {:else} +
+ {/if}

{name}

@@ -768,68 +884,79 @@ {#if sponsor} {/if} -

- {continent?.replace('-', ' ')} - -

- {#if community?.geo_json} - View on community map - - + {#if continent} +

+ {continent.replace('-', ' ')} + +

+ {:else} + - + {#if dataInitialized} + + {:else} +
+ + {#each Array(3) as skeleton} +
+ {/each} +
+ {/if} {#if lightning} @@ -841,16 +968,29 @@ class="rounded-t-3xl border border-b-0 border-statBorder p-5 text-center text-lg font-semibold text-primary dark:bg-white/10 dark:text-white md:text-left" > {name} Map -
@@ -922,41 +1068,50 @@ > {name} Supertaggers -
+
{#if taggers && taggers.length} - {#each taggers as tagger} - - {/each} - {:else if !communityEvents.length} -

No supertaggers to display.

+ + + {#if taggersPaginated.length !== taggers.length} + + {/if} + {:else if !dataInitialized} +
+ + {#each Array(5) as tagger} +
+

+

+

+ {/each} +
{:else} - - {#each Array(5) as tagger} -
-

-

-

- {/each} +

No supertaggers to display.

{/if}
@@ -974,17 +1129,17 @@ bind:this={activityDiv} class="hide-scroll relative max-h-[375px] space-y-2 overflow-y-scroll" on:scroll={() => { - if (!loading && !hideArrow) { + if (dataInitialized && !hideArrow) { hideArrow = true; } }} > - {#if eventElements && eventElements.length && !loading} + {#if eventElements && eventElements.length} {#each eventElementsPaginated as event} {/if} - {:else if !communityEvents.length} -

No activity to display.

- {:else} + {:else if !dataInitialized} {#each Array(5) as skeleton} {/each} + {:else} +

No activity to display.

{/if}
diff --git a/src/routes/tagger/[id]/+layout.svelte b/src/routes/tagger/[id]/+layout.svelte deleted file mode 100644 index feece2c8..00000000 --- a/src/routes/tagger/[id]/+layout.svelte +++ /dev/null @@ -1,26 +0,0 @@ - - - - {$page.data.username} - BTC Map Supertagger - - - - - -{#if $users && $users.length && $events && $events.length && $elements && $elements.length} - -{:else} - -{/if} diff --git a/src/routes/tagger/[id]/+page.svelte b/src/routes/tagger/[id]/+page.svelte index 4d32ffbf..fba575bf 100644 --- a/src/routes/tagger/[id]/+page.svelte +++ b/src/routes/tagger/[id]/+page.svelte @@ -2,6 +2,7 @@ export let data; import { browser } from '$app/environment'; + import { page } from '$app/stores'; import { Footer, Header, @@ -23,11 +24,22 @@ longCalc, toggleMapButtons } from '$lib/map/setup'; - import { elements, events, excludeLeader, theme, users } from '$lib/store'; + import { + elementError, + elements, + eventError, + events, + excludeLeader, + theme, + userError, + users + } from '$lib/store'; import { BadgeType, type ActivityEvent, + type DomEventType, type EarnedBadge, + type Leaflet, type ProfileLeaderboard } from '$lib/types.js'; import { detectTheme, errToast } from '$lib/utils'; @@ -38,251 +50,252 @@ import { marked } from 'marked'; import { onDestroy, onMount } from 'svelte'; - let userFound = $users.find((user) => user.id == data.user); - if (!userFound) { - errToast('Could not find user, please try again or contact BTC Map.'); - throw error(404, 'User Not Found'); - } - let userCreated = userFound['created_at']; - let supporter = - userFound.tags['supporter:expires'] && - Date.parse(userFound.tags['supporter:expires']) > Date.now(); - const user = userFound['osm_json']; - let avatar = user.img ? user.img.href : '/images/satoshi-nakamoto.png'; - let username = user['display_name']; - let description = user.description; - let removeLightning = description.match(/(\[⚡]\(lightning:[^)]+\))/g); - let filteredDesc = removeLightning?.length - ? description.replaceAll(removeLightning[0], '') - : description; - let profileDesc: HTMLHeadingElement; - let regexMatch = description.match('(lightning:[^)]+)'); - let lightning = regexMatch && regexMatch[0].slice(10); - - let userEvents = $events.filter((event) => event['user_id'] == user.id); - userEvents.sort((a, b) => Date.parse(b['created_at']) - Date.parse(a['created_at'])); - let created = - user.id === 17221642 - ? userEvents.filter((event) => event.type === 'create').length + 100 - : userEvents.filter((event) => event.type === 'create').length; - let updated = - user.id === 17221642 - ? userEvents.filter((event) => event.type === 'update').length + 20 - : userEvents.filter((event) => event.type === 'update').length; - let deleted = userEvents.filter((event) => event.type === 'delete').length; - let total = created + updated + deleted; - - let leaderboard: ProfileLeaderboard[] = []; - - const populateLeaderboard = () => { - $users.forEach((user) => { - if ($excludeLeader.includes(user.id)) { - return; - } - - let userEvents = $events.filter((event) => event['user_id'] == user.id); + // alert for user errors + $: $userError && errToast($userError); + // alert for event errors + $: $eventError && errToast($eventError); + // alert for element errors + $: $elementError && errToast($elementError); - if (userEvents.length) { - leaderboard.push({ - id: user.id, - total: user.id === 17221642 ? userEvents.length + 120 : userEvents.length - }); - } - }); + let dataInitialized = false; + let initialRenderComplete = false; - leaderboard.sort((a, b) => b.total - a.total); - leaderboard = leaderboard.slice(0, 10); - }; - populateLeaderboard(); - - let badges = [ - { - check: [ - 10396321, 17441326, 17199501, 668096, 17462838, 17221642, 5432507, 17354902, 18452174, - 18360665, 616774, 18062435, 7522075, 18380975, 1697546, 19288099, 11903494, 18552145, - 1836965, 19795869, 17872, 19768735, 17573979, 2929493, 19714509, 1851550, 18244560, - 19756689, 527105, 2339960, 17322349, 17300693, 1236325, 1787080 - ].includes(user.id), - title: 'Geyser Tournament', - icon: 'geyser', - type: BadgeType.Achievement - }, - { check: supporter, title: 'Supporter', icon: 'supporter', type: BadgeType.Achievement }, - { - check: leaderboard[0].id == user.id, - title: 'Top Tagger', - icon: 'top-tagger', - type: BadgeType.Achievement - }, - { - check: leaderboard.slice(0, 3).find((item) => item.id == user.id), - title: 'Podium', - icon: 'podium', - type: BadgeType.Achievement - }, - { - check: leaderboard.find((item) => item.id == user.id), - title: 'High Rank', - icon: 'high-rank', - type: BadgeType.Achievement - }, - { - check: Date.parse(userCreated) < new Date('December 26, 2022 00:00:00').getTime(), - title: 'OG Supertagger', - icon: 'og-supertagger', - type: BadgeType.Achievement - }, - { - check: lightning, - title: 'Lightning Junkie', - icon: 'lightning-junkie', - type: BadgeType.Achievement - }, - { - check: user.img, - title: 'Hello World', - icon: 'hello-world', - type: BadgeType.Achievement - }, - { - check: created > updated && created > deleted, - title: 'Creator', - icon: 'creator', - type: BadgeType.Achievement - }, - { - check: updated > created && updated > deleted, - title: 'Update Maxi', - icon: 'update-maxi', - type: BadgeType.Achievement - }, - { - check: deleted > created && deleted > updated, - title: 'Demolition Specialist', - icon: 'demolition-specialist', - type: BadgeType.Achievement - }, - { - check: total >= 21000000, - title: 'Hyperbitcoinisation', - icon: 'hyperbitcoinisation', - type: BadgeType.Contribution - }, - { - check: total >= 10000, - title: 'Pizza Time', - icon: 'pizza-time', - type: BadgeType.Contribution - }, - { check: total >= 7777, title: 'Godly', icon: 'godly', type: BadgeType.Contribution }, - { check: total >= 5000, title: 'Shadow', icon: 'shadow', type: BadgeType.Contribution }, - { check: total >= 3110, title: 'Whitepaper', icon: 'whitepaper', type: BadgeType.Contribution }, - { check: total >= 1984, title: 'Winston', icon: 'winston', type: BadgeType.Contribution }, - { check: total >= 1000, title: 'Whale', icon: 'whale', type: BadgeType.Contribution }, - { check: total >= 821, title: 'Infinity', icon: 'infinity', type: BadgeType.Contribution }, - { check: total >= 500, title: 'Legend', icon: 'legend', type: BadgeType.Contribution }, - { check: total >= 301, title: 'Chancellor', icon: 'chancellor', type: BadgeType.Contribution }, - { check: total >= 256, title: 'SHA', icon: 'sha', type: BadgeType.Contribution }, - { - check: total >= 210, - title: 'No Bailouts', - icon: 'no-bailouts', - type: BadgeType.Contribution - }, - { - check: total >= 100, - title: 'Supertagger', - icon: 'supertagger', - type: BadgeType.Contribution - }, - { check: total >= 69, title: 'ATH', icon: 'ath', type: BadgeType.Contribution }, - { - check: total >= 51, - title: 'Longest Chain', - icon: 'longest-chain', - type: BadgeType.Contribution - }, - { check: total >= 21, title: 'Satoshi', icon: 'satoshi', type: BadgeType.Contribution }, - { check: total >= 10, title: 'Heartbeat', icon: 'heartbeat', type: BadgeType.Contribution }, - { check: total >= 4, title: 'Segwit', icon: 'segwit', type: BadgeType.Contribution }, - { check: total >= 1, title: 'Whole Tagger', icon: 'whole-tagger', type: BadgeType.Contribution } - ]; + let leaflet: Leaflet; + let DomEvent: DomEventType; - let earnedBadges: EarnedBadge[] = []; - - const addBadge = (check: boolean, title: string, icon: string, type: BadgeType) => { - if (check) { - earnedBadges.push({ title, icon, type }); - } - }; + const initializeData = () => { + if (dataInitialized) return; - badges.some((badge) => { - if (earnedBadges.find((badge) => badge.type === BadgeType.Contribution)) { - return true; + const userFound = $users.find((user) => user.id == data.user); + if (!userFound) { + errToast('Could not find user, please try again or contact BTC Map.'); + throw error(404, 'User Not Found'); } - addBadge(Boolean(badge.check), badge.title, badge.icon, badge.type); - }); - - let createdPercent = new Intl.NumberFormat('en-US').format( - Number((created / (total / 100)).toFixed(0)) - ); - - let updatedPercent = new Intl.NumberFormat('en-US').format( - Number((updated / (total / 100)).toFixed(0)) - ); + userCreated = userFound['created_at']; + supporter = Boolean( + userFound.tags['supporter:expires'] && + Date.parse(userFound.tags['supporter:expires']) > Date.now() + ); + const user = userFound['osm_json']; + avatar = user.img ? user.img.href : '/images/satoshi-nakamoto.png'; + const description = user.description; + const removeLightning = description.match(/(\[⚡]\(lightning:[^)]+\))/g); + filteredDesc = removeLightning?.length + ? description.replaceAll(removeLightning[0], '') + : description; + const regexMatch = description.match('(lightning:[^)]+)'); + lightning = regexMatch && regexMatch[0].slice(10); + + const userEvents = $events.filter((event) => event['user_id'] == user.id); + userEvents.sort((a, b) => Date.parse(b['created_at']) - Date.parse(a['created_at'])); + created = + user.id === 17221642 + ? userEvents.filter((event) => event.type === 'create').length + 100 + : userEvents.filter((event) => event.type === 'create').length; + updated = + user.id === 17221642 + ? userEvents.filter((event) => event.type === 'update').length + 20 + : userEvents.filter((event) => event.type === 'update').length; + deleted = userEvents.filter((event) => event.type === 'delete').length; + total = created + updated + deleted; + + const populateLeaderboard = () => { + $users.forEach((user) => { + if ($excludeLeader.includes(user.id)) { + return; + } - let deletedPercent = new Intl.NumberFormat('en-US').format( - Number((deleted / (total / 100)).toFixed(0)) - ); + let userEvents = $events.filter((event) => event['user_id'] == user.id); - let tagTypeChartCanvas: HTMLCanvasElement; - // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars - let tagTypeChart; + if (userEvents.length) { + leaderboard.push({ + id: user.id, + total: user.id === 17221642 ? userEvents.length + 120 : userEvents.length + }); + } + }); - let loading = true; - let hideArrow = false; - let activityDiv; - let eventElements: ActivityEvent[] = []; + leaderboard.sort((a, b) => b.total - a.total); + leaderboard = leaderboard.slice(0, 10); + }; + populateLeaderboard(); + + const badges = [ + { + check: [ + 10396321, 17441326, 17199501, 668096, 17462838, 17221642, 5432507, 17354902, 18452174, + 18360665, 616774, 18062435, 7522075, 18380975, 1697546, 19288099, 11903494, 18552145, + 1836965, 19795869, 17872, 19768735, 17573979, 2929493, 19714509, 1851550, 18244560, + 19756689, 527105, 2339960, 17322349, 17300693, 1236325, 1787080 + ].includes(user.id), + title: 'Geyser Tournament', + icon: 'geyser', + type: BadgeType.Achievement + }, + { check: supporter, title: 'Supporter', icon: 'supporter', type: BadgeType.Achievement }, + { + check: leaderboard[0].id == user.id, + title: 'Top Tagger', + icon: 'top-tagger', + type: BadgeType.Achievement + }, + { + check: Boolean(leaderboard.slice(0, 3).find((item) => item.id == user.id)), + title: 'Podium', + icon: 'podium', + type: BadgeType.Achievement + }, + { + check: Boolean(leaderboard.find((item) => item.id == user.id)), + title: 'High Rank', + icon: 'high-rank', + type: BadgeType.Achievement + }, + { + check: Date.parse(userCreated) < new Date('December 26, 2022 00:00:00').getTime(), + title: 'OG Supertagger', + icon: 'og-supertagger', + type: BadgeType.Achievement + }, + { + check: Boolean(lightning), + title: 'Lightning Junkie', + icon: 'lightning-junkie', + type: BadgeType.Achievement + }, + { + check: Boolean(user.img), + title: 'Hello World', + icon: 'hello-world', + type: BadgeType.Achievement + }, + { + check: created > updated && created > deleted, + title: 'Creator', + icon: 'creator', + type: BadgeType.Achievement + }, + { + check: updated > created && updated > deleted, + title: 'Update Maxi', + icon: 'update-maxi', + type: BadgeType.Achievement + }, + { + check: deleted > created && deleted > updated, + title: 'Demolition Specialist', + icon: 'demolition-specialist', + type: BadgeType.Achievement + }, + { + check: total >= 21000000, + title: 'Hyperbitcoinisation', + icon: 'hyperbitcoinisation', + type: BadgeType.Contribution + }, + { + check: total >= 10000, + title: 'Pizza Time', + icon: 'pizza-time', + type: BadgeType.Contribution + }, + { check: total >= 7777, title: 'Godly', icon: 'godly', type: BadgeType.Contribution }, + { check: total >= 5000, title: 'Shadow', icon: 'shadow', type: BadgeType.Contribution }, + { + check: total >= 3110, + title: 'Whitepaper', + icon: 'whitepaper', + type: BadgeType.Contribution + }, + { check: total >= 1984, title: 'Winston', icon: 'winston', type: BadgeType.Contribution }, + { check: total >= 1000, title: 'Whale', icon: 'whale', type: BadgeType.Contribution }, + { check: total >= 821, title: 'Infinity', icon: 'infinity', type: BadgeType.Contribution }, + { check: total >= 500, title: 'Legend', icon: 'legend', type: BadgeType.Contribution }, + { + check: total >= 301, + title: 'Chancellor', + icon: 'chancellor', + type: BadgeType.Contribution + }, + { check: total >= 256, title: 'SHA', icon: 'sha', type: BadgeType.Contribution }, + { + check: total >= 210, + title: 'No Bailouts', + icon: 'no-bailouts', + type: BadgeType.Contribution + }, + { + check: total >= 100, + title: 'Supertagger', + icon: 'supertagger', + type: BadgeType.Contribution + }, + { check: total >= 69, title: 'ATH', icon: 'ath', type: BadgeType.Contribution }, + { + check: total >= 51, + title: 'Longest Chain', + icon: 'longest-chain', + type: BadgeType.Contribution + }, + { check: total >= 21, title: 'Satoshi', icon: 'satoshi', type: BadgeType.Contribution }, + { check: total >= 10, title: 'Heartbeat', icon: 'heartbeat', type: BadgeType.Contribution }, + { check: total >= 4, title: 'Segwit', icon: 'segwit', type: BadgeType.Contribution }, + { + check: total >= 1, + title: 'Whole Tagger', + icon: 'whole-tagger', + type: BadgeType.Contribution + } + ]; - userEvents.forEach((event) => { - let elementMatch = $elements.find((element) => element.id === event['element_id']); + const addBadge = (check: boolean, title: string, icon: string, type: BadgeType) => { + if (check) { + earnedBadges.push({ title, icon, type }); + } + }; - if (elementMatch) { - let location = - elementMatch['osm_json'].tags && elementMatch['osm_json'].tags.name - ? elementMatch['osm_json'].tags.name - : undefined; + badges.some((badge) => { + if (earnedBadges.find((badge) => badge.type === BadgeType.Contribution)) { + return true; + } + addBadge(Boolean(badge.check), badge.title, badge.icon, badge.type); + }); - eventElements.push({ - ...event, - location: location || 'Unnamed element', - merchantId: elementMatch.id - }); - } - }); + createdPercent = new Intl.NumberFormat('en-US').format( + Number((created / (total / 100)).toFixed(0)) + ); - let eventCount = 50; - $: eventElementsPaginated = eventElements.slice(0, eventCount); + updatedPercent = new Intl.NumberFormat('en-US').format( + Number((updated / (total / 100)).toFixed(0)) + ); - loading = false; + deletedPercent = new Intl.NumberFormat('en-US').format( + Number((deleted / (total / 100)).toFixed(0)) + ); - let mapElement: HTMLDivElement; - let map: Map; - let mapLoaded = false; + userEvents.forEach((event) => { + let elementMatch = $elements.find((element) => element.id === event['element_id']); - let osm: TileLayer; - let alidadeSmoothDark: TileLayer; + if (elementMatch) { + let location = + elementMatch['osm_json'].tags && elementMatch['osm_json'].tags.name + ? elementMatch['osm_json'].tags.name + : undefined; - onMount(async () => { - if (browser) { - const theme = detectTheme(); + eventElements.push({ + ...event, + location: location || 'Unnamed element', + merchantId: elementMatch.id + }); + } + }); - // add markdown support for profile description - profileDesc.innerHTML = DOMPurify.sanitize(marked.parse(filteredDesc)); + eventElements = eventElements; - // setup chart - tagTypeChartCanvas.getContext('2d'); + // add markdown support for profile description + profileDesc.innerHTML = DOMPurify.sanitize(marked.parse(filteredDesc)); + const setupChart = () => { tagTypeChart = new Chart(tagTypeChartCanvas, { type: 'pie', data: { @@ -309,15 +322,11 @@ } } }); + }; + setupChart(); - //import packages - const leaflet = await import('leaflet'); - // @ts-expect-error - const DomEvent = await import('leaflet/src/dom/DomEvent'); - /* eslint-disable no-unused-vars, @typescript-eslint/no-unused-vars */ - const leafletMarkerCluster = await import('leaflet.markercluster'); - const leafletLocateControl = await import('leaflet.locatecontrol'); - /* eslint-enable no-unused-vars, @typescript-eslint/no-unused-vars */ + const setupMap = () => { + const theme = detectTheme(); // add map and tiles map = leaflet.map(mapElement, { attributionControl: false }); @@ -411,16 +420,86 @@ map.fitBounds(bounds.map(({ lat, long }) => [lat, long])); mapLoaded = true; + }; + setupMap(); + + dataInitialized = true; + }; + + $: $users && + $users.length && + $events && + $events.length && + $elements && + $elements.length && + initialRenderComplete && + !dataInitialized && + initializeData(); + + let userCreated: string | undefined; + let supporter: boolean | undefined; + let avatar: string | undefined; + let username = data.username; + let filteredDesc: string | undefined; + let profileDesc: HTMLHeadingElement; + let lightning: string | null; + + let created: number | undefined; + let updated: number | undefined; + let deleted: number | undefined; + let total: number | undefined; + + let leaderboard: ProfileLeaderboard[] = []; + + let earnedBadges: EarnedBadge[] = []; + + let createdPercent: string | undefined; + let updatedPercent: string | undefined; + let deletedPercent: string | undefined; + + let tagTypeChartCanvas: HTMLCanvasElement; + // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars + let tagTypeChart; + + let hideArrow = false; + let activityDiv; + let eventElements: ActivityEvent[] = []; + + let eventCount = 50; + $: eventElementsPaginated = eventElements.slice(0, eventCount); + + let mapElement: HTMLDivElement; + let map: Map; + let mapLoaded = false; + + let osm: TileLayer; + let alidadeSmoothDark: TileLayer; + + onMount(async () => { + if (browser) { + // setup chart + tagTypeChartCanvas.getContext('2d'); + + //import packages + leaflet = await import('leaflet'); + // @ts-expect-error + DomEvent = await import('leaflet/src/dom/DomEvent'); + /* eslint-disable no-unused-vars, @typescript-eslint/no-unused-vars */ + const leafletMarkerCluster = await import('leaflet.markercluster'); + const leafletLocateControl = await import('leaflet.locatecontrol'); + /* eslint-enable no-unused-vars, @typescript-eslint/no-unused-vars */ + + initialRenderComplete = true; } }); - $: $theme !== undefined && mapLoaded === true && toggleMapButtons(); + $: $theme !== undefined && mapLoaded && toggleMapButtons(); const closePopup = () => { map.closePopup(); }; - $: $theme !== undefined && mapLoaded === true && closePopup(); + $: $theme !== undefined && mapLoaded && closePopup(); const toggleTheme = () => { if ($theme === 'dark') { @@ -432,7 +511,7 @@ } }; - $: $theme !== undefined && mapLoaded === true && toggleTheme(); + $: $theme !== undefined && mapLoaded && toggleTheme(); onDestroy(async () => { if (map) { @@ -443,6 +522,11 @@ + {$page.data.username} - BTC Map Supertagger + + + + {#if lightning} @@ -462,14 +546,18 @@
- avatar + {#if avatar} + avatar + {:else} +
+ {/if}

@@ -512,18 +600,28 @@
- {#each earnedBadges as badge} - + {#if dataInitialized} + {#each earnedBadges as badge} + +
+ {badge.title} +

{badge.title}

+
+
+ {/each} + {:else} + + {#each Array(3) as badge}
- {badge.title} -

{badge.title}

+
+
- - {/each} + {/each} + {/if}
@@ -534,7 +632,6 @@ - +

-
+
+ {#if !dataInitialized} +

+ Loading chart... +

+ {/if} +
@@ -571,12 +678,12 @@ ? 'h-[375px]' : ''} relative overflow-y-scroll" on:scroll={() => { - if (!loading && !hideArrow) { + if (dataInitialized && !hideArrow) { hideArrow = true; } }} > - {#if eventElements && eventElements.length && !loading} + {#if eventElements && eventElements.length && dataInitialized} {#each eventElementsPaginated as event}