WIP - initial filtering working
This commit is contained in:
parent
e0fd7714be
commit
429087ce80
3 changed files with 215 additions and 31 deletions
8
package-lock.json
generated
8
package-lock.json
generated
|
@ -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",
|
||||||
|
|
|
@ -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"
|
||||||
},
|
},
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue