-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
153 lines (139 loc) · 4.61 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
const usage_repos = {
scanpy: ['scanpy_usage', 'scanpy-tutorials'],
anndata: ['anndata_usage'],
scvelo: ['scvelo_notebooks'],
diffxpy: ['diffxpy_tutorials'],
scGen: ['scGen_reproducibility'],
}
const categories = {
scanpy: ['scanpy', 'anndata', 'anndata2ri', 'scanpydoc'],
// Currently, “bartseq-pipeline” lives in “Analysis pipelines”. Good idea?
//'Bart-Seq': ['bartSeq', 'bartseq-pipeline'],
'Deep learning': ['dca', 'deepflow', 'scGen'],
MetaMap: ['MetaMap', 'MetaMap-web'],
'Statistical models': ['diffxpy', 'batchglm', 'LineagePulse', 'kBET', 'enrichment_analysis_celltype'],
'Manifold learning': ['paga', 'destiny', 'kbranches'],
'Dynamical models': ['pseudodynamics', 'scvelo', /*'graphdynamics'*/],
Analyses: ['2018_Angelidis', 'LungAgingAtlas'],
'Analysis pipelines': ['single-cell-tutorial', 'scAnalysisTutorial', 'bartseq-pipeline']
}
const invert = obj =>
Object.entries(obj)
.reduce((o, [k, vs]) => {
for (const v of vs) o[v] = k
return o
}, {})
const repo2cat = invert(categories)
const sorter = (...keys) => (a, b) => {
for (const key of keys) {
const keyfn = typeof key === 'string' ? (o => o[key]) : key
const x_raw = keyfn(a)
const y_raw = keyfn(b)
const x = typeof x_raw === 'string' ? x_raw.toLowerCase() : x_raw
const y = typeof y_raw === 'string' ? y_raw.toLowerCase() : y_raw
// if (x === y) console.log(`${key} is equal: ${x} === ${y}!`)
// else console.log(`${key} is not equal: ${x} !== ${y}!`)
if (x === y) continue
return x < y ? -1 : 1
}
}
const urlify = text =>
text.replace(/(https?:\/\/[^\s]+)/g, '<a href="$1">$1</a>')
const maybe_link = (html, url) => url ? `<a href="${url}">${html}</a>` : html
const icon_link = (html, url) => {
if (!url) return html
return `<a href="${url}">${html}</a>`
}
const render_repos = all_repos => {
const cat_names = Object.keys(categories)
const groups_unsorted = [
...cat_names.map(cat => ({cat, repos: all_repos.filter(({name}) => repo2cat[name] === cat)})),
{cat: 'Other', repos: all_repos.filter(({name}) => !cat_names.includes(repo2cat[name]))}
]
// Sort by sum(stars) / #repos
const groups = groups_unsorted
.sort(sorter(({repos}) => repos.reduce((sum, {stargazers_count: stars}) => sum + stars, 0) / repos.length))
.reverse()
return groups.map(({cat, repos}) => `
<h2>${cat}</h2>
<ul class=collection>
${repos.map(render_repo).join('\n')}
</ul>
`).join('\n')
}
const chip_link = (html, url, img) => `<a href="${url}" class=chip><img src=images/${img}.svg>${html}</a>`
const render_repo = ({
name,
description,
html_url,
homepage,
stargazers_count: stars,
has_issues, open_issues,
usage_repos,
}) => {
// Some repos are just links to others
if (homepage && homepage.startsWith('https://github.com')) {
html_url = homepage
homepage = null
}
return `
<li class="collection-item avatar">
<div>
<a href="${html_url}"><img src=images/github.svg class=circle></a>
<div>
<span class=title>${icon_link(name, homepage)}</span>
${description ? `<p>${urlify(description)}</p>` : ''}
</div>
</div>
<div class=secondary-content>
${!usage_repos.length ? '' : usage_repos.map(r => chip_link(r.name, r.html_url, 'github')).join('\n')}
${!stars ? '' : chip_link(stars, `${html_url}/stargazers`, 'star')}
<!--
${!has_issues ? '' : chip_link(open_issues, `${html_url}/issues`, 'issue')}
-->
</div>
</li>
`
}
const render_error = e => `
<div class=error>
${e}<br>
${e.stack.replace('\n', '<br>')}
</div>
`
const parse_link_hdr = hdr => hdr.split(/,\s+/)
.map(s => /<([^>]+)>; rel="(\w+)"/.exec(s))
.reduce((o, [, l, r]) => (o[r] = l, o), {})
const fetch_all = url => fetch(url).then(r => {
const links = parse_link_hdr(r.headers.get('Link'))
if (links.next)
return Promise.all([r.json(), fetch_all(links.next)])
.then(([a, b]) => a.concat(b))
else
return r.json()
})
const repos_url = 'https://api.github.com/orgs/theislab/repos'
const render_project_list = () =>
fetch_all(repos_url)
.then(repos => {
const sorted = repos
.filter(({archived}) => !archived)
.filter(({name}) => !Object.values(usage_repos).flat().includes(name))
.filter(({name}) => name !== 'theislab.github.io')
.sort(sorter('stargazers_count', 'name'))
.reverse()
.map(repo => ({
...repo,
usage_repos: repos.filter(({name}) => (usage_repos[repo.name] || []).includes(name)),
}))
return render_repos(sorted)
})
document.addEventListener('DOMContentLoaded', () => {
const project_list = document.getElementById('project-list')
render_project_list()
.then(html => project_list.innerHTML = html)
.catch(e => {
project_list.innerHTML = render_error(e)
throw e
})
}, false)