diff --git a/_includes/scripts.html b/_includes/scripts.html index ccd8c9ccfb0..32fe28aae30 100644 --- a/_includes/scripts.html +++ b/_includes/scripts.html @@ -77,6 +77,16 @@ <% }) %> +
+ + +
<% let count=0 %> <% _.each(projects, function(project) { %> <% diff --git a/javascripts/main.js b/javascripts/main.js index 77a2401e044..c4258e2ab7b 100644 --- a/javascripts/main.js +++ b/javascripts/main.js @@ -64,12 +64,19 @@ define([ return `about ${Math.round(elapsed / msPerYear)} years ago`; } - const renderProjects = function (projectService, tags, names, labels, date) { + const renderProjects = function ( + projectService, + tags, + names, + labels, + date, + platforms + ) { const allTags = projectService.getTags(); projectsPanel.html( compiledtemplateFn({ - projects: projectService.get(tags, names, labels, date), + projects: projectService.get(tags, names, labels, date, platforms), relativeTime, tags: allTags, popularTags: projectService.getPopularTags(6), @@ -78,6 +85,9 @@ define([ selectedNames: names, labels: projectService.getLabels(), selectedLabels: labels, + platforms: projectService.getPlatforms(), + popularPlatforms: projectService.getPopularPlatforms(4), + selectedPlatforms: platforms, }) ); date = date || 'invalid'; @@ -172,6 +182,40 @@ define([ } }); }); + + projectsPanel.find('ul.popular-platforms li a').each((i, elem) => { + // add selected class on load + let selPlatforms = getParameterByName('platforms') || ''; + if (selPlatforms) { + selPlatforms = selPlatforms.split(','); + for (let i = 0; i < selPlatforms.length; i++) { + $(`a:contains(${selPlatforms[i]})`).addClass('selected'); + } + } + + $(elem).on('click', function (e) { + e.preventDefault(); + + $(this).toggleClass('selected'); + + let curPlatform = $(this).text().split('(')[0].trim() || ''; + let selPlatforms = getParameterByName('platforms') || ''; + if (selPlatforms.indexOf(curPlatform) === -1) { + selPlatforms += selPlatforms ? `,${curPlatform}` : curPlatform; + } else { + selPlatforms = selPlatforms + .split(',') + .filter((platform) => platform !== curPlatform) + .join(','); + } + + location.href = updateQueryStringParameter( + getFilterUrl(), + 'platforms', + encodeURIComponent(selPlatforms) + ); + }); + }); }; /* @@ -228,6 +272,7 @@ define([ $('#back2Top').fadeOut(); } }); + $(document).ready(() => { $('#back2Top').click((event) => { event.preventDefault(); @@ -335,8 +380,9 @@ define([ const labels = prepareForHTML(getParameterByName('labels')); const names = prepareForHTML(getParameterByName('names')); const tags = prepareForHTML(getParameterByName('tags')); + const platforms = prepareForHTML(getParameterByName('platforms')); const date = getParameterByName('date'); - renderProjects(projectsSvc, tags, names, labels, date); + renderProjects(projectsSvc, tags, names, labels, date, platforms); }); this.get('/', () => { diff --git a/javascripts/projectsService.js b/javascripts/projectsService.js index 41e43cdce35..448b22cae1a 100644 --- a/javascripts/projectsService.js +++ b/javascripts/projectsService.js @@ -196,6 +196,46 @@ define(['underscore', 'tag-builder', 'project-ordering'], ( return _.flatten(results, (arr1, arr2) => arr1.append(arr2)); }; + /* + * The function here is used for front end filtering when given + * selecting certain projects. It ensures that only the selected projects + * are returned. If none of the platforms was added to the filter. + * Then it fallsback to show all the projects. + * @param Array projects : An array having all the Projects in _data + * @param Array projectPlatformsSorted : This is another array showing all the + * projects in a sorted order + * @param Array platforms : This is an array with the given platform filters. + */ + const applyPlatformsFilter = function ( + projects, + projectPlatformsSorted, + platforms + ) { + if (typeof platforms === 'string') { + platforms = platforms.split(','); + } + + platforms = _.map( + platforms, + (entry) => entry && entry.replace(/^\s+|\s+$/g, '') + ); + + if (!platforms || !platforms.length || platforms[0] == '') { + return projects; + } + + // find all projects with the given platforms + results = _.map(platforms, (hostname) => { + return _.filter(projects, (project) => { + const curHostname = new URL(String(project.upforgrabs.link)).hostname; + return curHostname === hostname; + }); + }); + + // the above statements returns n arrays in an array, which we flatten here and return then + return _.flatten(results, (arr1, arr2) => arr1.append(arr2)); + }; + const extractTags = function (projectsData) { const tagBuilder = new TagBuilder(); _.each(projectsData, (entry) => { @@ -218,6 +258,7 @@ define(['underscore', 'tag-builder', 'project-ordering'], ( const tagsMap = {}; const namesMap = {}; const labelsMap = {}; + const platformsMap = {}; const projects = orderAllProjects(_projectsData.projects, (length) => _.shuffle(_.range(length)) @@ -228,7 +269,7 @@ define(['underscore', 'tag-builder', 'project-ordering'], ( }); _.each(_projectsData.projects, (project) => { - if (project.name.toLowerCase) { + if (project.name) { namesMap[project.name.toLowerCase()] = project; } }); @@ -237,7 +278,20 @@ define(['underscore', 'tag-builder', 'project-ordering'], ( labelsMap[project.upforgrabs.name.toLowerCase()] = project.upforgrabs; }); - this.get = function (tags, names, labels, date) { + _.each(_projectsData.projects, (project) => { + const platform = new URL(project.upforgrabs.link); + + if (platform.hostname in platformsMap) { + platformsMap[platform.hostname].frequency += 1; + } else { + platformsMap[platform.hostname] = { + hostname: platform.hostname, + frequency: 1, + }; + } + }); + + this.get = function (tags, names, labels, date, platforms) { let filteredProjects = projects; if (names && names.length) { filteredProjects = applyNamesFilter( @@ -263,6 +317,13 @@ define(['underscore', 'tag-builder', 'project-ordering'], ( tags ); } + if (platforms && platforms.length) { + filteredProjects = applyPlatformsFilter( + filteredProjects, + this.getPlatforms(), + platforms + ); + } return filteredProjects; }; @@ -270,6 +331,10 @@ define(['underscore', 'tag-builder', 'project-ordering'], ( return _.sortBy(tagsMap, (entry) => entry.name.toLowerCase()); }; + this.getPlatforms = function () { + return _.sortBy(platformsMap, (entry) => entry.hostname.toLowerCase()); + }; + this.getNames = function () { return _.sortBy(namesMap, (entry) => entry.name.toLowerCase()); }; @@ -281,6 +346,16 @@ define(['underscore', 'tag-builder', 'project-ordering'], ( this.getPopularTags = function (popularTagCount) { return _.take(_.values(tagsMap), popularTagCount || 10); }; + + this.getPopularPlatforms = function (popularPlatformCount) { + return _.take( + _.sortBy( + _.values(platformsMap), + (platform) => platform.frequency + ).reverse(), + popularPlatformCount || 6 + ); + }; }; return ProjectsService; diff --git a/package-lock.json b/package-lock.json index f4780eb8c34..052aa9d25c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3690,6 +3690,7 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, + "license": "MIT", "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", diff --git a/stylesheets/stylesheet.css b/stylesheets/stylesheet.css index b5d4bea7925..c30545e29e9 100644 --- a/stylesheets/stylesheet.css +++ b/stylesheets/stylesheet.css @@ -883,20 +883,23 @@ form { width: 16px; } -.popular-tags, +.popular-tags, +.popular-platforms, .tags { display: flex; flex-wrap: wrap; gap: 1em 0.5em; } -ul.popular-tags li, +ul.popular-tags li, +ul.popular-platforms li, ul.tags li { list-style: none; padding-left: 0; } .popular-tags a, +.popular-platforms a, .tags a { display: inline-flex; align-content: center; @@ -916,6 +919,7 @@ ul.tags li { .popular-tags a:hover, .popular-tags a:focus, .popular-tags a:focus-within, +.popular-platforms a:hover, .tags a:hover, .tags a:focus, .tags a:focus-within { @@ -924,12 +928,22 @@ ul.tags li { color: var(--databox-text); } -.popular-tags a span.popular-tags__frequency { +.popular-tags a span.popular-tags__frequency, +.popular-platforms a span.popular-platforms__frequency { margin-left: 0.45em; font-size: 10px; color: var(--body-color); } +.popular-platforms a.selected { + background-color: var(--databox-text); + color: var(--abs); +} + +.popular-platforms a.selected span.popular-platforms__frequency { + color: var(--abs); +} + @media (max-width: 768px) { body { padding: 0 0;