Skip to content

Commit

Permalink
Merge pull request #5345 from daiagi/collection-activity-tab
Browse files Browse the repository at this point in the history
✨  Collection Activity Tab
  • Loading branch information
yangwao authored Mar 28, 2023
2 parents c1f4427 + 9f4865d commit 1d36208
Show file tree
Hide file tree
Showing 49 changed files with 2,243 additions and 105 deletions.
28 changes: 23 additions & 5 deletions components/chart/PriceChart.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
>Price ({{ chainSymbol }})
</span>
<NeoDropdown class="py-0">
<NeoButton :label="selectedTimeRange.label" class="time-range-button" />
<NeoButton
:label="selectedTimeRange.label"
class="time-range-button"
no-shadow />

<template #items>
<NeoDropdownItem
Expand All @@ -19,7 +22,7 @@
</template>
</NeoDropdown>

<div class="content">
<div :class="{ content: !chartHeight }" :style="heightStyle">
<canvas id="priceChart" />
</div>
</div>
Expand Down Expand Up @@ -66,7 +69,12 @@ const setTimeRange = (value: { value: number; label: string }) => {
const props = defineProps<{
priceChartData?: [Date, number][][]
chartHeight?: string
}>()
const heightStyle = computed(() =>
props.chartHeight ? `height: ${props.chartHeight}` : ''
)
let Chart: ChartJS<'line', any, unknown>
onMounted(() => {
Expand Down Expand Up @@ -124,11 +132,11 @@ const getPriceChartData = () => {
)?.getContext('2d')
if (ctx) {
const commonStyle = {
tension: 0,
tension: 0.2,
pointRadius: 6,
pointHoverRadius: 6,
pointHoverBackgroundColor: isDarkMode.value ? '#181717' : 'white',
borderJoinStyle: 'miter' as const,
borderJoinStyle: 'round' as const,
radius: 0,
pointStyle: 'rect',
borderWidth: 1,
Expand Down Expand Up @@ -157,6 +165,16 @@ const getPriceChartData = () => {
},
options: {
maintainAspectRatio: false,
responsive: true,
responsiveAnimationDuration: 0,
transitions: {
resize: {
animation: {
duration: 0,
},
},
},
plugins: {
customCanvasBackgroundColor: {
color: isDarkMode.value ? '#181717' : 'white',
Expand Down Expand Up @@ -225,7 +243,7 @@ const getPriceChartData = () => {
callback: (value) => {
return `${Number(value).toFixed(2)} `
},
maxTicksLimit: 7,
stepSize: 1,
color: lineColor.value,
},
grid: {
Expand Down
104 changes: 104 additions & 0 deletions components/collection/activity/Activity.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<template>
<div class="is-flex">
<SidebarFilter />
<div ref="wrapper" class="w-full mt-5">
<div v-if="tablet">
<div class="columns">
<div class="column is-two-thirds">
<ActivityChart :events="events" />
</div>
<div class="column">
<OwnerInsights :owners="owners" :flippers="flippers" />
</div>
</div>
<BreadcrumbsFilter />
</div>
<div v-else>
<div class="is-flex is-flex-direction-column gap">
<OwnerInsights :owners="owners" :flippers="flippers" />
<div class="max-width">
<ActivityChart :events="events" />
</div>
</div>
</div>
<hr class="mb-40" :class="{ 'my-40': !isBreadCrumbsShowing }" />
<Events :events="sortedEventsWithOffersDesc" />
</div>
</div>
</template>

<script setup lang="ts">
import ActivityChart from './ActivityChart.vue'
import OwnerInsights from './OwnerInsights.vue'
import Events from './events/Events.vue'
import BreadcrumbsFilter from '@/components/shared/BreadcrumbsFilter.vue'
import { Interaction } from '@kodadot1/minimark'
import { useResizeObserver } from '@vueuse/core'
import SidebarFilter from '@/components/shared/filters/SidebarFilter.vue'
import { isAnyActivityFilterActive } from './utils'
import { mintInteraction } from '@/composables/collectionActivity/helpers'
import { useCollectionActivity } from '@/composables/collectionActivity/useCollectionActivity'
const mobileBreakpoint = 800
const route = useRoute()
const tablet = ref(true)
const wrapper = ref<HTMLDivElement | null>(null)
const isBreadCrumbsShowing = computed(
() => isAnyActivityFilterActive() && tablet.value
)
const collectionId = computed(() => route.params.id)
const { events, flippers, owners, offers } = useCollectionActivity({
collectionId: collectionId.value,
})
const InteractionIncluded = [
Interaction.BUY,
Interaction.LIST,
mintInteraction(),
Interaction.SEND,
]
const filteredEvents = computed(() =>
events.value.filter((event) =>
InteractionIncluded.includes(event.interaction as Interaction)
)
)
const withOffers = computed(() => [...filteredEvents.value, ...offers.value])
// newest events first (bigger timestamp first)
const sortedEventsWithOffersDesc = computed(() =>
withOffers.value.sort((a, b) => b.timestamp - a.timestamp)
)
useResizeObserver(wrapper, (entry) => {
if (entry[0].contentRect.width >= mobileBreakpoint) {
tablet.value = true
} else {
tablet.value = false
}
})
</script>

<style lang="scss" scoped>
.gap {
gap: 2.5rem;
}
.my-40 {
margin: 2.5rem 0;
}
.mb-40 {
margin-bottom: 2.5rem;
}
.is-flex-basis-two-thirds {
flex-basis: 66.6%;
}
.is-flex-basis-auto {
flex-basis: auto;
}
//hack to make the chart responsive
.max-width {
max-width: calc(100% - 1px);
}
</style>
53 changes: 53 additions & 0 deletions components/collection/activity/ActivityChart.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<template>
<PriceChart
v-if="events.length > 0"
:price-chart-data="chartData"
chart-height="350px" />
</template>

<script setup lang="ts">
import { ActivityInteraction } from '@/components/rmrk/service/scheme'
import { Interaction } from '@kodadot1/minimark'
import PriceChart from '@/components/chart/PriceChart.vue'
import { bin, displayValue, sortAsc, toDataPoint } from './utils'
const props = withDefaults(
defineProps<{
events: ActivityInteraction[]
}>(),
{
events: () => [],
}
)
const buyEvents = computed(() =>
sortAsc(
props.events
.filter((e) => e.interaction === Interaction.BUY)
.map(toDataPoint)
)
)
const listEvents = computed(() =>
sortAsc(
props.events
.filter((e) => e.interaction === Interaction.LIST)
.map(toDataPoint)
)
)
const chartData = computed(() => {
const buyBins = bin(buyEvents.value, { days: 1 })
const listBins = bin(listEvents.value, { days: 1 })
const binnedBuyEvents = buyBins.map(({ timestamp, value }) => [
new Date(timestamp),
displayValue(value),
]) as [Date, number][]
const binnedListEvents = listBins.map(({ timestamp, value }) => [
new Date(timestamp),
displayValue(value),
]) as [Date, number][]
return [binnedBuyEvents, binnedListEvents]
})
</script>
51 changes: 51 additions & 0 deletions components/collection/activity/OwnerInsights.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<template>
<div class="fixed-height border">
<div class="py-4 px-5 is-flex border-bottom" aria-label="controls">
<div
class="mr-4 is-clickable"
:class="{ 'has-text-weight-bold': activeTab === Tabs.holders }"
@click="activeTab = Tabs.holders">
{{ $t('holders') }}
</div>
<div
class="is-clickable"
:class="{ 'has-text-weight-bold': activeTab === Tabs.flippers }"
@click="activeTab = Tabs.flippers">
{{ $t('flippers') }}
</div>
</div>
<div class="py-4 limit-height is-scrollable">
<HoldersTab v-if="activeTab === Tabs.holders" :owners="owners" />
<FlippersTab v-if="activeTab === Tabs.flippers" :flippers="flippers" />
</div>
</div>
</template>

<script setup lang="ts">
import HoldersTab from './ownerInsightsTabs/HolderTab.vue'
import FlippersTab from './ownerInsightsTabs/FlipperTab.vue'
import { Flippers, Owners } from '@/composables/collectionActivity/types'
enum Tabs {
holders,
flippers,
}
defineProps<{
owners?: Owners
flippers?: Flippers
}>()
const activeTab = ref(Tabs.holders)
</script>

<style lang="scss" scoped>
.fixed-height {
height: 350px;
}
.limit-height {
max-height: 290px;
}
.is-scrollable {
overflow-y: auto;
}
</style>
20 changes: 20 additions & 0 deletions components/collection/activity/events/EventRow.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<template>
<div>
<EventRowDesktop v-if="variant === 'Desktop'" :event="event" />
<EventRowTablet v-else :event="event" />
</div>
</template>

<script setup lang="ts">
import {
InteractionWithNFT,
Offer,
} from '@/composables/collectionActivity/types'
import EventRowDesktop from './eventRow/EventRowDesktop.vue'
import EventRowTablet from './eventRow/EventRowTablet.vue'
defineProps<{
event: InteractionWithNFT | Offer
variant: 'Touch' | 'Desktop'
}>()
</script>
Loading

0 comments on commit 1d36208

Please sign in to comment.