mirror of
https://github.com/web3privacy/explorer-app.git
synced 2024-10-15 16:46:26 +02:00
feat(rating): calculate and show rating based on github data
This commit is contained in:
parent
04750862c5
commit
161ba35ff6
5 changed files with 298 additions and 173 deletions
|
@ -1,12 +1,14 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { ProjectShallow } from '~/types'
|
import type { ProjectRating, ProjectShallow } from '~/types'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
project: ProjectShallow
|
project: ProjectShallow
|
||||||
}>()
|
}>()
|
||||||
const { switcher } = storeToRefs(useData())
|
const { switcher, ecosystems } = storeToRefs(useData())
|
||||||
|
|
||||||
const projectItems = ['Swap,Mixer', { label: 'Openess', rating: props.project.ratings.openess, type: 'openess' }, { label: 'Technology', rating: props.project.ratings.technology, type: 'technology' }, { label: 'Privacy', rating: props.project.ratings.privacy, type: 'privacy' }, 'Ecosystem', 'Links']
|
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: ['Links'], type: 'array' }]
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -26,7 +28,7 @@ const projectItems = ['Swap,Mixer', { label: 'Openess', rating: props.project.ra
|
||||||
>
|
>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
col-span="1 lg:3"
|
col-span="1 lg:2"
|
||||||
flex
|
flex
|
||||||
items-center
|
items-center
|
||||||
gap="12px lg:16px"
|
gap="12px lg:16px"
|
||||||
|
@ -76,62 +78,80 @@ const projectItems = ['Swap,Mixer', { label: 'Openess', rating: props.project.ra
|
||||||
leading-16px
|
leading-16px
|
||||||
lg:hidden
|
lg:hidden
|
||||||
>
|
>
|
||||||
Usecases
|
{{ project.usecases?.join(', ') }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<ClientOnly>
|
||||||
v-for="(projectItem, index) of projectItems"
|
<div
|
||||||
:key="projectItem.toString()"
|
v-for="(projectItem, index) of projectItems"
|
||||||
hidden
|
:key="projectItem.label.toString()"
|
||||||
lg:flex
|
hidden
|
||||||
items-center
|
lg:flex
|
||||||
justify-start
|
items-center
|
||||||
text-14px
|
text-14px
|
||||||
leading-24px
|
leading-24px
|
||||||
>
|
:class="{ 'col-span-1 lg:col-span-2': index === 0 }"
|
||||||
<p
|
|
||||||
v-if="typeof projectItem === 'string'"
|
|
||||||
text-app-text-grey
|
|
||||||
>
|
>
|
||||||
{{ projectItem }}
|
<p
|
||||||
</p>
|
v-if="projectItem.type === 'array'"
|
||||||
<ProjectRating
|
text-app-text-grey
|
||||||
v-else
|
>
|
||||||
:score="index"
|
{{ (projectItem.label as string[] || []).join(', ') }}
|
||||||
:rating="projectItem.rating"
|
</p>
|
||||||
:type="projectItem.type"
|
<div
|
||||||
/>
|
v-if="projectItem.type === 'ecosystem'"
|
||||||
</div>
|
flex
|
||||||
<div
|
items-center
|
||||||
flex
|
justify-start
|
||||||
items-center
|
gap-2px
|
||||||
justify-end
|
>
|
||||||
w-full
|
<NuxtImg
|
||||||
gap-16px
|
v-for="ecosystem of projectItem.label"
|
||||||
>
|
:key="ecosystem"
|
||||||
<UnoIcon
|
:src="ecosystem"
|
||||||
block
|
w-24px
|
||||||
lg:hidden
|
h-24px
|
||||||
i-iconoir-internet
|
rounded-full
|
||||||
text="24px"
|
/>
|
||||||
/>
|
</div>
|
||||||
|
<ProjectRating
|
||||||
|
v-if="projectItem.type! === 'rating' && projectItem.rating"
|
||||||
|
:percentage="projectItem.rating.points"
|
||||||
|
:rating="projectItem.rating"
|
||||||
|
:type="projectItem.rating.type"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
flex
|
flex
|
||||||
items-center
|
items-center
|
||||||
justify-center
|
justify-end
|
||||||
border="2px app-white"
|
|
||||||
text="14px md:18px"
|
|
||||||
leading="24px md:32px"
|
|
||||||
max-h-="28px md:32px"
|
|
||||||
max-w="48px md:56px"
|
|
||||||
w-full
|
w-full
|
||||||
font-700
|
gap-16px
|
||||||
whitespace-nowrap
|
|
||||||
>
|
>
|
||||||
{{ project.percentage }} %
|
<UnoIcon
|
||||||
|
block
|
||||||
|
lg:hidden
|
||||||
|
i-iconoir-internet
|
||||||
|
text="24px"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
flex
|
||||||
|
items-center
|
||||||
|
justify-center
|
||||||
|
border="2px app-white"
|
||||||
|
text="14px md:18px"
|
||||||
|
leading="24px md:32px"
|
||||||
|
max-h-="28px md:32px"
|
||||||
|
max-w="48px md:56px"
|
||||||
|
w-full
|
||||||
|
font-700
|
||||||
|
whitespace-nowrap
|
||||||
|
>
|
||||||
|
{{ project.percentage }} %
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</ClientOnly>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
|
|
@ -1,22 +1,31 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { OpenessRating, TechnologyRating, PrivacyRating } from '~/types'
|
import type { ProjectRating } from '~/types'
|
||||||
|
|
||||||
// Define props for score
|
// Define props for score
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
rating: TechnologyRating | OpenessRating | PrivacyRating
|
rating: ProjectRating
|
||||||
type: string
|
percentage: number
|
||||||
score: number
|
|
||||||
compact?: boolean
|
compact?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
// Determine the color by score
|
const colors = [
|
||||||
const colorByScore = props.score <= 1
|
'#ff0000', // 0-10%
|
||||||
? 'bg-app-bg-rating-red'
|
'#ff4500', // 11-20%
|
||||||
: props.score === 2
|
'#ff8c00', // 21-30%
|
||||||
? 'bg-app-bg-rating-orange'
|
'#ffd700', // 31-40%
|
||||||
: props.score === 3
|
'#adff2f', // 41-50%
|
||||||
? 'bg-app-bg-rating-yellow'
|
'#7fff00', // 51-60%
|
||||||
: 'bg-app-bg-rating-green'
|
'#00ff00', // 61-70%
|
||||||
|
'#32cd32', // 71-80%
|
||||||
|
'#00fa9a', // 81-90%
|
||||||
|
'#00ffff', // 91-100%
|
||||||
|
]
|
||||||
|
|
||||||
|
const backgroundColorByScore = computed(() => {
|
||||||
|
const normalizedPercentage = Math.min(Math.max(props.percentage, 0), 100)
|
||||||
|
const colorIndex = Math.floor(normalizedPercentage / 10)
|
||||||
|
return colors[colorIndex]
|
||||||
|
})
|
||||||
|
|
||||||
const isPopoverVisible = ref(false)
|
const isPopoverVisible = ref(false)
|
||||||
|
|
||||||
|
@ -34,40 +43,6 @@ const hidePopover = () => {
|
||||||
isPopoverVisible.value = false
|
isPopoverVisible.value = false
|
||||||
}, 100) // Delay of 200ms before hiding
|
}, 100) // Delay of 200ms before hiding
|
||||||
}
|
}
|
||||||
|
|
||||||
function currentRating<T>(): T {
|
|
||||||
if (props.type === 'privacy')
|
|
||||||
return props.rating as T
|
|
||||||
if (props.type === 'technology')
|
|
||||||
return props.rating as T
|
|
||||||
return props.rating as T
|
|
||||||
}
|
|
||||||
|
|
||||||
function items(): { label: string, condition: boolean, positive: string, negative: string, link?: string }[] {
|
|
||||||
if (props.type === 'privacy')
|
|
||||||
return [
|
|
||||||
{ label: 'KYC', condition: !!currentRating<PrivacyRating>().no_kyc, positive: 'No', negative: 'Yes' },
|
|
||||||
{ label: 'Compliance', condition: !!currentRating<PrivacyRating>().no_compliance, positive: 'No', negative: 'OFAC' },
|
|
||||||
{ label: 'Privacy policy', condition: !!currentRating<PrivacyRating>().policy, positive: 'Link', negative: 'Not available', link: currentRating<PrivacyRating>().policy },
|
|
||||||
{ label: 'Default privacy', condition: !!currentRating<PrivacyRating>().default_privacy, positive: 'Yes', negative: 'No' },
|
|
||||||
]
|
|
||||||
if (props.type === 'technology')
|
|
||||||
return [
|
|
||||||
{ label: 'Mainnet', condition: !!currentRating<TechnologyRating>().mainnet, positive: 'Yes', negative: 'No' },
|
|
||||||
{ label: 'Opensource', condition: !!currentRating<TechnologyRating>().opensource, positive: 'Yes', negative: 'No' },
|
|
||||||
{ label: 'Asset custody', condition: !!currentRating<TechnologyRating>().assets, positive: 'None', negative: 'Custodial' },
|
|
||||||
{ label: 'Upgradability', condition: !!currentRating<TechnologyRating>().no_pgradability, positive: 'Disabled', negative: 'Enabled' },
|
|
||||||
{ label: 'Audits', condition: currentRating<TechnologyRating>().audits >= 1, positive: `${currentRating<TechnologyRating>().audits} ${currentRating<TechnologyRating>().audits > 1 ? 'Audits' : 'Audit'}`, negative: 'None' },
|
|
||||||
]
|
|
||||||
return [
|
|
||||||
{ label: 'Documentation', condition: !!currentRating<OpenessRating>().documentation, positive: 'Link', negative: 'Not available', link: currentRating<OpenessRating>().documentation },
|
|
||||||
{ label: 'Github', condition: !!currentRating<OpenessRating>().github, positive: 'Link', negative: 'Not available', link: currentRating<OpenessRating>().github },
|
|
||||||
{ label: 'Socials', condition: !!currentRating<OpenessRating>().socials, positive: 'Link', negative: 'Not available', link: currentRating<OpenessRating>().socials },
|
|
||||||
{ label: 'Whitepaper', condition: !!currentRating<OpenessRating>().whitepaper, positive: 'Link', negative: 'Not available', link: currentRating<OpenessRating>().whitepaper },
|
|
||||||
{ label: 'Team', condition: currentRating<OpenessRating>().team >= 1, positive: `${currentRating<OpenessRating>().team} ${currentRating<OpenessRating>().team > 1 ? 'Members' : 'Member'}`, negative: 'Anonymous' },
|
|
||||||
{ label: 'Funding', condition: currentRating<OpenessRating>().funding >= 1, positive: `${currentRating<OpenessRating>().funding} ${currentRating<OpenessRating>().team > 1 ? 'Investments' : 'Investment'}`, negative: 'Not available' },
|
|
||||||
]
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -76,21 +51,18 @@ function items(): { label: string, condition: boolean, positive: string, negativ
|
||||||
<div
|
<div
|
||||||
flex
|
flex
|
||||||
items-center
|
items-center
|
||||||
gap-4px
|
|
||||||
p-12px
|
p-12px
|
||||||
|
gap-4px
|
||||||
hover:bg-app-bg-rating-hover
|
hover:bg-app-bg-rating-hover
|
||||||
hover:rounded-8px
|
hover:rounded-8px
|
||||||
@mouseenter="showPopover"
|
@mouseenter="showPopover"
|
||||||
@mouseleave="hidePopover"
|
@mouseleave="hidePopover"
|
||||||
>
|
>
|
||||||
<!-- Render the score points -->
|
|
||||||
<div
|
<div
|
||||||
v-for="point of 5"
|
v-for="point of [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]"
|
||||||
:key="point"
|
:key="point"
|
||||||
w="10px lg:12px"
|
:style="`background-color: ${percentage >= point * 10 ? backgroundColorByScore : '#494949'}`"
|
||||||
h="10px lg:12px"
|
:class="[compact ? 'h-8px lg:h-10px w-4px lg:w-5px' : 'w-5px lg:w-6px h-10px lg:h-12px', point % 2 === 0 ? 'rounded-l-2px' : 'rounded-r-2px ml--4px', 'bg-app-bg-rating-default']"
|
||||||
rounded-2px
|
|
||||||
:class="[point <= score ? colorByScore : 'bg-app-bg-rating-default', compact ? 'h-8px lg:h-10px w-8px lg:w-10px' : 'w-10px lg:w-12px h-10px lg:h-12px']"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -117,7 +89,7 @@ function items(): { label: string, condition: boolean, positive: string, negativ
|
||||||
@mouseleave="hidePopover"
|
@mouseleave="hidePopover"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-for="item in items()"
|
v-for="item in rating.items"
|
||||||
:key="item.label"
|
:key="item.label"
|
||||||
flex
|
flex
|
||||||
justify-between
|
justify-between
|
||||||
|
@ -125,7 +97,7 @@ function items(): { label: string, condition: boolean, positive: string, negativ
|
||||||
text-12px
|
text-12px
|
||||||
font-700
|
font-700
|
||||||
leading-20px
|
leading-20px
|
||||||
:class="{ 'text-app-text-rating-negative': !item.condition }"
|
:class="{ 'text-app-text-rating-negative': !item.isValid }"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
flex
|
flex
|
||||||
|
@ -133,24 +105,24 @@ function items(): { label: string, condition: boolean, positive: string, negativ
|
||||||
gap-6px
|
gap-6px
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
:class="[item.condition ? 'i-ic-sharp-thumb-up' : 'i-ic-sharp-thumb-down']"
|
:class="[item.isValid ? 'i-ic-sharp-thumb-up' : 'i-ic-sharp-thumb-down']"
|
||||||
text-20px
|
text-20px
|
||||||
mt--4px
|
mt--4px
|
||||||
/>
|
/>
|
||||||
{{ item.label }}
|
{{ item.label }}
|
||||||
</div>
|
</div>
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
v-if="item.link"
|
v-if="item.positive === 'Link'"
|
||||||
:to="item.link"
|
:to="item.value"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
external
|
external
|
||||||
underline
|
underline
|
||||||
@click.stop
|
@click.stop
|
||||||
>
|
>
|
||||||
Link
|
{{ item.positive }}
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
{{ item.condition ? item.positive : item.negative }}
|
{{ item.isValid ? item.positive : item.negative }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import type { Category, Project, ProjectShallow } from '~/types'
|
import type { Category, Project, ProjectRating, ProjectRatingItem, ProjectShallow } from '~/types'
|
||||||
import type { Asset } from '~/types/asset'
|
import type { Asset } from '~/types/asset'
|
||||||
import type { Ecosystem } from '~/types/ecosystem'
|
import type { Ecosystem } from '~/types/ecosystem'
|
||||||
import type { Feature } from '~/types/feature'
|
import type { Feature } from '~/types/feature'
|
||||||
|
import type { Rank } from '~/types/rank'
|
||||||
import type { Usecase } from '~/types/usecase'
|
import type { Usecase } from '~/types/usecase'
|
||||||
|
|
||||||
export const useData = defineStore('data', () => {
|
export const useData = defineStore('data', () => {
|
||||||
|
@ -11,21 +12,34 @@ export const useData = defineStore('data', () => {
|
||||||
const assets = useState<Asset[]>('assets')
|
const assets = useState<Asset[]>('assets')
|
||||||
const ecosystems = useState<Ecosystem[]>('ecosystems')
|
const ecosystems = useState<Ecosystem[]>('ecosystems')
|
||||||
const projects = useState<Project[]>('projects')
|
const projects = useState<Project[]>('projects')
|
||||||
|
const ranks = useState<Rank[]>('ranks')
|
||||||
|
|
||||||
const selectedCategoryId = useState(() => 'all')
|
const selectedCategoryId = useState(() => 'all')
|
||||||
|
const selectedUsecaseId = useState(() => 'all')
|
||||||
|
const selectedEcosystemId = useState(() => 'all')
|
||||||
|
const selectedAssetsUsedId = useState(() => 'all')
|
||||||
|
const selectedFeaturesId = useState(() => 'all')
|
||||||
|
|
||||||
const filter = reactive({
|
const filter = reactive({
|
||||||
query: '',
|
query: '',
|
||||||
sortby: 'atoz',
|
sortby: 'score',
|
||||||
|
sortDirection: 'desc',
|
||||||
})
|
})
|
||||||
const switcher = ref(true)
|
const switcher = ref(true)
|
||||||
|
|
||||||
watch(selectedCategoryId, () => {
|
watch([selectedCategoryId, selectedUsecaseId, selectedEcosystemId, selectedAssetsUsedId, selectedFeaturesId], () => {
|
||||||
if (selectedCategoryId.value !== 'all')
|
if (selectedCategoryId.value !== 'all' || selectedUsecaseId.value !== 'all' || selectedEcosystemId.value !== 'all' || selectedAssetsUsedId.value !== 'all' || selectedFeaturesId.value !== 'all')
|
||||||
filter.query = ''
|
filter.query = ''
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(filter, () => {
|
watch(filter, () => {
|
||||||
if (filter.query !== '')
|
if (filter.query !== '') {
|
||||||
selectedCategoryId.value = 'all'
|
selectedCategoryId.value = 'all'
|
||||||
|
selectedUsecaseId.value = 'all'
|
||||||
|
selectedEcosystemId.value = 'all'
|
||||||
|
selectedAssetsUsedId.value = 'all'
|
||||||
|
selectedFeaturesId.value = 'all'
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
|
@ -37,32 +51,11 @@ export const useData = defineStore('data', () => {
|
||||||
ecosystems: Ecosystem[]
|
ecosystems: Ecosystem[]
|
||||||
assets: Asset[]
|
assets: Asset[]
|
||||||
features: Feature[]
|
features: Feature[]
|
||||||
|
ranks: Rank[]
|
||||||
}>('/api/data')
|
}>('/api/data')
|
||||||
projects.value = data.projects.map(project => ({
|
projects.value = data.projects.map(project => ({
|
||||||
...project,
|
...project,
|
||||||
ratings: {
|
ratings: generateProjectRating(project),
|
||||||
openess: {
|
|
||||||
documentation: 'Link',
|
|
||||||
funding: 0,
|
|
||||||
github: 'Link',
|
|
||||||
socials: '',
|
|
||||||
team: 1,
|
|
||||||
whitepaper: '',
|
|
||||||
},
|
|
||||||
technology: {
|
|
||||||
mainnet: true,
|
|
||||||
opensource: true,
|
|
||||||
assets: false,
|
|
||||||
audits: 0,
|
|
||||||
no_pgradability: true,
|
|
||||||
},
|
|
||||||
privacy: {
|
|
||||||
no_kyc: true,
|
|
||||||
no_compliance: true,
|
|
||||||
default_privacy: false,
|
|
||||||
policy: 'Link',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})).filter(p => p.name)
|
})).filter(p => p.name)
|
||||||
categories.value = data.categories.map((c) => {
|
categories.value = data.categories.map((c) => {
|
||||||
c.projectsCount = projects.value.filter(p =>
|
c.projectsCount = projects.value.filter(p =>
|
||||||
|
@ -74,12 +67,12 @@ export const useData = defineStore('data', () => {
|
||||||
ecosystems.value = data.ecosystems
|
ecosystems.value = data.ecosystems
|
||||||
assets.value = data.assets
|
assets.value = data.assets
|
||||||
features.value = data.features
|
features.value = data.features
|
||||||
|
ranks.value = data.ranks
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,7 +91,7 @@ export const useData = defineStore('data', () => {
|
||||||
id: project.id,
|
id: project.id,
|
||||||
title1: project.name,
|
title1: project.name,
|
||||||
description: project.description ?? 'N/A',
|
description: project.description ?? 'N/A',
|
||||||
percentage: Math.floor(Math.random() * 91),
|
percentage: Math.round((project.ratings?.reduce((a, b) => a + b.points, 0) || 0) / 1.5),
|
||||||
forum: project.links?.forum,
|
forum: project.links?.forum,
|
||||||
explorer: project.links?.block_explorer,
|
explorer: project.links?.block_explorer,
|
||||||
twitter: project.links?.twitter,
|
twitter: project.links?.twitter,
|
||||||
|
@ -114,38 +107,32 @@ export const useData = defineStore('data', () => {
|
||||||
image: project.logos?.[0]?.url ?? '',
|
image: project.logos?.[0]?.url ?? '',
|
||||||
anonymity: true,
|
anonymity: true,
|
||||||
categories: project.categories,
|
categories: project.categories,
|
||||||
ratings: {
|
usecases: project.usecases,
|
||||||
openess: {
|
ecosystem: project.ecosystem,
|
||||||
documentation: 'Link',
|
assets_used: project.assets_used,
|
||||||
funding: 0,
|
ratings: project.ratings,
|
||||||
github: 'Link',
|
|
||||||
socials: '',
|
|
||||||
team: 1,
|
|
||||||
whitepaper: '',
|
|
||||||
},
|
|
||||||
technology: {
|
|
||||||
mainnet: true,
|
|
||||||
opensource: true,
|
|
||||||
assets: false,
|
|
||||||
audits: 0,
|
|
||||||
no_pgradability: true,
|
|
||||||
},
|
|
||||||
privacy: {
|
|
||||||
no_kyc: true,
|
|
||||||
no_compliance: true,
|
|
||||||
default_privacy: false,
|
|
||||||
policy: 'Link',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const shallowProjects = computed(() => projects.value.map(project => projectToShallow(project)))
|
const shallowProjects = computed(() => projects.value.map(project => projectToShallow(project)))
|
||||||
|
|
||||||
const getProjectsByCategory = <T extends ProjectShallow>(id: string, options?: { shallow: boolean }): T[] => {
|
const getProjectsByFilters = <T extends ProjectShallow>(options?: { shallow: boolean }): T[] => {
|
||||||
if (id === 'all')
|
const filteredProjects = projects.value
|
||||||
return projects.value.map(project => projectToShallow(project)) as T[]
|
.filter(project =>
|
||||||
else
|
selectedCategoryId.value !== 'all' ? project.categories.includes(selectedCategoryId.value) : true,
|
||||||
return projects.value.filter(project => project.categories?.includes(id)).map(project => options?.shallow ? projectToShallow(project) : project) as T[]
|
)
|
||||||
|
.filter(project =>
|
||||||
|
selectedUsecaseId.value !== 'all' ? project.usecases?.map(u => u.toLowerCase()).includes(selectedUsecaseId.value.toLowerCase()) : true,
|
||||||
|
)
|
||||||
|
.filter(project =>
|
||||||
|
selectedEcosystemId.value !== 'all' ? project.ecosystem?.map(e => e.toLowerCase()).includes(selectedEcosystemId.value.toLowerCase()) : true,
|
||||||
|
)
|
||||||
|
.filter(project =>
|
||||||
|
selectedAssetsUsedId.value !== 'all' ? project.assets_used?.map(a => a.toLowerCase()).includes(selectedAssetsUsedId.value.toLowerCase()) : true,
|
||||||
|
)
|
||||||
|
.filter(project =>
|
||||||
|
selectedFeaturesId.value !== 'all' ? project.technology?.features?.map(f => f.toLowerCase()).includes(selectedFeaturesId.value.toLowerCase()) : true,
|
||||||
|
)
|
||||||
|
return (filteredProjects.map(project => options?.shallow ? projectToShallow(project) : project) as T[])
|
||||||
}
|
}
|
||||||
|
|
||||||
const getProjectById = <T extends Project | ProjectShallow>(id: string, options?: { shallow: boolean }): T => {
|
const getProjectById = <T extends Project | ProjectShallow>(id: string, options?: { shallow: boolean }): T => {
|
||||||
|
@ -159,7 +146,7 @@ export const useData = defineStore('data', () => {
|
||||||
|
|
||||||
const query = filter.query.toLowerCase()
|
const query = filter.query.toLowerCase()
|
||||||
|
|
||||||
const filteredShallowProjects = getProjectsByCategory(selectedCategoryId.value, { shallow: true })
|
const filteredShallowProjects = getProjectsByFilters({ shallow: true })
|
||||||
.filter((project) => {
|
.filter((project) => {
|
||||||
return (
|
return (
|
||||||
project
|
project
|
||||||
|
@ -173,9 +160,23 @@ export const useData = defineStore('data', () => {
|
||||||
return true
|
return true
|
||||||
}).sort((a, b) => {
|
}).sort((a, b) => {
|
||||||
if (filter.sortby === 'score')
|
if (filter.sortby === 'score')
|
||||||
return b.percentage - a.percentage
|
if (filter.sortDirection === 'asc')
|
||||||
if (filter.sortby === 'atoz')
|
return a.percentage - b.percentage
|
||||||
return a.title1.localeCompare(b.title1)
|
else
|
||||||
|
return b.percentage - a.percentage
|
||||||
|
if (filter.sortby === 'title')
|
||||||
|
if (filter.sortDirection === 'asc')
|
||||||
|
return a.title1.toLowerCase().localeCompare(b.title1.toLowerCase())
|
||||||
|
else
|
||||||
|
return b.title1.toLowerCase().localeCompare(a.title1.toLowerCase())
|
||||||
|
if (filter.sortby === 'openess' || filter.sortby === 'technology' || filter.sortby === 'privacy') {
|
||||||
|
const scoreA = a.ratings?.find(r => r.type === filter.sortby)?.points || 0
|
||||||
|
const scoreB = b.ratings?.find(r => r.type === filter.sortby)?.points || 0
|
||||||
|
if (filter.sortDirection === 'asc')
|
||||||
|
return scoreB - scoreA
|
||||||
|
else
|
||||||
|
return scoreA - scoreB
|
||||||
|
}
|
||||||
else
|
else
|
||||||
return 0
|
return 0
|
||||||
})
|
})
|
||||||
|
@ -200,8 +201,69 @@ export const useData = defineStore('data', () => {
|
||||||
|
|
||||||
const filteredProjectsCount = computed(() => filteredProjects.value.length)
|
const filteredProjectsCount = computed(() => filteredProjects.value.length)
|
||||||
|
|
||||||
|
const getNestedField = (project: Project, field: string) => {
|
||||||
|
const fields = field.split('.')
|
||||||
|
|
||||||
|
return fields.reduce((acc: any, curr: string) => {
|
||||||
|
return acc && acc[curr as keyof typeof acc]
|
||||||
|
}, project)
|
||||||
|
}
|
||||||
|
|
||||||
|
const generateProjectRating = (project: Project) => {
|
||||||
|
const projectRatings: ProjectRating[] = ranks.value?.map((rank) => {
|
||||||
|
let rankPoints = 0
|
||||||
|
|
||||||
|
const ratingStats: ProjectRatingItem[] = rank.references?.map((ref) => {
|
||||||
|
let isValid = false
|
||||||
|
const field = ref.field.includes('.') ? getNestedField(project, ref.field) : project[ref.field]
|
||||||
|
|
||||||
|
let value
|
||||||
|
let positive
|
||||||
|
|
||||||
|
if (ref.condition.minLength) {
|
||||||
|
value = (field as any[])?.length
|
||||||
|
if (value) {
|
||||||
|
isValid = value >= ref.condition.minLength
|
||||||
|
positive = `${value} ${ref.positive}${value > 1 ? 's' : ''}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ref.condition.equals) {
|
||||||
|
value = field
|
||||||
|
if (value)
|
||||||
|
isValid = value === ref.condition.equals
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ref.condition.exists) {
|
||||||
|
value = field
|
||||||
|
if (value)
|
||||||
|
isValid = !!value
|
||||||
|
}
|
||||||
|
rankPoints += isValid ? ref.points : 0
|
||||||
|
return {
|
||||||
|
isValid,
|
||||||
|
label: ref.label,
|
||||||
|
positive: positive ? positive : ref.positive,
|
||||||
|
negative: ref.negative,
|
||||||
|
value,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
type: rank.id,
|
||||||
|
name: rank.name,
|
||||||
|
items: ratingStats,
|
||||||
|
points: rankPoints,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return projectRatings
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
selectedCategoryId,
|
selectedCategoryId,
|
||||||
|
selectedUsecaseId,
|
||||||
|
selectedEcosystemId,
|
||||||
|
selectedAssetsUsedId,
|
||||||
|
selectedFeaturesId,
|
||||||
filter,
|
filter,
|
||||||
switcher,
|
switcher,
|
||||||
categories,
|
categories,
|
||||||
|
@ -215,7 +277,6 @@ export const useData = defineStore('data', () => {
|
||||||
filteredProjectsCount,
|
filteredProjectsCount,
|
||||||
fetchData,
|
fetchData,
|
||||||
getProjectById,
|
getProjectById,
|
||||||
getProjectsByCategory,
|
|
||||||
filteredProjects,
|
filteredProjects,
|
||||||
projectToShallow,
|
projectToShallow,
|
||||||
}
|
}
|
||||||
|
|
22
types/rank.ts
Normal file
22
types/rank.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import type { Project } from './project'
|
||||||
|
|
||||||
|
export interface Rank {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
references: Reference[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Reference {
|
||||||
|
field: keyof Project
|
||||||
|
label: string
|
||||||
|
positive: string
|
||||||
|
negative: string
|
||||||
|
condition: Condition
|
||||||
|
points: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Condition {
|
||||||
|
minLength?: number
|
||||||
|
exists?: boolean
|
||||||
|
equals?: boolean | string
|
||||||
|
}
|
50
utils/score.ts
Normal file
50
utils/score.ts
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import type { Project, ProjectIndexable, ProjectShallow } from '~/types'
|
||||||
|
|
||||||
|
const fulfilled = (value: any): boolean => {
|
||||||
|
const type = typeof value
|
||||||
|
switch (type) {
|
||||||
|
case 'string':
|
||||||
|
if (value !== '')
|
||||||
|
return true
|
||||||
|
break
|
||||||
|
case 'object':
|
||||||
|
if (Object.keys(value!).length > 0)
|
||||||
|
return true
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
export const calculateScore = (project: ProjectIndexable | ProjectShallow | Project) => {
|
||||||
|
const criterias: { value: keyof ProjectIndexable, key: keyof ProjectIndexable | '' }[] = [
|
||||||
|
{ value: 'product_readiness', key: '' },
|
||||||
|
{ value: 'github', key: 'links' },
|
||||||
|
{ value: 'docs', key: 'links' },
|
||||||
|
{ value: 'team', key: '' },
|
||||||
|
{ value: 'audits', key: '' },
|
||||||
|
]
|
||||||
|
|
||||||
|
let matched = 0
|
||||||
|
for (let i = 0; i < criterias.length; i++) {
|
||||||
|
let value
|
||||||
|
// value = ((criterias[i].key ?? props.project[criterias[i].value as keyof typeof props.project]) ?? null === null) ? null : (props.project as ProjectIndexable)[criterias[i].key][criterias[i].value]
|
||||||
|
|
||||||
|
const indexableProject = project as ProjectIndexable
|
||||||
|
if (criterias[i].key !== '')
|
||||||
|
value = (indexableProject[criterias[i].key] as any)?.[criterias[i].value]
|
||||||
|
else
|
||||||
|
value = indexableProject?.[criterias[i].value]
|
||||||
|
|
||||||
|
// console.log(props.project?.links?.github);
|
||||||
|
// console.log(Object.keys(props.indexableProject["team"]).length);
|
||||||
|
if (value === null || value === undefined)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if (fulfilled(value))
|
||||||
|
matched++
|
||||||
|
}
|
||||||
|
|
||||||
|
return 100 / criterias.length * matched
|
||||||
|
}
|
Loading…
Reference in a new issue