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>
|
||||
import type { ProjectShallow } from '~/types'
|
||||
import type { ProjectRating, ProjectShallow } from '~/types'
|
||||
|
||||
const props = defineProps<{
|
||||
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>
|
||||
|
||||
<template>
|
||||
|
@ -26,7 +28,7 @@ const projectItems = ['Swap,Mixer', { label: 'Openess', rating: props.project.ra
|
|||
>
|
||||
|
||||
<div
|
||||
col-span="1 lg:3"
|
||||
col-span="1 lg:2"
|
||||
flex
|
||||
items-center
|
||||
gap="12px lg:16px"
|
||||
|
@ -76,31 +78,48 @@ const projectItems = ['Swap,Mixer', { label: 'Openess', rating: props.project.ra
|
|||
leading-16px
|
||||
lg:hidden
|
||||
>
|
||||
Usecases
|
||||
{{ project.usecases?.join(', ') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<ClientOnly>
|
||||
<div
|
||||
v-for="(projectItem, index) of projectItems"
|
||||
:key="projectItem.toString()"
|
||||
:key="projectItem.label.toString()"
|
||||
hidden
|
||||
lg:flex
|
||||
items-center
|
||||
justify-start
|
||||
text-14px
|
||||
leading-24px
|
||||
:class="{ 'col-span-1 lg:col-span-2': index === 0 }"
|
||||
>
|
||||
<p
|
||||
v-if="typeof projectItem === 'string'"
|
||||
v-if="projectItem.type === 'array'"
|
||||
text-app-text-grey
|
||||
>
|
||||
{{ projectItem }}
|
||||
{{ (projectItem.label as string[] || []).join(', ') }}
|
||||
</p>
|
||||
<div
|
||||
v-if="projectItem.type === 'ecosystem'"
|
||||
flex
|
||||
items-center
|
||||
justify-start
|
||||
gap-2px
|
||||
>
|
||||
<NuxtImg
|
||||
v-for="ecosystem of projectItem.label"
|
||||
:key="ecosystem"
|
||||
:src="ecosystem"
|
||||
w-24px
|
||||
h-24px
|
||||
rounded-full
|
||||
/>
|
||||
</div>
|
||||
<ProjectRating
|
||||
v-else
|
||||
:score="index"
|
||||
v-if="projectItem.type! === 'rating' && projectItem.rating"
|
||||
:percentage="projectItem.rating.points"
|
||||
:rating="projectItem.rating"
|
||||
:type="projectItem.type"
|
||||
:type="projectItem.rating.type"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
|
@ -132,6 +151,7 @@ const projectItems = ['Swap,Mixer', { label: 'Openess', rating: props.project.ra
|
|||
{{ project.percentage }} %
|
||||
</div>
|
||||
</div>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
|
||||
</NuxtLink>
|
||||
|
|
|
@ -1,22 +1,31 @@
|
|||
<script lang="ts" setup>
|
||||
import type { OpenessRating, TechnologyRating, PrivacyRating } from '~/types'
|
||||
import type { ProjectRating } from '~/types'
|
||||
|
||||
// Define props for score
|
||||
const props = defineProps<{
|
||||
rating: TechnologyRating | OpenessRating | PrivacyRating
|
||||
type: string
|
||||
score: number
|
||||
rating: ProjectRating
|
||||
percentage: number
|
||||
compact?: boolean
|
||||
}>()
|
||||
|
||||
// Determine the color by score
|
||||
const colorByScore = props.score <= 1
|
||||
? 'bg-app-bg-rating-red'
|
||||
: props.score === 2
|
||||
? 'bg-app-bg-rating-orange'
|
||||
: props.score === 3
|
||||
? 'bg-app-bg-rating-yellow'
|
||||
: 'bg-app-bg-rating-green'
|
||||
const colors = [
|
||||
'#ff0000', // 0-10%
|
||||
'#ff4500', // 11-20%
|
||||
'#ff8c00', // 21-30%
|
||||
'#ffd700', // 31-40%
|
||||
'#adff2f', // 41-50%
|
||||
'#7fff00', // 51-60%
|
||||
'#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)
|
||||
|
||||
|
@ -34,40 +43,6 @@ const hidePopover = () => {
|
|||
isPopoverVisible.value = false
|
||||
}, 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>
|
||||
|
||||
<template>
|
||||
|
@ -76,21 +51,18 @@ function items(): { label: string, condition: boolean, positive: string, negativ
|
|||
<div
|
||||
flex
|
||||
items-center
|
||||
gap-4px
|
||||
p-12px
|
||||
gap-4px
|
||||
hover:bg-app-bg-rating-hover
|
||||
hover:rounded-8px
|
||||
@mouseenter="showPopover"
|
||||
@mouseleave="hidePopover"
|
||||
>
|
||||
<!-- Render the score points -->
|
||||
<div
|
||||
v-for="point of 5"
|
||||
v-for="point of [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]"
|
||||
:key="point"
|
||||
w="10px lg:12px"
|
||||
h="10px lg:12px"
|
||||
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']"
|
||||
:style="`background-color: ${percentage >= point * 10 ? backgroundColorByScore : '#494949'}`"
|
||||
: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']"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -117,7 +89,7 @@ function items(): { label: string, condition: boolean, positive: string, negativ
|
|||
@mouseleave="hidePopover"
|
||||
>
|
||||
<div
|
||||
v-for="item in items()"
|
||||
v-for="item in rating.items"
|
||||
:key="item.label"
|
||||
flex
|
||||
justify-between
|
||||
|
@ -125,7 +97,7 @@ function items(): { label: string, condition: boolean, positive: string, negativ
|
|||
text-12px
|
||||
font-700
|
||||
leading-20px
|
||||
:class="{ 'text-app-text-rating-negative': !item.condition }"
|
||||
:class="{ 'text-app-text-rating-negative': !item.isValid }"
|
||||
>
|
||||
<div
|
||||
flex
|
||||
|
@ -133,24 +105,24 @@ function items(): { label: string, condition: boolean, positive: string, negativ
|
|||
gap-6px
|
||||
>
|
||||
<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
|
||||
mt--4px
|
||||
/>
|
||||
{{ item.label }}
|
||||
</div>
|
||||
<NuxtLink
|
||||
v-if="item.link"
|
||||
:to="item.link"
|
||||
v-if="item.positive === 'Link'"
|
||||
:to="item.value"
|
||||
target="_blank"
|
||||
external
|
||||
underline
|
||||
@click.stop
|
||||
>
|
||||
Link
|
||||
{{ item.positive }}
|
||||
</NuxtLink>
|
||||
<div v-else>
|
||||
{{ item.condition ? item.positive : item.negative }}
|
||||
{{ item.isValid ? item.positive : item.negative }}
|
||||
</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 { Ecosystem } from '~/types/ecosystem'
|
||||
import type { Feature } from '~/types/feature'
|
||||
import type { Rank } from '~/types/rank'
|
||||
import type { Usecase } from '~/types/usecase'
|
||||
|
||||
export const useData = defineStore('data', () => {
|
||||
|
@ -11,21 +12,34 @@ export const useData = defineStore('data', () => {
|
|||
const assets = useState<Asset[]>('assets')
|
||||
const ecosystems = useState<Ecosystem[]>('ecosystems')
|
||||
const projects = useState<Project[]>('projects')
|
||||
const ranks = useState<Rank[]>('ranks')
|
||||
|
||||
const selectedCategoryId = useState(() => 'all')
|
||||
const selectedUsecaseId = useState(() => 'all')
|
||||
const selectedEcosystemId = useState(() => 'all')
|
||||
const selectedAssetsUsedId = useState(() => 'all')
|
||||
const selectedFeaturesId = useState(() => 'all')
|
||||
|
||||
const filter = reactive({
|
||||
query: '',
|
||||
sortby: 'atoz',
|
||||
sortby: 'score',
|
||||
sortDirection: 'desc',
|
||||
})
|
||||
const switcher = ref(true)
|
||||
|
||||
watch(selectedCategoryId, () => {
|
||||
if (selectedCategoryId.value !== 'all')
|
||||
watch([selectedCategoryId, selectedUsecaseId, selectedEcosystemId, selectedAssetsUsedId, selectedFeaturesId], () => {
|
||||
if (selectedCategoryId.value !== 'all' || selectedUsecaseId.value !== 'all' || selectedEcosystemId.value !== 'all' || selectedAssetsUsedId.value !== 'all' || selectedFeaturesId.value !== 'all')
|
||||
filter.query = ''
|
||||
})
|
||||
|
||||
watch(filter, () => {
|
||||
if (filter.query !== '')
|
||||
if (filter.query !== '') {
|
||||
selectedCategoryId.value = 'all'
|
||||
selectedUsecaseId.value = 'all'
|
||||
selectedEcosystemId.value = 'all'
|
||||
selectedAssetsUsedId.value = 'all'
|
||||
selectedFeaturesId.value = 'all'
|
||||
}
|
||||
})
|
||||
|
||||
const fetchData = async () => {
|
||||
|
@ -37,32 +51,11 @@ export const useData = defineStore('data', () => {
|
|||
ecosystems: Ecosystem[]
|
||||
assets: Asset[]
|
||||
features: Feature[]
|
||||
ranks: Rank[]
|
||||
}>('/api/data')
|
||||
projects.value = data.projects.map(project => ({
|
||||
...project,
|
||||
ratings: {
|
||||
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',
|
||||
},
|
||||
},
|
||||
ratings: generateProjectRating(project),
|
||||
})).filter(p => p.name)
|
||||
categories.value = data.categories.map((c) => {
|
||||
c.projectsCount = projects.value.filter(p =>
|
||||
|
@ -74,12 +67,12 @@ export const useData = defineStore('data', () => {
|
|||
ecosystems.value = data.ecosystems
|
||||
assets.value = data.assets
|
||||
features.value = data.features
|
||||
ranks.value = data.ranks
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -98,7 +91,7 @@ export const useData = defineStore('data', () => {
|
|||
id: project.id,
|
||||
title1: project.name,
|
||||
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,
|
||||
explorer: project.links?.block_explorer,
|
||||
twitter: project.links?.twitter,
|
||||
|
@ -114,38 +107,32 @@ export const useData = defineStore('data', () => {
|
|||
image: project.logos?.[0]?.url ?? '',
|
||||
anonymity: true,
|
||||
categories: project.categories,
|
||||
ratings: {
|
||||
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',
|
||||
},
|
||||
},
|
||||
usecases: project.usecases,
|
||||
ecosystem: project.ecosystem,
|
||||
assets_used: project.assets_used,
|
||||
ratings: project.ratings,
|
||||
}
|
||||
}
|
||||
const shallowProjects = computed(() => projects.value.map(project => projectToShallow(project)))
|
||||
|
||||
const getProjectsByCategory = <T extends ProjectShallow>(id: string, options?: { shallow: boolean }): T[] => {
|
||||
if (id === 'all')
|
||||
return projects.value.map(project => projectToShallow(project)) as T[]
|
||||
else
|
||||
return projects.value.filter(project => project.categories?.includes(id)).map(project => options?.shallow ? projectToShallow(project) : project) as T[]
|
||||
const getProjectsByFilters = <T extends ProjectShallow>(options?: { shallow: boolean }): T[] => {
|
||||
const filteredProjects = projects.value
|
||||
.filter(project =>
|
||||
selectedCategoryId.value !== 'all' ? project.categories.includes(selectedCategoryId.value) : true,
|
||||
)
|
||||
.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 => {
|
||||
|
@ -159,7 +146,7 @@ export const useData = defineStore('data', () => {
|
|||
|
||||
const query = filter.query.toLowerCase()
|
||||
|
||||
const filteredShallowProjects = getProjectsByCategory(selectedCategoryId.value, { shallow: true })
|
||||
const filteredShallowProjects = getProjectsByFilters({ shallow: true })
|
||||
.filter((project) => {
|
||||
return (
|
||||
project
|
||||
|
@ -173,9 +160,23 @@ export const useData = defineStore('data', () => {
|
|||
return true
|
||||
}).sort((a, b) => {
|
||||
if (filter.sortby === 'score')
|
||||
if (filter.sortDirection === 'asc')
|
||||
return a.percentage - b.percentage
|
||||
else
|
||||
return b.percentage - a.percentage
|
||||
if (filter.sortby === 'atoz')
|
||||
return a.title1.localeCompare(b.title1)
|
||||
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
|
||||
return 0
|
||||
})
|
||||
|
@ -200,8 +201,69 @@ export const useData = defineStore('data', () => {
|
|||
|
||||
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 {
|
||||
selectedCategoryId,
|
||||
selectedUsecaseId,
|
||||
selectedEcosystemId,
|
||||
selectedAssetsUsedId,
|
||||
selectedFeaturesId,
|
||||
filter,
|
||||
switcher,
|
||||
categories,
|
||||
|
@ -215,7 +277,6 @@ export const useData = defineStore('data', () => {
|
|||
filteredProjectsCount,
|
||||
fetchData,
|
||||
getProjectById,
|
||||
getProjectsByCategory,
|
||||
filteredProjects,
|
||||
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