diff --git a/components/Card.vue b/components/Card.vue index 7881b79..07b8ea5 100644 --- a/components/Card.vue +++ b/components/Card.vue @@ -1,12 +1,14 @@ @@ -26,7 +28,7 @@ const projectItems = ['Swap,Mixer', { label: 'Openess', rating: props.project.ra > - Usecases + {{ project.usecases?.join(', ') }} - - + - {{ projectItem }} - - - - - + + {{ (projectItem.label as string[] || []).join(', ') }} + + + + + + - {{ project.percentage }} % + + + {{ project.percentage }} % + - + diff --git a/components/Project/ProjectRating.vue b/components/Project/ProjectRating.vue index 7909084..bbc6684 100644 --- a/components/Project/ProjectRating.vue +++ b/components/Project/ProjectRating.vue @@ -1,22 +1,31 @@ @@ -76,21 +51,18 @@ function items(): { label: string, condition: boolean, positive: string, negativ - @@ -117,7 +89,7 @@ function items(): { label: string, condition: boolean, positive: string, negativ @mouseleave="hidePopover" > {{ item.label }} - Link + {{ item.positive }} - {{ item.condition ? item.positive : item.negative }} + {{ item.isValid ? item.positive : item.negative }} diff --git a/composables/useData.ts b/composables/useData.ts index f3fbbd9..a517b2a 100644 --- a/composables/useData.ts +++ b/composables/useData.ts @@ -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('assets') const ecosystems = useState('ecosystems') const projects = useState('projects') + const ranks = useState('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 = (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 = (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 = (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') - return b.percentage - a.percentage - if (filter.sortby === 'atoz') - return a.title1.localeCompare(b.title1) + if (filter.sortDirection === 'asc') + return a.percentage - b.percentage + 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 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, } diff --git a/types/rank.ts b/types/rank.ts new file mode 100644 index 0000000..07ed7b9 --- /dev/null +++ b/types/rank.ts @@ -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 +} diff --git a/utils/score.ts b/utils/score.ts new file mode 100644 index 0000000..5174c2f --- /dev/null +++ b/utils/score.ts @@ -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 +}
+
+ {{ (projectItem.label as string[] || []).join(', ') }} +