mirror of
https://github.com/web3privacy/explorer-app.git
synced 2024-10-15 16:46:26 +02:00
commit
120744e9bb
14 changed files with 103 additions and 56 deletions
|
@ -3,6 +3,7 @@ import type { ProjectRating, ProjectShallow } from '~/types'
|
|||
|
||||
const props = defineProps<{
|
||||
project: ProjectShallow
|
||||
hiddenColumns?: string[]
|
||||
}>()
|
||||
const { switcher, ecosystems, filter } = storeToRefs(useData())
|
||||
|
||||
|
@ -11,6 +12,14 @@ const isLargeScreen = useMediaQuery('(min-width: 1024px)')
|
|||
const ratings: { label: string, type: string, rating: ProjectRating }[] = (props.project.ratings || []).map(rating => ({ label: rating.name, type: 'rating', rating: rating }))
|
||||
const ecosystem: { label: string[], type: string } = { label: ecosystems.value.filter(e => (props.project.ecosystem || []).includes(e.id)).map(e => e.icon!), type: 'ecosystem' }
|
||||
const projectItems: { label: string | string[], type: string, rating?: ProjectRating }[] = [{ label: props.project.usecases || [], type: 'array' }, ...ratings, ecosystem, { label: [props.project.website || '', props.project.github || '', props.project.twitter || ''], type: 'links' }]
|
||||
|
||||
const { width } = useWindowSize()
|
||||
const visibleColumnsCount = computed(() => {
|
||||
if (width.value >= 1024)
|
||||
return projectItems.filter(item => item.rating?.type ? !props.hiddenColumns?.includes(item.rating.type) : true).length + 4
|
||||
else
|
||||
return 2
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -25,7 +34,7 @@ const projectItems: { label: string | string[], type: string, rating?: ProjectRa
|
|||
>
|
||||
<div
|
||||
grid
|
||||
grid-cols="2 lg:10"
|
||||
:style="`grid-template-columns: repeat(${visibleColumnsCount}, 1fr)`"
|
||||
w-full
|
||||
>
|
||||
|
||||
|
@ -89,7 +98,7 @@ const projectItems: { label: string | string[], type: string, rating?: ProjectRa
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-for="(projectItem, index) of projectItems"
|
||||
v-for="(projectItem, index) of projectItems.filter((item) => item.rating?.type ? !hiddenColumns?.includes(item.rating.type): true)"
|
||||
:key="projectItem.label.toString()"
|
||||
hidden
|
||||
lg:flex
|
||||
|
|
|
@ -28,7 +28,7 @@ function onOptionSelected(value: string | number) {
|
|||
<div class="relative font-700 font-24px">
|
||||
<HeadlessListboxButton
|
||||
:id="`headless-listbox-button-${name}`"
|
||||
class="relative cursor-pointer py-6px px-14px text-left border-2px bg-app-white text-app-black text-xs sm:text-sm sm:leading-6"
|
||||
class="w-full relative cursor-pointer py-6px px-14px text-left border-2px bg-app-white text-app-black text-xs sm:text-sm sm:leading-6"
|
||||
hover="bg-app-black text-app-white"
|
||||
transition="all"
|
||||
duration-250
|
||||
|
@ -52,7 +52,7 @@ function onOptionSelected(value: string | number) {
|
|||
leave-to-class="opacity-0"
|
||||
>
|
||||
<HeadlessListboxOptions
|
||||
class="absolute z-100 max-h-60 w-auto border-2px border-t-0 overflow-auto bg-app-black text-app-white focus:outline-none sm:text-sm"
|
||||
class="absolute z-100 max-h-60 w-fit border-2px border-t-0 overflow-auto bg-app-black text-app-white focus:outline-none sm:text-sm"
|
||||
>
|
||||
<HeadlessListboxOption
|
||||
v-for="option in props.options"
|
||||
|
|
|
@ -3,7 +3,7 @@ const isHovered = ref(false)
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div app-container>
|
||||
<div w-full>
|
||||
<div mt-48px>
|
||||
<hr
|
||||
border-t-2px
|
||||
|
@ -13,7 +13,8 @@ const isHovered = ref(false)
|
|||
>
|
||||
</div>
|
||||
<div
|
||||
p-24px
|
||||
app-container
|
||||
pt-24px
|
||||
pb-8px
|
||||
flex
|
||||
items-center
|
||||
|
@ -121,8 +122,8 @@ const isHovered = ref(false)
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
app-container
|
||||
text="14px sm:16px"
|
||||
px-24px
|
||||
grid
|
||||
grid-cols-2
|
||||
sm:grid-cols-3
|
||||
|
@ -196,7 +197,7 @@ const isHovered = ref(false)
|
|||
w-full
|
||||
>
|
||||
<div
|
||||
px-24px
|
||||
app-container
|
||||
my-16px
|
||||
sm:mt-32px
|
||||
flex
|
||||
|
|
|
@ -19,6 +19,7 @@ const isProjectDetailRoute = computed(() => {
|
|||
z-100
|
||||
bg-black
|
||||
overflow-hidden
|
||||
border="b-1px white/20"
|
||||
>
|
||||
<div
|
||||
relative
|
||||
|
@ -28,7 +29,7 @@ const isProjectDetailRoute = computed(() => {
|
|||
>
|
||||
<img
|
||||
absolute
|
||||
lg="left--30px"
|
||||
lg="left-10px"
|
||||
left--30px
|
||||
top--25px
|
||||
z-101
|
||||
|
|
|
@ -15,6 +15,7 @@ const totalProjectsCount = props.projects.map(g => g.projects.length).reduce((a,
|
|||
items-start
|
||||
>
|
||||
<ProjectGridGroup
|
||||
my-24px lg:my-32px
|
||||
v-for="group in projects"
|
||||
:key="group.title"
|
||||
:group="group"
|
||||
|
|
|
@ -46,7 +46,18 @@ const shownProjects = computed(() => props.group.projects.slice(0, shownProjects
|
|||
@click="groupCollapsed = !groupCollapsed"
|
||||
/>
|
||||
</div>
|
||||
<ProjectGridGroupSort v-if="!groupCollapsed" />
|
||||
<ClientOnly>
|
||||
<ProjectGridGroupSort
|
||||
v-if="!groupCollapsed"
|
||||
:hidden-columns="group.title === 'Social & Communications' ? ['technology'] : []"
|
||||
/>
|
||||
<template #fallback>
|
||||
<div
|
||||
h-48px
|
||||
w-full
|
||||
/>
|
||||
</template>
|
||||
</ClientOnly>
|
||||
<div
|
||||
v-if="!groupCollapsed"
|
||||
grid
|
||||
|
@ -61,6 +72,7 @@ const shownProjects = computed(() => props.group.projects.slice(0, shownProjects
|
|||
<Card
|
||||
v-for="project in group.projects.slice(0, shownProjectsCount)"
|
||||
:key="project.id"
|
||||
:hidden-columns="group.title === 'Social & Communications' ? ['technology'] : []"
|
||||
:project="project"
|
||||
/>
|
||||
|
||||
|
@ -79,9 +91,11 @@ const shownProjects = computed(() => props.group.projects.slice(0, shownProjects
|
|||
text="12px lg:14px"
|
||||
leading="24px lg:32px"
|
||||
font-bold
|
||||
pt-16px
|
||||
pb-24px
|
||||
mt-24px
|
||||
px-16px
|
||||
py-4px
|
||||
text-app-text-grey
|
||||
border="2px white/50"
|
||||
@click="shownProjectsCount += 15"
|
||||
>
|
||||
LOAD MORE
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
<script lang="ts" setup>
|
||||
const props = defineProps<{
|
||||
hiddenColumns?: string[]
|
||||
}>()
|
||||
|
||||
const { filter } = storeToRefs(useData())
|
||||
|
||||
function onChangeSort(sortKey: string) {
|
||||
|
@ -15,6 +19,10 @@ function onChangeSort(sortKey: string) {
|
|||
filter.value.sortDirection = sortKey === 'score' ? 'desc' : 'asc'
|
||||
}
|
||||
|
||||
watch(filter, () => {
|
||||
console.log(filter.value)
|
||||
}, { deep: true })
|
||||
|
||||
const cardTitles = ref< { label: string, sortKey: string, togglable?: boolean }[]>([
|
||||
{ label: 'Usecase', sortKey: 'usecase' },
|
||||
{ label: 'Openess', sortKey: 'openess', togglable: true },
|
||||
|
@ -24,12 +32,21 @@ const cardTitles = ref< { label: string, sortKey: string, togglable?: boolean }[
|
|||
{ label: 'Links', sortKey: 'links' },
|
||||
{ label: 'W3PN Score', sortKey: 'score', togglable: true },
|
||||
])
|
||||
|
||||
const { width } = useWindowSize()
|
||||
const visibleColumnsCount = computed(() => {
|
||||
if (width.value >= 1024)
|
||||
return cardTitles.value.filter(title => !props.hiddenColumns?.includes(title.sortKey)).length + 3
|
||||
else {
|
||||
return 2
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
grid
|
||||
grid-cols="2 lg:10"
|
||||
:style="`grid-template-columns: repeat(${visibleColumnsCount}, 1fr)`"
|
||||
w-full
|
||||
mb-16px
|
||||
>
|
||||
|
@ -76,7 +93,7 @@ const cardTitles = ref< { label: string, sortKey: string, togglable?: boolean }[
|
|||
/>
|
||||
</div>
|
||||
<div
|
||||
v-for="(title, index) in cardTitles"
|
||||
v-for="(title, index) in cardTitles.filter((title) => !hiddenColumns?.includes(title.sortKey))"
|
||||
:key="title.label"
|
||||
lg:flex
|
||||
items-center
|
||||
|
@ -100,8 +117,8 @@ const cardTitles = ref< { label: string, sortKey: string, togglable?: boolean }[
|
|||
<button
|
||||
v-if="title.togglable"
|
||||
type="button"
|
||||
:class="[title.sortKey === filter.sortby ? filter.sortDirection === 'desc' ? 'i-ic-baseline-arrow-drop-up'
|
||||
: 'i-ic-baseline-arrow-drop-down'
|
||||
:class="[title.sortKey === filter.sortby ? filter.sortDirection === 'desc' ? 'i-ic-baseline-arrow-drop-down'
|
||||
: 'i-ic-baseline-arrow-drop-up'
|
||||
: 'i-ic-baseline-arrow-drop-down']"
|
||||
text-24px
|
||||
/>
|
||||
|
|
|
@ -114,10 +114,9 @@ const logo = props.project?.logos?.at(0)?.url
|
|||
flex
|
||||
justify-center
|
||||
lg:grid
|
||||
grid-cols-10
|
||||
grid-cols-8
|
||||
w-full
|
||||
items-center
|
||||
mt-4px
|
||||
>
|
||||
<h2
|
||||
hidden
|
||||
|
@ -126,10 +125,10 @@ const logo = props.project?.logos?.at(0)?.url
|
|||
Usecases:
|
||||
</h2>
|
||||
<p
|
||||
mt-8px
|
||||
mb-16px
|
||||
mb-8px
|
||||
lg="mb-0 mt-0"
|
||||
text-app-white
|
||||
col-span-9
|
||||
col-span-7
|
||||
>
|
||||
{{ projectUsecases }}
|
||||
</p>
|
||||
|
@ -143,15 +142,13 @@ const logo = props.project?.logos?.at(0)?.url
|
|||
order-3
|
||||
lg:order-2
|
||||
w-full
|
||||
md:ml-0px
|
||||
ml-18px
|
||||
>
|
||||
<div
|
||||
flex
|
||||
flex-col
|
||||
gap-4px
|
||||
lg:grid
|
||||
lg:grid-cols-10
|
||||
lg:grid-cols-8
|
||||
lg:items-center
|
||||
w-full
|
||||
>
|
||||
|
@ -160,7 +157,7 @@ const logo = props.project?.logos?.at(0)?.url
|
|||
</h2>
|
||||
<p
|
||||
text-app-white
|
||||
col-span-9
|
||||
col-span-7
|
||||
>
|
||||
{{ projectCategories }}
|
||||
</p>
|
||||
|
@ -170,7 +167,7 @@ const logo = props.project?.logos?.at(0)?.url
|
|||
flex-col
|
||||
gap-4px
|
||||
lg:grid
|
||||
lg:grid-cols-10
|
||||
lg:grid-cols-8
|
||||
lg:items-center
|
||||
w-full
|
||||
>
|
||||
|
@ -179,7 +176,7 @@ const logo = props.project?.logos?.at(0)?.url
|
|||
</h2>
|
||||
<p
|
||||
text-app-white
|
||||
col-span-9
|
||||
col-span-7
|
||||
>
|
||||
{{ projectEcosystems }}
|
||||
</p>
|
||||
|
@ -192,6 +189,8 @@ const logo = props.project?.logos?.at(0)?.url
|
|||
items-center
|
||||
order-2
|
||||
lg:order-3
|
||||
lg:ml-0
|
||||
ml--12px
|
||||
>
|
||||
<div
|
||||
flex
|
||||
|
|
|
@ -30,19 +30,13 @@ const props = defineProps<{
|
|||
v-for="member in members"
|
||||
:key="member.name"
|
||||
>
|
||||
<div
|
||||
<NuxtLink
|
||||
:to="member.link"
|
||||
target="_blank"
|
||||
flex
|
||||
items-center
|
||||
gap-12px
|
||||
>
|
||||
<!-- <template v-if="member.link">
|
||||
<NuxtImg
|
||||
:src="member.link"
|
||||
width="48"
|
||||
height="48"
|
||||
:alt="member.name"
|
||||
/>
|
||||
</template> -->
|
||||
<div
|
||||
flex
|
||||
items-center
|
||||
|
@ -67,7 +61,7 @@ const props = defineProps<{
|
|||
font-700
|
||||
>{{ member.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
|
|
|
@ -14,7 +14,7 @@ const props = defineProps<{
|
|||
const emits = defineEmits(['selected'])
|
||||
|
||||
const colors = [
|
||||
'#EA171D', // 0-10%
|
||||
'#EA171D', // 1-10%
|
||||
'#FB2D00', // 11-20%
|
||||
'#FD6515', // 21-30%
|
||||
'#FD941A', // 31-40%
|
||||
|
@ -29,6 +29,8 @@ const colors = [
|
|||
const backgroundColorByScore = computed(() => {
|
||||
if (props.percentage === 100)
|
||||
return '#42FF00'
|
||||
if (props.percentage === 0)
|
||||
return '#494949'
|
||||
const normalizedPercentage = Math.min(Math.max(props.percentage, 0), 100)
|
||||
const colorIndex = Math.floor(normalizedPercentage / 10)
|
||||
return colors[colorIndex]
|
||||
|
|
|
@ -61,10 +61,16 @@ export const useData = defineStore('data', () => {
|
|||
ranks: Rank[]
|
||||
}>('/api/data')
|
||||
data.projects.forEach(project => project.ratings = generateProjectRating(project))
|
||||
projects.value = data.projects.map(project => ({
|
||||
projects.value = data.projects.map((project) => {
|
||||
const totalPoints = project.ratings?.reduce((a, b) => a + b.percentagePoints, 0) || 0
|
||||
const numberOfRatings = project.ratings?.length || 1 // Avoid division by zero
|
||||
const averagePercentage = totalPoints / numberOfRatings
|
||||
return {
|
||||
...project,
|
||||
percentage: Math.round((project.ratings?.reduce((a, b) => a + b.points, 0) || 0) / 1.5),
|
||||
})).filter(p => p.name)
|
||||
percentage: Math.min(Math.max(Math.round(averagePercentage), 0), 100), // Ensure within 0-100%
|
||||
}
|
||||
}).filter(p => p.name)
|
||||
console.log(projects.value.filter(p => p.name === 'ETHBerlin'))
|
||||
const projectCategories = projects.value.map(p => p.categories).flat()
|
||||
categories.value = data.categories.filter(c => projectCategories.includes(c.id))
|
||||
usecases.value = data.usecases
|
||||
|
@ -72,7 +78,6 @@ export const useData = defineStore('data', () => {
|
|||
assets.value = data.assets
|
||||
features.value = data.features
|
||||
ranks.value = data.ranks
|
||||
|
||||
projectPhase.value = data.phases?.map(p => ({ id: p.id.toLowerCase(), name: p.name }))
|
||||
assetCustody.value = data.custodys.map(a => ({ id: a.id.toLowerCase(), name: a.name }))
|
||||
signInRequirments.value = data.requirements.map(s => ({ id: s.id.toLowerCase(), name: s.name }))
|
||||
|
@ -173,9 +178,6 @@ export const useData = defineStore('data', () => {
|
|||
case 'live-on-mainnet':
|
||||
return project.project_phase === 'mainnet'
|
||||
}
|
||||
// const features = project.technology?.features?.map(f => f.toLowerCase()) || []
|
||||
// if (!features.includes(selectedFeature))
|
||||
// return false
|
||||
}
|
||||
|
||||
return true
|
||||
|
@ -191,7 +193,7 @@ export const useData = defineStore('data', () => {
|
|||
if (!projects.value) return []
|
||||
|
||||
const query = filter.query.toLowerCase()
|
||||
const sortDirection = filter.sortDirection === 'asc' ? 1 : -1
|
||||
const sortDirection = filter.sortDirection === 'desc' ? 1 : -1
|
||||
const sortBy = filter.sortby
|
||||
|
||||
const filteredShallowProjects = getProjectsByFilters({ shallow: true })
|
||||
|
@ -200,10 +202,11 @@ export const useData = defineStore('data', () => {
|
|||
})
|
||||
.sort((a, b) => {
|
||||
if (sortBy === 'score') {
|
||||
return sortDirection * (a.percentage - b.percentage)
|
||||
return sortDirection * (b.percentage - a.percentage)
|
||||
}
|
||||
|
||||
if (sortBy === 'title') {
|
||||
// sortDirection is reversed because the default sort is descending
|
||||
return sortDirection * a.title1.toLowerCase().localeCompare(b.title1.toLowerCase())
|
||||
}
|
||||
|
||||
|
@ -248,11 +251,17 @@ export const useData = defineStore('data', () => {
|
|||
const projectRatings: ProjectRating[] = ranks.value?.map((rank) => {
|
||||
let rankPoints = 0
|
||||
let maxPoints = 0
|
||||
const ratingStats: ProjectRatingItem[] = []
|
||||
|
||||
const ratingStats: ProjectRatingItem[] = rank.references?.map((ref) => {
|
||||
rank.references?.forEach((ref) => {
|
||||
let isValid = false
|
||||
const field = ref.field.includes('.') ? getNestedField(project, ref.field) : project[ref.field]
|
||||
|
||||
// adds only one valid social link
|
||||
if (ref.label.positive === 'Link' && ref.label.name !== 'Documentation')
|
||||
if (ratingStats.some(r => r.positive === 'Link' && r.label !== 'Documentation' && r.value) || !field)
|
||||
return
|
||||
|
||||
let value
|
||||
let positive
|
||||
let negative
|
||||
|
@ -285,14 +294,15 @@ export const useData = defineStore('data', () => {
|
|||
rankPoints += isValid ? ref.points : 0
|
||||
maxPoints += ref.points
|
||||
|
||||
return {
|
||||
ratingStats.push({
|
||||
isValid,
|
||||
label: ref.label.name,
|
||||
positive: positive ? positive : ref.label.positive,
|
||||
negative: negative ? negative : ref.label.negative,
|
||||
value,
|
||||
} as ProjectRatingItem
|
||||
} as ProjectRatingItem)
|
||||
})
|
||||
|
||||
return {
|
||||
type: rank.id,
|
||||
name: rank.name,
|
||||
|
|
|
@ -81,10 +81,10 @@ watch([scrollY, top, y], (newValues, oldValues) => {
|
|||
md:justify-between
|
||||
md:items-center
|
||||
gap-16px
|
||||
mb="16px md:32px"
|
||||
>
|
||||
<SearchBox
|
||||
flex-1
|
||||
class="w-full lg:max-w-1/3"
|
||||
placeholder:text-app-text-grey
|
||||
:placeholder="`Search in ${filteredProjectsCount} Projects`"
|
||||
/>
|
||||
|
@ -98,7 +98,7 @@ watch([scrollY, top, y], (newValues, oldValues) => {
|
|||
pb-274px
|
||||
mb--250px
|
||||
md="overflow-x-visible pb-0 mb-0"
|
||||
class="no-scrollbar"
|
||||
class="no-scrollbar lg:w-full lg:max-w-2/3"
|
||||
>
|
||||
<CategorySelectBox
|
||||
v-model="selectedCategoryId"
|
||||
|
|
|
@ -34,13 +34,12 @@ useSeoMeta({
|
|||
<div v-if="project">
|
||||
<div
|
||||
app-container
|
||||
px-16px
|
||||
>
|
||||
<div
|
||||
flex
|
||||
flex-col
|
||||
gap-48px
|
||||
mt-54px
|
||||
mt-32px
|
||||
>
|
||||
<div>
|
||||
<div>
|
||||
|
|
|
@ -10,7 +10,7 @@ import { collections } from './unocss.config.collections'
|
|||
export default defineConfig({
|
||||
shortcuts: [
|
||||
{
|
||||
'app-container': 'px-12px sm:px-1.5rem max-w-1140px 2xl:max-w-1400px m-auto w-full',
|
||||
'app-container': 'px-24px sm:px-1.5rem max-w-1140px 2xl:max-w-1400px m-auto w-full',
|
||||
'custom-link': 'text-app-text-grey hover:underline underline-offset-4px text-18px font-400 leading-32px cursor-pointer',
|
||||
},
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue