WIP - initial filtering working

This commit is contained in:
Dave Lane 2024-08-22 21:57:10 +12:00
parent e0fd7714be
commit 429087ce80
3 changed files with 215 additions and 31 deletions

8
package-lock.json generated
View file

@ -23,7 +23,6 @@
"prettier": "^3.1.1", "prettier": "^3.1.1",
"prettier-plugin-svelte": "^3.1.2", "prettier-plugin-svelte": "^3.1.2",
"svelte": "^4.2.7", "svelte": "^4.2.7",
"svelte-popover": "^2.0.8",
"vite": "^5.0.3", "vite": "^5.0.3",
"vitest": "^2.0.0" "vitest": "^2.0.0"
} }
@ -3112,13 +3111,6 @@
"svelte": "^3.19.0 || ^4.0.0" "svelte": "^3.19.0 || ^4.0.0"
} }
}, },
"node_modules/svelte-popover": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/svelte-popover/-/svelte-popover-2.0.8.tgz",
"integrity": "sha512-Yvz4FpvvXc5aBGyIE/TlGlqULLKkcfc7N0MoaqNXHMRVXxla+sc7G1xSCCURP3VqP0nZ5cIyPWOLLWFXuJjTRA==",
"dev": true,
"license": "ISC"
},
"node_modules/svelte-preprocess": { "node_modules/svelte-preprocess": {
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-6.0.2.tgz", "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-6.0.2.tgz",

View file

@ -22,7 +22,6 @@
"prettier": "^3.1.1", "prettier": "^3.1.1",
"prettier-plugin-svelte": "^3.1.2", "prettier-plugin-svelte": "^3.1.2",
"svelte": "^4.2.7", "svelte": "^4.2.7",
"svelte-popover": "^2.0.8",
"vite": "^5.0.3", "vite": "^5.0.3",
"vitest": "^2.0.0" "vitest": "^2.0.0"
}, },

View file

@ -1,14 +1,12 @@
<script> <script>
// get the data from the JSON Webservices feed in +page.js
export let data; export let data;
const { webservices } = data; const { webservices } = data;
//console.log('orig:', webservices);
//console.log('orig affiliates:', webservices.affiliates);
//console.log('orig hosts:', webservices.hosts);
// source: https://mokole.com/palette.html 20 colors with default settings otherwise // source: https://mokole.com/palette.html 20 colors with default settings otherwise
const colours30 = [ "#808080", "#7f0000", "#006400", "#808000", "#483d8b", "#008b8b", "#cd853f", "#00008b", "#7f007f", "#8fbc8f", "#b03060", "#ff0000", "#ff8c00", "#00ff00", "#9400d3", "#00ff7f", "#dc143c", "#00ffff", "#00bfff", "#0000ff", "#f08080", "#adff2f", "#1e90ff", "#ffff54", "#90ee90", "#add8e6", "#ff1493", "#7b68ee", "#ee82ee", "#ffe4b5" ]; const colours30 = [ "#808080", "#7f0000", "#006400", "#808000", "#483d8b", "#008b8b", "#cd853f", "#00008b", "#7f007f", "#8fbc8f", "#b03060", "#ff0000", "#ff8c00", "#00ff00", "#9400d3", "#00ff7f", "#dc143c", "#00ffff", "#00bfff", "#0000ff", "#f08080", "#adff2f", "#1e90ff", "#ffff54", "#90ee90", "#add8e6", "#ff1493", "#7b68ee", "#ee82ee", "#ffe4b5" ];
// digest and return useful info from the Webservices JSON feed
function processData(webservices) { function processData(webservices) {
let instances = 0; let instances = 0;
let current = []; let current = [];
@ -96,12 +94,6 @@
// affiliate_data[key] = affiliate; // affiliate_data[key] = affiliate;
// } // }
} }
//console.log('categories: ', categories);
//console.log('analogues: ', analogues);
//console.log('licenses: ', licenses);
//console.log('statuses: ', statuses);
//console.log('affiliates: ', affiliates);
//console.log('hosts: ', hosts);
return { return {
total_instances: instances, total_instances: instances,
@ -123,11 +115,13 @@
} }
// console.log(technologies); // console.log(technologies);
// return true if a tech object includes valid instances
function hasInstances(tech) { function hasInstances(tech) {
if (tech.hasOwnProperty('instances') && tech.instances.constructor === Array && tech.instances.length) return true; if (tech.hasOwnProperty('instances') && tech.instances.constructor === Array && tech.instances.length) return true;
return false; return false;
} }
// return an array of keys from an object
function getKeys(ob) { function getKeys(ob) {
let keys = []; let keys = [];
for (let key in ob) { for (let key in ob) {
@ -136,14 +130,37 @@
return keys; return keys;
} }
// get intersection: ref https://bobbyhadz.com/blog/javascript-get-intersection-of-two-arrays
function getIntersection(a, b) {
const set1 = new Set(a);
const set2 = new Set(b);
const intersection = [...set1].filter(
element => set2.has(element)
);
return intersection;
}
// combine an array of terms into a sentence with proper Oxford commas, dealing with the special cases
// of one or two elements.
function toOxfordCommaString(arr) { function toOxfordCommaString(arr) {
if (arr.length == 1) return arr; if (arr.length == 1) return arr;
if (arr.length == 2) return arr.join(' and ');
else { else {
var last = arr.pop(); var last = arr.pop();
return arr.join(', ') + ', and ' + last; return arr.join(', ') + ', and ' + last;
} }
} }
// return a singular or plural term depending on the value of 'count'
function pluraliser(singular, plural, count) {
if (count > 1) return plural;
return singular;
}
// assign colours from a set of differentiated colours to a list of tags...
// this one is for 'hosts'
function hostColours(host_list, colours, hosts) { function hostColours(host_list, colours, hosts) {
let host_array = {}; let host_array = {};
let i = 0; let i = 0;
@ -163,31 +180,76 @@
return host_array; return host_array;
} }
// assign colours from a set of differentiated colours to a list of tags...
// this one is for 'affiliates'
function affiliateColours(affiliate_list, colours, affiliates) { function affiliateColours(affiliate_list, colours, affiliates) {
let affiliate_array = {}; let affiliate_result = {};
let i = 0; let i = 0;
//console.log('affiliates:', affiliates); //console.log('affiliates:', affiliates);
console.log('affiliate_list:', affiliate_list);
for (const affiliate of affiliate_list) { for (const affiliate of affiliate_list) {
//console.log('affiliate:', affiliate); //console.log('affiliate:', affiliate);
if (affiliates[affiliate].hasOwnProperty('name') && affiliates[affiliate].hasOwnProperty('website')) { if (affiliates[affiliate].hasOwnProperty('name') && affiliates[affiliate].hasOwnProperty('website')) {
affiliate_array[affiliate] = { affiliate_result[affiliate] = {
"colour": colours[i++], "colour": colours[i++],
"name": affiliates[affiliate].name, "name": affiliates[affiliate].name,
"website": affiliates[affiliate].website "website": affiliates[affiliate].website
} }
} }
} }
return affiliate_array; return affiliate_result;
} }
// sort technologies alphabetically by name
function sortTechnologies(technologies) {
return technologies.sort((a,b) => a.name.localeCompare(b.name));
}
// generic function to find the intersection of two arrays
// approach ref: https://bobbyhadz.com/blog/javascript-get-intersection-of-two-arrays
function inCommon(a, b) {
const set1 = new Set(a);
const set2 = new Set(b);
const intersection = [...set1].filter(
element => set2.has(element)
);
console.log('intersection = ', intersection);
return intersection;
}
// filter technologies based on a list of Categories
function filterTechnologiesByCategoryList(technologies, list) {
console.log('looking for tech in categories: ', list);
const included = [];
technologies.forEach(function(tech, index) {
//console.log('tech(' + index + '): ', tech);
if (hasInstances(tech)) {
const intersection = inCommon(tech.categories, list);
console.log('categories: ', tech.categories);
//console.log('list: ', list);
console.log('intersection: ', intersection);
if (intersection && intersection.constructor === Array) {
//console.log('intersection length: '. intersection.length);
if (intersection.length > 0) {
console.log('including tech: ', tech);
included.push(tech);
}
} else {
console.log('intersection not array');
}
}
});
console.log('found ' + included.length + ' technologies.')
return included;
}
const results = processData(webservices); const results = processData(webservices);
//console.log('results: ', results); //console.log('results: ', results);
//const technologies = webservices.technologies; //const technologies = webservices.technologies;
const technologies = results.active_services;
//console.log(technologies); //console.log(technologies);
const category_list = getKeys(results.tech_lists.category_list).sort(); const category_list = getKeys(results.tech_lists.category_list).sort();
@ -203,12 +265,127 @@
//console.log('affiliate_data: ', results.affiliates); //console.log('affiliate_data: ', results.affiliates);
const host_colours = hostColours(host_list, colours30, hosts); const host_colours = hostColours(host_list, colours30, hosts);
//console.log('host_colours:', host_colours); //console.log('host_colours:', host_colours);
colours30.sort();
const affiliate_colours = affiliateColours(affiliate_list, colours30, affiliates); const affiliate_colours = affiliateColours(affiliate_list, colours30, affiliates);
//console.log('affiliate_colours:',affiliate_colours); //console.log('affiliate_colours:',affiliate_colours);
//console.log('categories array: ', results.tech_tags.categories); //console.log('categories array: ', results.tech_tags.categories);
//console.log('categories keys: ', categories); //console.log('categories keys: ', categories);
console.log('category_list: ', category_list);
//console.log('analogue_list: ', analogue_list);
console.log('license_list: ', license_list);
console.log('status_list: ', status_list);
console.log('affiliate_list: ', affiliate_list);
console.log('hosts_list: ', host_list);
// filter technolologies based on a subset of categories
const full_category_list = [
'App Ecosystem',
'Application Design',
'Asset Management',
'Association Management',
'Association Management System',
'Blog Syndication',
'Book Reviews',
'Calendar',
'Collaborative',
'Collaborative Markdown Editing',
'Collaborative Wiki',
'Content Management Systems',
'Digital Media Sales',
'Document Management',
'Email Marketing Automation',
'Email Services',
'Enterprise Resource Planning',
'Event Management',
'Facial Recognition',
'Facility Booking',
'Federated Messaging',
'Fediverse',
'File Synchronisation',
'Forum',
'Framasoft',
'Home Automation',
'Home Security',
'Image Backup',
'Image Gallery',
'Kanban Project Management',
'Learning Management System',
'Link Ranking',
'Link Sharing',
'Link Shortener',
'Membership Management',
'Messaging Cache',
'Micro-blogging',
'Multi-Domain Server',
'Multimedia',
'Music Discovery',
'Music Streaming',
'Network Infrastructure Monitoring',
'Newsletters',
'Online Forms',
'Password Manager',
'Photos',
'Portfolio Management',
'Privacy',
'Productivity',
'Reading List',
'Remote Desktop Sharing',
'Remote Incremental Encrypted System Backups',
'Reverse Proxy',
'Rich Messaging Client',
'Rich Messaging Server',
'Scheduling',
'Server Monitoring',
'Share Secrets Management',
'Single Sign-On',
'Social Bookmarking',
'Social Media',
'Software Development Forge',
'Software Interface Designer',
'Streaming',
'Surveys',
'TURN/STUN Server',
'Video',
'Video Conferencing',
'Video Surveillance',
'Visualisation of Time Series Data',
'Webmail',
'Webserver',
'Website Analytics'
];
const copyleft_list = [
'AGPL-3',
'AGPL-3+',
'GPL',
'GPL-2',
'GPL-2+',
'GPL-3',
'GPL-3+',
'LGPL-3',
];
const category_filter_list = [
'Fediverse',
'Social Media',
'Software Development Forge',
'Software Interface Designer',
'Streaming',
'Surveys',
'TURN/STUN Server',
'Video',
'Video Conferencing',
'Video Surveillance',
'Visualisation of Time Series Data',
'Webmail',
'Webserver',
'Website Analytics'
];
const filtered_technologies = filterTechnologiesByCategoryList(results.active_services, category_filter_list);
const technologies = sortTechnologies(filtered_technologies);
</script> </script>
<div class="webservices"> <div class="webservices">
@ -231,10 +408,15 @@
</div> </div>
</div> </div>
<div class="tiles"> <div class="tiles">
{#each technologies as technology} {#each filtered_technologies as technology}
<div class="tile technology"> <div class="tile technology">
<div class="links">
{#if (technology.repository)}<span class="repository"><a href="{technology.repository}" title="The source code repository for {technology.name}">R</a></span>{/if}
{#if (technology.wikipedia)}<span class="wikipedia"><a href="{technology.wikipedia}" title="Wikipedia Page for {technology.name}">W</a></span>{/if}
</div>
<h2><a href="{technology.website}">{technology.name}</a></h2> <h2><a href="{technology.website}">{technology.name}</a></h2>
{#if (technology.license)}<p class="license" title="The libre license for this project is {technology.license}">License: <span class="value">{technology.license}</span></p>{/if} {#if (technology.license)}<p class="license" title="The libre license for this project is {technology.license}">License: <span class="value">{technology.license}</span></p>{/if}
{#if (technology.categories)}<p class="categories">{pluraliser('Category','Categories',technology.categories.length)}: <span class="value">{toOxfordCommaString(technology.categories)}</span></p>{/if}
{#if (technology.analogues)}<p class="analogues">Alternative to <span class="value">{toOxfordCommaString(technology.analogues)}</span></p>{/if} {#if (technology.analogues)}<p class="analogues">Alternative to <span class="value">{toOxfordCommaString(technology.analogues)}</span></p>{/if}
<p class="description">{technology.description}</p> <p class="description">{technology.description}</p>
{#if hasInstances(technology)} {#if hasInstances(technology)}
@ -310,12 +492,12 @@
.tiles { .tiles {
display: grid; display: grid;
grid-gap: 15px; grid-gap: 15px;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(270px, 1fr));
} }
/* flip card stuff: https://www.w3schools.com/howto/howto_css_flip_card.asp */ /* flip card stuff: https://www.w3schools.com/howto/howto_css_flip_card.asp */
.tile { .tile {
height: 400px; height: 500px;
min-width: 250px; min-width: 270px;
max-width: 400px; max-width: 400px;
overflow: hidden; overflow: hidden;
box-sizing: border-box; box-sizing: border-box;
@ -324,7 +506,18 @@
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
background-color: #eee;
} }
.tile .links {
position: absolute;
top: 0;
right: 0;
padding: 2px 6px;
font-size: 90%;
}
.tile .links a { color: #e6c4fc; }
.tile .links a:visited { color: #ced0ff; }
.tile .links a:hover { color: #fff; }
.tile h2 { .tile h2 {
background-color: #999; background-color: #999;
text-align: center; text-align: center;
@ -335,7 +528,7 @@
.tile h2 a:visited { color: #ced0ff; } .tile h2 a:visited { color: #ced0ff; }
.tile h2 a:hover { color: #fff; } .tile h2 a:hover { color: #fff; }
.tile .description { .tile .description {
height: 200px; height: 260px;
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
} }
@ -345,7 +538,7 @@
color: #555; color: #555;
} }
.instances { .instances {
background-color: #eee; background-color: #ddd;
position: absolute; position: absolute;
bottom: 0; bottom: 0;
height: 3em; height: 3em;
@ -357,14 +550,14 @@
.webservices li { list-style-type: none; } .webservices li { list-style-type: none; }
.value { font-weight: bold; color: #000; } .value { font-weight: bold; color: #000; }
.marker { vertical-align: middle; } .marker { vertical-align: middle; }
.triangle { /* .triangle {
--side-size: 20px; --side-size: 20px;
border-left: var(--side-size) solid transparent; border-left: var(--side-size) solid transparent;
border-right: var(--side-size) solid transparent; border-right: var(--side-size) solid transparent;
border-bottom: calc(2 * var(--side-size) * 0.866) solid green; border-bottom: calc(2 * var(--side-size) * 0.866) solid green;
border-top: var(--side-size) solid transparent; border-top: var(--side-size) solid transparent;
display: inline-block; display: inline-block;
} }*/
.circle { .circle {
height: 20px; height: 20px;
width: 20px; width: 20px;