diff --git a/routes/global.js b/routes/global.js
index 83aa326..cf1501e 100644
--- a/routes/global.js
+++ b/routes/global.js
@@ -14,35 +14,105 @@ function format_yymmdd(x){
return `${year}-${month}-${day}`;
}
+function cleanup_sysdep_desc(str){
+ if(!str) return "";
+ var str = str.charAt(0).toUpperCase() + str.slice(1);
+ return str.replace(/\(.*\)$/, '').replace('SASL -', 'SASL').replace(/[-,]+ .*(shared|runtime|binary|library|legacy|precision|quantum).*$/i, '');
+}
+
+function check_to_color(check){
+ switch (check) {
+ case 'ERROR':
+ return 'text-danger';
+ case 'WARNING':
+ return 'text-warning';
+ case 'NOTE':
+ return 'text-success';
+ case 'OK':
+ return 'text-success';
+ default:
+ return 'text-dark';
+ }
+}
+
+function os_icon(type){
+ switch (type) {
+ case 'win':
+ return 'fa-windows';
+ case 'linux':
+ return 'fa-linux';
+ case 'mac':
+ return 'fa-apple';
+ default:
+ return 'fa-question';
+ }
+}
+
router.get("/_global/search", function(req, res, next){
- res.render("search");
+ res.render("global/search");
});
router.get("/_global/activity", function(req, res, next){
- res.render("activity");
+ res.render("global/activity");
+});
+
+router.get("/_global/builds", function(req, res, next){
+ db.get_builds().then(function(packages){
+ packages.forEach(function(x){
+ var checks = x.runs.filter(run => run.check && run.built.Platform !== 'x86_64-apple-darwin20');
+ x.check_icon_html = function(version, type){
+ var bin = checks.find(run => run.built.R.startsWith(version) && run.type == type);
+ if(bin){
+ return ``
+ } else if(type != 'linux' && x.user == 'cran'){
+ return '';
+ } else {
+ return '';
+ }
+ };
+ x.date = format_yymmdd(x.timestamp * 1000);
+ x.src = x.runs.find(run => run.type == 'src') || {};
+ x.failure = x.runs.find(run => run.type == 'failure');
+ });
+ res.render('global/builds', {packages: packages});
+ }).catch(next);
});
router.get("/_global/organizations", function(req, res, next){
db.get_organizations().then(function(orgs){
- res.render('organizations', {orgs: orgs});
+ orgs.forEach(function(x){
+ x.avatar = x.uuid ? `https://avatars.githubusercontent.com/u/${x.uuid}` : `https://r-universe.dev/avatars/${x.universe}.png`;
+ });
+ res.render('global/organizations', {orgs: orgs});
}).catch(next);
});
router.get("/_global/repositories", function(req, res, next){
db.get_repositories().then(function(repos){
- res.render('repositories', {repos: repos, format_yymmdd: format_yymmdd});
+ res.render('global/repositories', {repos: repos, format_yymmdd: format_yymmdd});
});
});
router.get("/_global/scores", function(req, res, next){
db.get_scores().then(function(packages){
- res.render('scores', {packages: packages});
+ res.render('global/scores', {packages: packages});
+ });
+});
+
+router.get("/_global/sysdeps", function(req, res, next){
+ db.get_sysdeps().then(function(sysdeps){
+ sysdeps = sysdeps.filter(x => x.library && x.library !== 'c++');
+ sysdeps.forEach(function(x){
+ x.description = cleanup_sysdep_desc(x.description);
+ x.usedby = x.usedby.sort((a,b) => a.package.toLowerCase() < b.package.toLowerCase() ? -1 : 1);
+ });
+ res.render('global/sysdeps', {sysdeps: sysdeps});
});
});
router.get("/_global/galaxy", function(req, res, next){
db.get_organizations().then(function(orgs){
- res.render('galaxy', {rnd: rnd, orgs: orgs.slice(0, 250)});
+ res.render('global/galaxy', {rnd: rnd, orgs: orgs.slice(0, 250)});
}).catch(next);
});
diff --git a/src/db.js b/src/db.js
index 125cc11..bb1c7d2 100644
--- a/src/db.js
+++ b/src/db.js
@@ -184,6 +184,128 @@ function mongo_universe_vignettes(user){
return cursor.toArray();
}
+function mongo_all_universes(organizations_only){
+ var query = {_type: 'src', _registered: true};
+ if(organizations_only){
+ query['_userbio.type'] = 'organization';
+ query['_user'] = {$ne: 'cran'};
+ }
+ var cursor = mongo_aggregate([
+ {$match: query},
+ {$sort:{ _id: -1}},
+ {$group: {
+ _id : '$_user',
+ updated: { $max: '$_commit.time'},
+ packages: { $sum: 1 },
+ indexed: { $sum: { $toInt: '$_indexed' }},
+ name: { $first: '$_userbio.name'},
+ type: { $first: '$_userbio.type'},
+ uuid: { $first: '$_userbio.uuid'},
+ bio: { $first: '$_userbio.description'},
+ emails: { $addToSet: '$_maintainer.email'}
+ }},
+ {$project: {_id: 0, universe: '$_id', packages: 1, updated: 1, type: 1, uuid: 1,
+ indexed:1, name: 1, type: 1, bio: 1, maintainers: { $size: '$emails' },
+ }},
+ {$sort:{ indexed: -1}}
+ ]);
+ return cursor.toArray();
+}
+
+function mongo_all_scores(){
+ function array_size(key){
+ return {$cond: [{ $isArray: key }, {$size: key}, 0 ]};
+ }
+ var query = {_type: 'src', _indexed: true};
+ var projection = {
+ _id: 0,
+ package: '$Package',
+ universe: "$_user",
+ score: '$_score',
+ stars: "$_stars",
+ downloads: "$_downloads.count",
+ scripts: "$_searchresults",
+ dependents: '$_usedby',
+ commits: {$sum: '$_updates.n'},
+ contributors: array_size({$objectToArray: '$_contributions'}),
+ datasets: array_size('$_datasets'),
+ vignettes: array_size('$_vignettes'),
+ releases: array_size('$_releases')
+ }
+ var cursor = mongo_find(query).sort({_score: -1}).project(projection);
+ return cursor.toArray();
+}
+
+function mongo_all_sysdeps(distro){
+ var query = {_type: 'src', '_sysdeps': {$exists: true}};
+ if(distro){
+ query['_distro'] = distro;
+ }
+ var cursor = mongo_aggregate([
+ {$match: query},
+ {$unwind: '$_sysdeps'},
+ {$group: {
+ _id : '$_sysdeps.name',
+ packages: { $addToSet: '$_sysdeps.package'},
+ headers: { $addToSet: '$_sysdeps.headers'},
+ version: { $first: '$_sysdeps.version'},
+ homepage: { $addToSet: '$_sysdeps.homepage'},
+ description: { $addToSet: '$_sysdeps.description'},
+ distro : { $addToSet: '$_distro'},
+ usedby : { $addToSet: {owner: '$_owner', package:'$Package'}}
+ }},
+ {$project: {_id: 0, library: '$_id', packages: 1, headers: 1, version: 1, usedby: 1,
+ homepage: { '$first' : '$homepage'}, description: { '$first' : '$description'}, distro:{ '$first' : '$distro'}}},
+ {$sort:{ library: 1}}
+ ]);
+ return cursor.toArray();
+}
+
+function days_ago(n){
+ var now = new Date();
+ return now.getTime()/1000 - (n*60*60*24);
+}
+
+function mongo_recent_builds(days = 7){
+ var query = {'_commit.time' : {'$gt': days_ago(days)}};
+ var cursor = mongo_aggregate([
+ {$match: query},
+ {$group : {
+ _id : { user: '$_user', package: '$Package', commit: '$_commit.id'},
+ version: { $first : "$Version" },
+ maintainer: { $first : "$_maintainer.name" },
+ maintainerlogin: { $first : "$_maintainer.login" },
+ timestamp: { $first : "$_commit.time" },
+ upstream: { $first : "$_upstream" },
+ registered: { $first: "$_registered" },
+ os_restriction: { $addToSet: '$OS_type'},
+ macbinary: { $addToSet : '$_macbinary' },
+ winbinary: { $addToSet : '$_winbinary' },
+ runs : { $addToSet:
+ { type: "$_type", built: '$Built', date:'$_published', url: '$_buildurl', status: '$_status', distro: '$_distro', check: '$_check'}
+ }
+ }},
+ {$sort : {"timestamp" : -1}},
+ {$project: {
+ _id: 0,
+ user: '$_id.user',
+ package: '$_id.package',
+ commit: '$_id.commit',
+ maintainer: 1,
+ maintainerlogin: 1,
+ version: 1,
+ timestamp: 1,
+ registered: 1,
+ runs: 1,
+ upstream: 1,
+ macbinary: { $first: "$macbinary" },
+ winbinary: { $first: "$winbinary" },
+ os_restriction:{ $first: "$os_restriction" }
+ }}
+ ]);
+ return cursor.toArray();
+}
+
function get_package_info(package, universe){
if(production){
return mongo_package_info(package, universe);
@@ -236,15 +358,35 @@ function get_scores(){
function get_organizations(){
if(production){
- return mongo_all_universes()
+ return mongo_all_universes(true);
} else {
console.warn(`Fetching universes data from API...`);
return get_ndjson(`https://r-universe.dev/api/universes?type=organization&skipcran=1&stream=1`);
}
}
+function get_sysdeps(){
+ if(production){
+ return mongo_all_sysdeps()
+ } else {
+ console.warn(`Fetching sysdeps data from API...`);
+ return get_ndjson(`https://r-universe.dev/stats/sysdeps?all=1`);
+ }
+}
+
+function get_builds(){
+ if(production){
+ return mongo_recent_builds()
+ } else {
+ console.warn(`Fetching builds data from API...`);
+ return get_ndjson(`https://r-universe.dev/stats/builds?limit=1000`);
+ }
+}
+
module.exports = {
get_scores: get_scores,
+ get_builds : get_builds,
+ get_sysdeps : get_sysdeps,
get_repositories: get_repositories,
get_organizations: get_organizations,
get_package_info: get_package_info,
diff --git a/static/layout.css b/static/layout.css
index 4d78767..f1d9efc 100644
--- a/static/layout.css
+++ b/static/layout.css
@@ -149,4 +149,31 @@ canvas {
p.overflow-x-invisible::-webkit-scrollbar {
display: none;
}
-}
\ No newline at end of file
+}
+
+.logo-img{
+ height: 70px
+}
+
+#global-sidebar {
+ width: 60px;
+ padding: 15px 5px 15px 5px;
+}
+
+#global-sidebar:hover{
+ width: 240px;
+ margin-right: -180px;
+ z-index: 9;
+}
+
+#global-sidebar .menu-expanded {
+ display: none;
+}
+
+#global-sidebar:hover .menu-expanded {
+ display: unset;
+}
+
+#global-sidebar:hover .menu-collapsed {
+ display: none;
+}
diff --git a/static/logo-big.svg b/static/logo-big.svg
new file mode 100644
index 0000000..94b95d5
--- /dev/null
+++ b/static/logo-big.svg
@@ -0,0 +1,24 @@
+
+
\ No newline at end of file
diff --git a/static/logo-icon.svg b/static/logo-icon.svg
new file mode 100644
index 0000000..267398b
--- /dev/null
+++ b/static/logo-icon.svg
@@ -0,0 +1,9 @@
+
+
diff --git a/static/organizations.js b/static/organizations.js
deleted file mode 100644
index 6810fb4..0000000
--- a/static/organizations.js
+++ /dev/null
@@ -1,8 +0,0 @@
-window.onload = function(){
- /* Workaround for: https://github.com/desandro/masonry/issues/1147 */
- var msnry = new Masonry('.card-rows');
- $('.avatar').on('load', function(){
- msnry.layout();
- console.log("refresh")
- });
-};
diff --git a/testprod.sh b/testprod.sh
new file mode 100755
index 0000000..6b19ea9
--- /dev/null
+++ b/testprod.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+# NB: docker compose server should be restarted without -d to open external mongo port
+export CRANLIKE_MONGODB_SERVER=packages.r-universe.dev
+eval $(cat ../production-server/secrets.env) npm start
diff --git a/views/activity.pug b/views/activity.pug
deleted file mode 100644
index ec80ce1..0000000
--- a/views/activity.pug
+++ /dev/null
@@ -1,20 +0,0 @@
-doctype html
-head
- meta(charset='utf-8')
- meta(name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no')
- link(rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.3.2/css/bootstrap.min.css')
- title R-universe: personal package repositories for R!
-
-#content-container.container(style='max-width: 1440px')
- // Careful: padding may result unsharp/pixelated bitmap rendering
- .col.p-0
- canvas#activity-canvas.col.p-0(height='300')
- .col.p-0
- canvas#contributors-canvas.col.p-0(height='700')
-
-include includes/default-scripts.pug
-script(src='https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.js')
-script(src='https://cdnjs.cloudflare.com/ajax/libs/chartjs-plugin-annotation/3.0.1/chartjs-plugin-annotation.min.js')
-script(src='https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.30.1/moment.min.js')
-script(src='https://cdnjs.cloudflare.com/ajax/libs/chartjs-adapter-moment/1.0.1/chartjs-adapter-moment.min.js')
-script(src='/activity.js')
diff --git a/views/global.pug b/views/global.pug
deleted file mode 100644
index f56b918..0000000
--- a/views/global.pug
+++ /dev/null
@@ -1,26 +0,0 @@
-doctype html
-html
- head
- meta(charset='utf-8')
- meta(name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no')
- link(rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.3.2/css/bootstrap.min.css')
- link(rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css')
- link(rel='stylesheet' href='/layout.css')
- title R-universe - Organizations
-
- body
- header.mb-3.border-bottom.bg-body-tertiary
- .container(style='max-width: 1440px')
- nav.navbar
- form#searchform.mx-auto.d-none.d-md-flex.input-group.col-auto(role='search')
- input.form-control(type='search' placeholder=`search for a package, author, keyword, ...`)
- button.btn.btn-outline-secondary(type='submit') Search
- a.m-2.d-block(href='#' data-bs-toggle='dropdown')
- img#mainlogo(src='https://r-universe.dev/runiverse.svg', width=110)
-
- main.col.mx-sm-2
- block content
-
- block before_scripts
- include includes/default-scripts.pug
- block after_scripts
\ No newline at end of file
diff --git a/static/activity.js b/views/global/activity.js
similarity index 97%
rename from static/activity.js
rename to views/global/activity.js
index 7253342..5e29944 100644
--- a/static/activity.js
+++ b/views/global/activity.js
@@ -126,10 +126,10 @@ function make_contributor_chart(universe, max, imsize){
max = max || 100;
return get_ndjson(`https://${universe && universe + "." || ""}r-universe.dev/stats/contributors?all=true&limit=${max}`).then(function(contributors){
const size = imsize || 50;
- contributors = contributors.sort(function(x,y){return x.repos.length < y.repos.length ? 1 : -1});
+ //contributors = contributors.sort(function(x,y){return x.repos.length < y.repos.length ? 1 : -1});
contributors = contributors.filter(x => x.login != 'nturaga') //this is a bot from BioConductor
const logins = contributors.map(x => x.login);
- const totals = contributors.map(x => x.repos.length);
+ const totals = contributors.map(x => x.total);
const counts = contributors.map(x => sort_packages(x.repos).map(x => x.upstream.split(/[\\/]/).pop()));
const avatars = logins.map(x => `https://r-universe.dev/avatars/${x.replace('[bot]', '')}.png?size=${size}`);
const images = avatars.map(x => undefined);
diff --git a/views/global/activity.pug b/views/global/activity.pug
new file mode 100644
index 0000000..ae785d0
--- /dev/null
+++ b/views/global/activity.pug
@@ -0,0 +1,18 @@
+extends global
+
+block content
+ #content-container.container-fluid.p-0
+ // Careful: padding may result unsharp/pixelated bitmap rendering
+ .col.p-0
+ canvas#activity-canvas.col.p-0(height='300')
+ .col.p-0
+ canvas#contributors-canvas.col.p-0(height='700')
+
+
+block after_scripts
+ script(src='https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.js')
+ script(src='https://cdnjs.cloudflare.com/ajax/libs/chartjs-plugin-annotation/3.0.1/chartjs-plugin-annotation.min.js')
+ script(src='https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.30.1/moment.min.js')
+ script(src='https://cdnjs.cloudflare.com/ajax/libs/chartjs-adapter-moment/1.0.1/chartjs-adapter-moment.min.js')
+ script
+ include activity.js
diff --git a/views/global/builds.pug b/views/global/builds.pug
new file mode 100644
index 0000000..ac193c4
--- /dev/null
+++ b/views/global/builds.pug
@@ -0,0 +1,56 @@
+extends global
+
+block content
+ #content-container.container-fluid
+ table.table
+ thead
+ tr.border-bottom.border-dark
+ th Commit
+ th Universe
+ th Package
+ th Version
+ th Maintainer
+ th Vignettes
+ th R-4.5 | R-4.4 | R-4.3
+ tbody
+ each x in packages
+ tr
+ td.text-nowrap #{x.date}
+ td
+ a.text-secondary(href=`https://${x.user}.r-universe.dev`) #{x.user}
+ td
+ if x.src.status
+ a.fw-semibold(href=`https://${x.user}.r-universe.dev/${x.package}`) #{x.package}
+ else
+ s #{x.package}
+ if x.failure
+ a.ms-1.text-danger.fw-semibold(href=`${x.failure.url}` target='_blank') (build failure)
+ td
+ a.text-dark(href=`${x.upstream}/commit/${x.commit}` target='_blank') #{x.version}
+ td
+ if x.maintainerlogin
+ a.text-secondary(href=`https://${x.maintainerlogin}.r-universe.dev/`) #{x.maintainer}
+ else
+ span.text-secondary #{x.maintainer}
+ td
+ if x.src.status
+ a(href=x.src.url target='_blank' class=`${x.src.status == 'success' ? "text-success" : "text-danger"}`).noline
+ i.fa-fw.fa-solid.fa-box-archive
+ td
+ a(href=x.src.url target='_blank').text-secondary.noline
+ | !{x.check_icon_html('4.5', 'linux')}
+ | !{x.check_icon_html('4.5', 'win')}
+ | |
+ | !{x.check_icon_html('4.4', 'win')}
+ | !{x.check_icon_html('4.4', 'mac')}
+ | |
+ | !{x.check_icon_html('4.3', 'win')}
+ | !{x.check_icon_html('4.3', 'mac')}
+
+block after_head
+ link(rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css')
+
+block after_scripts
+ script(src='https://cdn.datatables.net/2.1.3/js/dataTables.min.js')
+ script
+ include updates.js
diff --git a/views/includes/galaxy.js b/views/global/galaxy.js
similarity index 100%
rename from views/includes/galaxy.js
rename to views/global/galaxy.js
diff --git a/views/galaxy.pug b/views/global/galaxy.pug
similarity index 79%
rename from views/galaxy.pug
rename to views/global/galaxy.pug
index 7fff542..6276ddb 100644
--- a/views/galaxy.pug
+++ b/views/global/galaxy.pug
@@ -10,9 +10,8 @@ img#runiverselogo(src='https://r-universe.dev/runiverse.svg')
svg(viewbox='0 0 800 600' preserveaspectratio='xMinYMid slice')
g#stars
each x in orgs
- if x.universe != 'cran'
- a(href=`https://${x.universe}.r-universe.dev` data-bs-toggle="tooltip" data-bs-title=x.universe)
- circle(cx=`${rnd(100)}%`, cy=`${rnd(100)}%`, r=Math.sqrt(Math.sqrt(x.indexed)), style=`animationDelay: ${rnd(100)/10}s`)
+ a(href=`https://${x.universe}.r-universe.dev` data-bs-toggle="tooltip" data-bs-title=x.universe)
+ circle(cx=`${rnd(100)}%`, cy=`${rnd(100)}%`, r=Math.sqrt(Math.sqrt(x.indexed)), style=`animationDelay: ${rnd(100)/10}s`)
g#comets
line(x1='1185' y1='-15' x2='77.5' y2='1092.5')
@@ -28,6 +27,6 @@ svg(viewbox='0 0 800 600' preserveaspectratio='xMinYMid slice')
circle
polygon(points='371.3,338.6 355.7,344.7 363.1,329.7')
-include includes/default-scripts.pug
+include ../includes/default-scripts.pug
script
- include includes/galaxy.js
\ No newline at end of file
+ include galaxy.js
\ No newline at end of file
diff --git a/views/global/global.css b/views/global/global.css
new file mode 100644
index 0000000..5f40d7e
--- /dev/null
+++ b/views/global/global.css
@@ -0,0 +1,173 @@
+main {
+ font-size: .875rem;
+ padding-left: 80px;
+}
+
+.menu-logo {
+ background-image: url("/logo-icon.svg");
+ background-repeat: no-repeat;
+ background-size: 60%;
+ background-position: center;
+ filter: invert(100%);
+ height: 80px;
+}
+
+#global-sidebar {
+ width: 72px;
+ box-shadow: 10px 0 10px -2px #888;
+}
+
+#global-sidebar i.bi {
+ font-size: 1.2em;
+}
+
+#global-sidebar:hover{
+ width: 250px;
+}
+
+#global-sidebar .menu-expanded {
+ display: none;
+}
+
+#global-sidebar:hover .menu-expanded {
+ display: unset;
+ padding-left: 10px;
+}
+
+#global-sidebar:hover .menu-logo{
+ background-image: url("/logo-big.svg");
+ filter: unset;
+}
+
+a {
+ color: #3b71ca;
+ text-decoration: none;
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+a.noline:hover {
+ text-decoration: none;
+}
+
+.maintainer-card{
+ transition: 0.3s;
+}
+
+.maintainer-card:hover {
+ background-color: #eee;
+}
+
+svg {
+ height: 150px;
+}
+
+#rocket {
+ animation: shakes 3s linear infinite;
+}
+
+@keyframes shakes {
+ 10% {
+ transform: translate(5px, 4px);
+ }
+ 20% {
+ transform: translate(1px, 4px);
+ }
+ 30% {
+ transform: translate(1px, 4px);
+ }
+ 40% {
+ transform: translate(1px, 1px);
+ }
+ 50% {
+ transform: translate(5px, 3px);
+ }
+ 60% {
+ transform: translate(2px, 4px);
+ }
+ 70% {
+ transform: translate(3px, 3px);
+ }
+ 80% {
+ transform: translate(4px, 3px);
+ }
+ 90% {
+ transform: translate(4px, 3px);
+ }
+ 100% {
+ transform: translate(0, 0);
+ }
+}
+#rocket path, #rocket circle, #rocket polygon {
+ fill: #333;
+}
+#rocket circle {
+ cx: 369.5;
+ cy: 331.8;
+ r: 7;
+}
+#rocket polygon {
+ animation: burst 0.5s infinite;
+ transform-origin: 363px 337px;
+}
+
+@keyframes burst {
+ 20% {
+ transform: scale(0.8);
+ }
+ 40% {
+ transform: scale(0.6);
+ }
+ 60% {
+ transform: scale(1);
+ }
+ 80% {
+ transform: scale(0.8);
+ }
+ 100% {
+ transform: scale(1);
+ }
+}
+
+.carousel-control{
+ color: #000 !important;
+ width: auto !important;
+}
+
+.carousel-control-prev {
+ left: -35px;
+}
+
+.carousel-control-next {
+ right: -35px;
+}
+
+#extra-search-fields ::placeholder {
+ opacity: .6;
+ font-style: italic;
+}
+
+.blog-posts {
+ padding-left: 10px;
+}
+
+.blog-posts li {
+ padding: 3px 5px;
+ list-style-type: "ยป" !important;
+}
+
+.blog-posts li span {
+ float: right;
+ padding-top: 3px;
+ font-size: 12px;
+ color: #999;
+ text-transform: uppercase;
+}
+
+.avatar {
+ width: 100%;
+ aspect-ratio: 1 / 1;
+ object-fit: cover;
+}
diff --git a/views/global/global.pug b/views/global/global.pug
new file mode 100644
index 0000000..b63ed6a
--- /dev/null
+++ b/views/global/global.pug
@@ -0,0 +1,64 @@
+doctype html
+html
+ head
+ meta(charset='utf-8')
+ meta(name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no')
+ link(rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.3.2/css/bootstrap.min.css')
+ link(rel='stylesheet' href='https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css')
+ block after_head
+ style
+ include global.css
+
+ title R-universe - Organizations
+
+ body
+ #global-sidebar.text-bg-dark.fixed-top.vh-100
+ ul.nav.nav-pills.nav-flush.flex-column.mb-auto
+ li.nav-item.text-center.menu-logo
+ li.nav-item
+ a.nav-link.py-3.border-bottom.rounded-0.text-white(href='./search')
+ i.bi.bi-house-door.pe-none.px-2
+ span.menu-expanded Search
+ li.nav-item
+ a.nav-link.py-3.border-bottom.rounded-0.text-white(href='./organizations')
+ i.bi.bi-globe-americas.pe-none.px-2
+ span.menu-expanded Community
+ li.nav-item
+ a.nav-link.py-3.border-bottom.rounded-0.text-white(href='./repositories')
+ i.bi.bi-grid.pe-none.px-2
+ span.menu-expanded Repositories
+ li.nav-item
+ a.nav-link.py-3.border-bottom.rounded-0.text-white(href='./scores')
+ i.bi.bi-boxes.pe-none.px-2
+ span.menu-expanded Scoreboard
+ li.nav-item
+ a.nav-link.py-3.border-bottom.rounded-0.text-white(href='./builds')
+ i.bi.bi-arrow-clockwise.pe-none.px-2
+ span.menu-expanded Recent Builds
+ li.nav-item
+ a.nav-link.py-3.border-bottom.rounded-0.text-white(href='./sysdeps')
+ i.bi.bi-gear.pe-none.px-2
+ span.menu-expanded System libs
+ li.nav-item
+ a.nav-link.py-3.border-bottom.rounded-0.text-white(href='./activity')
+ i.bi.bi-people.pe-none.px-2
+ span.menu-expanded Activity
+ li.nav-item
+ a.nav-link.py-3.border-bottom.rounded-0.text-white(href='./galaxy')
+ i.bi.bi-rocket-takeoff.pe-none.px-2
+ span.menu-expanded Galaxy
+ li.nav-item
+ a.nav-link.py-3.border-bottom.rounded-0.text-white(href='https://docs.r-universe.dev' target='_blank')
+ i.bi.bi-info-circle.pe-none.px-2
+ span.menu-expanded Docs
+
+ script
+ | var navlink = document.querySelector(`a.nav-link[href='${location.pathname.replace(/.*\//, './')}']`);
+ | if(navlink) navlink.classList.add('active')
+
+ main.col.vw-100
+ block content
+
+ block before_scripts
+ include ../includes/default-scripts.pug
+ block after_scripts
\ No newline at end of file
diff --git a/views/global/organizations.js b/views/global/organizations.js
new file mode 100644
index 0000000..3d98bd2
--- /dev/null
+++ b/views/global/organizations.js
@@ -0,0 +1,14 @@
+window.onload = function(){
+ /* Workaround for: https://github.com/desandro/masonry/issues/1147 */
+ const msnry = new Masonry('.card-rows');
+ const refresh = debounce(() => msnry.layout());
+ $('.avatar').on('load', refresh);
+};
+
+function debounce(func, timeout = 100){
+ let timer;
+ return (...args) => {
+ clearTimeout(timer);
+ timer = setTimeout(() => { func.apply(this, args); }, timeout);
+ };
+}
diff --git a/views/organizations.pug b/views/global/organizations.pug
similarity index 58%
rename from views/organizations.pug
rename to views/global/organizations.pug
index 29669e6..934ff43 100644
--- a/views/organizations.pug
+++ b/views/global/organizations.pug
@@ -1,12 +1,12 @@
extends global
block content
- #content-container.container(style='max-width: 1440px' data-masonry='{"percentPosition":true}')
+ #content-container.container-fluid(style='max-width: 1600px' data-masonry='{"percentPosition":true}')
.card-group.card-rows
each x in orgs
.col-xl-2.col-lg-3.col-md-4.col-6
a.card.m-2.package-description-card(href=`https://${x.universe}.r-universe.dev`).text-decoration-none
- img.p-2.avatar.card-img-top(src=`https://r-universe.dev/avatars/${x.universe}.png?size=214` loading='lazy')
+ img.p-2.avatar.card-img-top(src=`${x.avatar}` loading='lazy')
.card-body
h5 #{x.universe}
p.card-text #{x.bio || x.name}
@@ -15,5 +15,5 @@ block content
block after_scripts
- script(src='https://cdnjs.cloudflare.com/ajax/libs/masonry/4.2.2/masonry.pkgd.min.js')
- script(src='/organizations.js')
+ script
+ include organizations.js
diff --git a/static/repositories.js b/views/global/repositories.js
similarity index 100%
rename from static/repositories.js
rename to views/global/repositories.js
diff --git a/views/repositories.pug b/views/global/repositories.pug
similarity index 62%
rename from views/repositories.pug
rename to views/global/repositories.pug
index 2261f2d..11d4155 100644
--- a/views/repositories.pug
+++ b/views/global/repositories.pug
@@ -1,17 +1,17 @@
extends global
block content
- #content-container.container(style='max-width: 1440px')
- table.table.table-bordered.d-none
+ #content-container.container-fluid
+ table.table.d-none
thead
- tr
- th universe
- th packages
- th indexed
- th maintainers
- th type
- th name
- th updated
+ tr.border-bottom.border-dark
+ th Universe
+ th Packages
+ th Indexed
+ th Maintainers
+ th Type
+ th Name
+ th Updated
tbody
each x in repos
tr
@@ -26,4 +26,5 @@ block content
block after_scripts
script(src='https://cdn.datatables.net/2.1.3/js/dataTables.min.js')
- script(src="/repositories.js")
+ script
+ include repositories.js
diff --git a/static/scores.js b/views/global/scores.js
similarity index 100%
rename from static/scores.js
rename to views/global/scores.js
diff --git a/views/scores.pug b/views/global/scores.pug
similarity index 59%
rename from views/scores.pug
rename to views/global/scores.pug
index 4d0a30a..56e582c 100644
--- a/views/scores.pug
+++ b/views/global/scores.pug
@@ -1,25 +1,25 @@
extends global
block content
- #content-container.container(style='max-width: 1440px')
- table.table.table-bordered.d-none
+ #content-container.container-fluid
+ table.table.d-none
thead
- tr
- th package
- th score
- th stars
- th downloads
- th scripts
- th dependents
- th contributors
- th vignettes
- th datasets
- th releases
- th commits/year
+ tr.border-bottom.border-dark
+ th Package
+ th Score
+ th Stars
+ th Downloads
+ th Scripts
+ th Dependents
+ th Contributors
+ th Vignettes
+ th Datasets
+ th Releases
+ th Commits/year
tbody
each x in packages
tr
- td
+ th
a(href=`https://${x.universe}.r-universe.dev/${x.package}`) #{x.package}
td #{Math.pow(x.score-1, 2).toFixed(2)}
td #{x.stars}
@@ -34,4 +34,5 @@ block content
block after_scripts
script(src='https://cdn.datatables.net/2.1.3/js/dataTables.min.js')
- script(src="/scores.js")
+ script
+ include scores.js
diff --git a/static/search.js b/views/global/search.js
similarity index 99%
rename from static/search.js
rename to views/global/search.js
index f689131..92be158 100644
--- a/static/search.js
+++ b/views/global/search.js
@@ -167,12 +167,14 @@ function get_query(){
function update_results(){
var q = $("#search-input").val();
$("title").text(`R-universe search: ${q}`)
- if(!q){
+ if(q.length < 2){
$('#search-results').empty();
+ $('#search-results-comment').empty();
$('#results-placeholder').show();
$('svg').show('fast', () => $('#search-input').focus());
+ msnry.layout();
+ return;
}
- if(q.length < 2) return;
$('#search-results').empty();
$('#results-placeholder').hide();
$('svg').hide('fast');
diff --git a/views/global/search.pug b/views/global/search.pug
new file mode 100644
index 0000000..b24795e
--- /dev/null
+++ b/views/global/search.pug
@@ -0,0 +1,101 @@
+extends global
+
+block content
+ .container.p-4#search-container
+ .d-flex.justify-content-center
+ svg(viewbox='250 250 300 150' preserveaspectratio='xMinYMid slice')
+ g#rocket
+ path(d='M445.3,256.3c-47.3-3.5-62.8,27-62.8,27c-18.5,2-24,10-24,10l15.5,12.3c-0.5,15.3,3.5,16.5,3.5,16.5c5.5,7.5,17.8,4.8,17.8,4.8l12.8,17c12-12.5,10.3-25.5,10.3-25.5C450.8,300,445.3,256.3,445.3,256.3zM416.3,297c-6.5,0-11.8-5.3-11.8-11.8c0-6.5,5.3-11.8,11.8-11.8s11.8,5.3,11.8,11.8C428,291.7,422.7,297,416.3,297z')
+ circle
+ polygon(points='371.3,338.6 355.7,344.7 363.1,329.7')
+ #searchbox.row.justify-content-center
+ .col-12.col-lg-10.col-xl-8.col-xxl-7
+ .input-group.input-group-lg
+ input#search-input.form-control(type='text' autocomplete='off' placeholder='Search for a package, author, topic, keyword, ...')
+ button#search-button.btn.btn-outline-secondary(type='button') Search
+ button.btn.btn-outline-secondary.dropdown-toggle.dropdown-toggle-split(type='button' data-bs-toggle='collapse' data-bs-target='#extra-search-fields')
+ #extra-search-fields.card.card-body.border-top-0.collapse.noanim.shadow(style='margin-right: 46px;')
+ #results-placeholder.py-4(style='max-width: 920px; margin: auto;')
+ p.summary-paragraph
+ | Currently serving
+ span.ps-1#summary-n-packages ..
+ a.ps-1(href='../builds') packages
+ | ,
+ span.ps-1#summary-n-datasets ..
+ | datasets, and
+ span.ps-1#summary-n-articles ..
+ a.ps-1(href='../articles') articles
+ | by
+ span.ps-1#summary-n-maintainers ..
+ a.ps-1(href='../maintainers') maintainers
+ | and
+ span.ps-1#summary-n-contributors ..
+ | contributors.
+ | Not sure what to search for? Why not try:
+ span.ps-1#topics-list
+ .d-none.d-xl-block
+ br
+ br
+ #maintainers-carousel.carousel.slide.border-top.border-bottom.mt-4.text-center(data-bs-ride='carousel')
+ span.bg-white.position-relative.d-inline.p-2.text-muted(style='top: -1em;') Organizations
+ button.text-decoration-none.btn.btn-link.carousel-control.carousel-control-prev(type='button' data-bs-target='#maintainers-carousel' data-bs-slide='prev')
+ i.fas.fa-angle-double-left
+ button.text-decoration-none.btn.btn-link.carousel-control.carousel-control-next(type='button' data-bs-target='#maintainers-carousel' data-bs-slide='next')
+ i.fas.fa-angle-double-right
+ .carousel-inner
+ p(style='margin-top: 50px;')
+ | Want to learn more about r-universe? Have a look at
+ a.mx-1(href='https://ropensci.org/r-universe') ropensci.org/r-universe
+ | or updates from the rOpenSci blog:
+ ul.blog-posts
+ .container(style='max-width: 1440px')
+ #search-results-comment.text-secondary.search-results.col-12.mt-2
+ .row#search-results(data-masonry='{"percentPosition": true }')
+
+ #templatezone.d-none
+ .package-description-item.col-md-12.col-xl-6
+ .card.mt-4.package-description-card
+ .card-body
+ .float-end.m-2(style="max-width: 140px;")
+ img.img.border.border-secondary-subtle.rounded.p-1.mx-1(src='https://r-universe.dev/static/nobody.jpg' style='max-height: 120px;')
+ p.pt-1.m-1.text-center.package-org.text-secondary
+ a.package-link.text-decoration-none.text-reset
+ h2.h5.pb-1
+ span.package-name.text-dark
+ | :
+ small.ms-1.text-secondary.package-title
+ p.card-text.package-description.fst-italic
+ p.card-text.pt-2
+ small.text-muted.description-maintainer
+ small.text-muted.description-last-updated
+ p.card-text.description-topics.d-none
+ span.me-1.text-muted.badge.badge-light.description-score.d-none.p-1.border
+ i.fas.fa-chart-bar.text-primary
+ span.me-1.text-muted.badge.badge-light.description-stars.d-none.p-1.border
+ i.fas.fa-star.text-warning
+ span.me-1.text-muted.badge.badge-light.description-pkgscore.p-1.border.d-none
+ i.fas.fa-chart-line.text-primary
+ span.me-1.text-muted.badge.badge-light.description-dependencies.p-1.border.d-none
+ i.fas.fa-cube.text-danger
+ span.me-1.text-muted.badge.badge-light.description-dependents.p-1.border.d-none
+ i.fas.fa-cubes.text-success
+
+ .organization-item.col-xl-2.col-md-3.col-4.mb-2
+ a.card.maintainer-card.border-0.text-decoration-none.p-2
+ img.card-img-top.img-rounded
+ .card-body.px-0.py-1
+ p.card-text.text-center.text-muted.text-truncate
+ .carousel-item(data-interval='5000')
+ .row.justify-content-center.maintainer-row
+ .form-group.row.search-field-item.mb-3
+ label.col-sm-3.col-lg-2.col-form-label.col-form-label-sm
+ .col-sm-9.col-lg-10
+ input.form-control.form-control-sm.search-item-input(type='search' autocomplete='off')
+
+block after_scripts
+ script
+ include search.js
+
+
+block after_head
+ link(rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css')
diff --git a/views/global/sysdeps.js b/views/global/sysdeps.js
new file mode 100644
index 0000000..7105fbc
--- /dev/null
+++ b/views/global/sysdeps.js
@@ -0,0 +1,13 @@
+$(function(){
+ new DataTable('table', {
+ searching: true,
+ paging: false,
+ lengthChange: false,
+ info: false,
+ language: { search: "Filter: "},
+ initComplete: function () {
+ $('div.dt-search').addClass("float-end")
+ $('div.dt-search input').removeClass('dt-input').addClass('m-2')
+ }
+ });
+});
diff --git a/views/global/sysdeps.pug b/views/global/sysdeps.pug
new file mode 100644
index 0000000..4e7445d
--- /dev/null
+++ b/views/global/sysdeps.pug
@@ -0,0 +1,36 @@
+extends global
+
+block content
+ #content-container.container-fluid
+ table.table
+ thead
+ tr.border-bottom.border-dark
+ th Library
+ th Description
+ th Debian (Headers)
+ th Used by R packages
+ tbody
+ each x in sysdeps
+ tr
+ th.text-nowrap #{x.library}
+ if x.homepage
+ a.ms-1(href=x.homepage target='_blank')
+ sup.fa-solid.fa-up-right-from-square
+ td.text-nowrap #{x.description}
+ td
+ each deb in x.headers
+ a.text-nowrap(href=`https://packages.ubuntu.com/${x.distro}/${deb}`) #{deb}
+ br
+ td
+ each pkg in x.usedby
+ a.text-nowrap.text-dark(href=`https://${pkg.owner}.r-universe.dev/${pkg.package}`) #{pkg.package}
+ |
+
+block after_scripts
+ script(src='https://cdn.datatables.net/2.1.3/js/dataTables.min.js')
+ script
+ include sysdeps.js
+
+
+block after_head
+ link(rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css')
diff --git a/views/global/updates.js b/views/global/updates.js
new file mode 100644
index 0000000..e69de29
diff --git a/views/includes/default-scripts.pug b/views/includes/default-scripts.pug
index 1fb26e6..da7c55d 100644
--- a/views/includes/default-scripts.pug
+++ b/views/includes/default-scripts.pug
@@ -1,4 +1,5 @@
script(src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.slim.min.js')
script(src='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.3.2/js/bootstrap.bundle.min.js')
+script(src='https://cdnjs.cloudflare.com/ajax/libs/masonry/4.2.2/masonry.pkgd.min.js')
if node_env == 'production'
script(src='https://r-universe.dev/matomo.js' async)
diff --git a/views/search.pug b/views/search.pug
deleted file mode 100644
index d38054b..0000000
--- a/views/search.pug
+++ /dev/null
@@ -1,108 +0,0 @@
-doctype html
-html
- head
- meta(charset='utf-8')
- meta(name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no')
- link(rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.3.2/css/bootstrap.min.css')
- link(rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css')
- link(rel='stylesheet' href='/layout.css')
- link(rel='stylesheet' href='/search.css')
- title R-universe - Discover and Publish R Packages
-
- body
- .row
- .col.p-4
- .container
- .d-flex.justify-content-center
- svg(viewbox='250 250 300 150' preserveaspectratio='xMinYMid slice')
- g#rocket
- path(d='M445.3,256.3c-47.3-3.5-62.8,27-62.8,27c-18.5,2-24,10-24,10l15.5,12.3c-0.5,15.3,3.5,16.5,3.5,16.5c5.5,7.5,17.8,4.8,17.8,4.8l12.8,17c12-12.5,10.3-25.5,10.3-25.5C450.8,300,445.3,256.3,445.3,256.3zM416.3,297c-6.5,0-11.8-5.3-11.8-11.8c0-6.5,5.3-11.8,11.8-11.8s11.8,5.3,11.8,11.8C428,291.7,422.7,297,416.3,297z')
- circle
- polygon(points='371.3,338.6 355.7,344.7 363.1,329.7')
- #searchbox.row.justify-content-center
- .col-12.col-lg-10.col-xl-8.col-xxl-7
- .input-group.input-group-lg
- input#search-input.form-control(type='text' autocomplete='off' placeholder='Search for a package, author, topic, keyword, ...')
- button#search-button.btn.btn-outline-secondary(type='button') Search
- button.btn.btn-outline-secondary.dropdown-toggle.dropdown-toggle-split(type='button' data-bs-toggle='collapse' data-bs-target='#extra-search-fields')
- #extra-search-fields.card.card-body.border-top-0.collapse.noanim.shadow(style='margin-right: 46px;')
- #results-placeholder.py-4(style='max-width: 920px; margin: auto;')
- p.summary-paragraph
- | Currently serving
- span.ps-1#summary-n-packages ..
- a.ps-1(href='../builds') packages
- | ,
- span.ps-1#summary-n-datasets ..
- | datasets, and
- span.ps-1#summary-n-articles ..
- a.ps-1(href='../articles') articles
- | by
- span.ps-1#summary-n-maintainers ..
- a.ps-1(href='../maintainers') maintainers
- | and
- span.ps-1#summary-n-contributors ..
- | contributors.
- | Not sure what to search for? Why not try:
- span.ps-1#topics-list
- .d-none.d-xl-block
- br
- br
- #maintainers-carousel.carousel.slide.border-top.border-bottom.mt-4.text-center(data-bs-ride='carousel')
- span.bg-white.position-relative.d-inline.p-2.text-muted(style='top: -1em;') Organizations
- button.text-decoration-none.btn.btn-link.carousel-control.carousel-control-prev(type='button' data-bs-target='#maintainers-carousel' data-bs-slide='prev')
- i.fas.fa-angle-double-left
- button.text-decoration-none.btn.btn-link.carousel-control.carousel-control-next(type='button' data-bs-target='#maintainers-carousel' data-bs-slide='next')
- i.fas.fa-angle-double-right
- .carousel-inner
- p(style='margin-top: 50px;')
- | Want to learn more about r-universe? Have a look at
- a.mx-1(href='https://ropensci.org/r-universe') ropensci.org/r-universe
- | or updates from the rOpenSci blog:
- ul.blog-posts
- .container(style='max-width: 1440px')
- #search-results-comment.text-secondary.search-results.col-12.mt-2
- .row#search-results(data-masonry='{"percentPosition": true }')
-
- #templatezone.d-none
- .package-description-item.col-md-12.col-xl-6
- .card.mt-4.package-description-card
- .card-body
- .float-end.m-2(style="max-width: 140px;")
- img.img.border.border-secondary-subtle.rounded.p-1.mx-1(src='https://r-universe.dev/static/nobody.jpg' style='max-height: 120px;')
- p.pt-1.m-1.text-center.package-org.text-secondary
- a.package-link.text-decoration-none.text-reset
- h2.h5.pb-1
- span.package-name.text-dark
- | :
- small.ms-1.text-secondary.package-title
- p.card-text.package-description.fst-italic
- p.card-text.pt-2
- small.text-muted.description-maintainer
- small.text-muted.description-last-updated
- p.card-text.description-topics.d-none
- span.me-1.text-muted.badge.badge-light.description-score.d-none.p-1.border
- i.fas.fa-chart-bar.text-primary
- span.me-1.text-muted.badge.badge-light.description-stars.d-none.p-1.border
- i.fas.fa-star.text-warning
- span.me-1.text-muted.badge.badge-light.description-pkgscore.p-1.border.d-none
- i.fas.fa-chart-line.text-primary
- span.me-1.text-muted.badge.badge-light.description-dependencies.p-1.border.d-none
- i.fas.fa-cube.text-danger
- span.me-1.text-muted.badge.badge-light.description-dependents.p-1.border.d-none
- i.fas.fa-cubes.text-success
-
- .organization-item.col-xl-2.col-md-3.col-4.mb-2
- a.card.maintainer-card.border-0.text-decoration-none.p-2
- img.card-img-top.img-rounded
- .card-body.px-0.py-1
- p.card-text.text-center.text-muted.text-truncate
- .carousel-item(data-interval='5000')
- .row.justify-content-center.maintainer-row
- .form-group.row.search-field-item.mb-3
- label.col-sm-3.col-lg-2.col-form-label.col-form-label-sm
- .col-sm-9.col-lg-10
- input.form-control.form-control-sm.search-item-input(type='search' autocomplete='off')
-
- include includes/default-scripts.pug
- script(src='https://cdnjs.cloudflare.com/ajax/libs/masonry/4.2.2/masonry.pkgd.min.js')
- script(src='/search.js')