WIP, added nav, about & attribution pages, and updated styles to ensure tags flow properly

This commit is contained in:
Dave Lane 2024-08-26 17:00:21 +12:00
parent 429087ce80
commit 6d3723cc11
4 changed files with 738 additions and 532 deletions

View file

@ -3,5 +3,37 @@
</script> </script>
<nav>
<a href="/">Web Services</a>
<a href="/about">About</a>
</nav>
<!-- content provided by the +page.svelte file in this directory - and sub-directories -->
<slot /> <slot />
<footer>
<div class="repo"><a href="https://forge.magnificent.nz/lightweight/webservices-app" title="Access the source code repository for this site">Code for this site</a></div>
<div class="copyright">Copyleft 2024 by <a href="https://davelane.nz">Dave Lane</a></div>
<div class="attribution"><a href="/attribution" title="All images used on this site are made available by their authors under a Creative Commons license">Attribution</a></div>
</footer>
<style>
body {
width: 96%;
margin: auto;
}
nav {
margin: auto;
}
nav a {
margin-right: 20px;
}
footer {
display: inline-grid;
margin: auto;
grid-template-columns: 1fr 1fr 1fr;
}
footer div {
text-align: center;
}
</style>

View file

@ -4,7 +4,38 @@
const { webservices } = data; const { webservices } = data;
// 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 // digest and return useful info from the Webservices JSON feed
function processData(webservices) { function processData(webservices) {
@ -30,14 +61,14 @@
// pull out relevant info in useful chunks. // pull out relevant info in useful chunks.
for (let key in webservices.technologies) { for (let key in webservices.technologies) {
let tech = webservices.technologies[key]; let tech = webservices.technologies[key];
if (tech.hasOwnProperty('categories') && tech.categories.constructor === Array ) { if (tech.hasOwnProperty('categories') && tech.categories.constructor === Array) {
tech.categories.forEach(function(category, index) { tech.categories.forEach(function (category, index) {
if (category_list.hasOwnProperty(category)) category_list[category]++; if (category_list.hasOwnProperty(category)) category_list[category]++;
else category_list[category] = 1; else category_list[category] = 1;
}); });
} }
if (tech.hasOwnProperty('analogues') && tech.analogues.constructor === Array ) { if (tech.hasOwnProperty('analogues') && tech.analogues.constructor === Array) {
tech.analogues.forEach(function(analogue, index) { tech.analogues.forEach(function (analogue, index) {
if (analogue_list.hasOwnProperty(analogue)) analogue_list[analogue]++; if (analogue_list.hasOwnProperty(analogue)) analogue_list[analogue]++;
else analogue_list[analogue] = 1; else analogue_list[analogue] = 1;
}); });
@ -52,7 +83,7 @@
tech['name'] = key; tech['name'] = key;
current.push(tech); current.push(tech);
//console.log(tech.name + ': ' + tech.instances.length + ' instances...'); //console.log(tech.name + ': ' + tech.instances.length + ' instances...');
tech.instances.forEach(function(instance, i) { tech.instances.forEach(function (instance, i) {
instances++; instances++;
if (instance.hasOwnProperty('status')) { if (instance.hasOwnProperty('status')) {
let tag = instance.status; let tag = instance.status;
@ -73,14 +104,16 @@
} else { } else {
future[key] = tech; future[key] = tech;
} }
}
};
for (let key in webservices.hosts) { for (let key in webservices.hosts) {
//console.log('key: ', key); //console.log('key: ', key);
let host = webservices.hosts[key]; let host = webservices.hosts[key];
host['name'] = key; host['name'] = key;
//console.log('host: ', host); //console.log('host: ', host);
if (host.hasOwnProperty('domain') && !(host.hasOwnProperty('status') && host.status == 'retired')) { if (
host.hasOwnProperty('domain') &&
!(host.hasOwnProperty('status') && host.status == 'retired')
) {
hosts[key] = host; hosts[key] = host;
} }
} }
@ -114,10 +147,15 @@
}; };
} }
// console.log(technologies); // console.log(technologies);
// return true if a tech object includes valid instances // 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;
} }
@ -125,8 +163,9 @@
function getKeys(ob) { function getKeys(ob) {
let keys = []; let keys = [];
for (let key in ob) { for (let key in ob) {
//keys.push(key.replace(/ /g, '\u00a0'));
keys.push(key); keys.push(key);
}; }
return keys; return keys;
} }
@ -135,9 +174,7 @@
const set1 = new Set(a); const set1 = new Set(a);
const set2 = new Set(b); const set2 = new Set(b);
const intersection = [...set1].filter( const intersection = [...set1].filter((element) => set2.has(element));
element => set2.has(element)
);
return intersection; return intersection;
} }
@ -167,14 +204,14 @@
//console.log('hosts:', hosts); //console.log('hosts:', hosts);
host_list.forEach(function(host) { host_list.forEach(function (host) {
//console.log(host); //console.log(host);
if (hosts[host].hasOwnProperty('domain') && hosts[host].hasOwnProperty('affiliation')) { if (hosts[host].hasOwnProperty('domain') && hosts[host].hasOwnProperty('affiliation')) {
host_array[host] = { host_array[host] = {
"colour": colours[i++], colour: colours[i++],
"domain": hosts[host].domain, domain: hosts[host].domain,
"affiliation": hosts[host].affiliation affiliation: hosts[host].affiliation
} };
} }
}); });
return host_array; return host_array;
@ -191,12 +228,15 @@
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_result[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_result; return affiliate_result;
@ -204,7 +244,7 @@
// sort technologies alphabetically by name // sort technologies alphabetically by name
function sortTechnologies(technologies) { function sortTechnologies(technologies) {
return technologies.sort((a,b) => a.name.localeCompare(b.name)); return technologies.sort((a, b) => a.name.localeCompare(b.name));
} }
// generic function to find the intersection of two arrays // generic function to find the intersection of two arrays
@ -213,9 +253,7 @@
const set1 = new Set(a); const set1 = new Set(a);
const set2 = new Set(b); const set2 = new Set(b);
const intersection = [...set1].filter( const intersection = [...set1].filter((element) => set2.has(element));
element => set2.has(element)
);
console.log('intersection = ', intersection); console.log('intersection = ', intersection);
return intersection; return intersection;
} }
@ -224,7 +262,7 @@
function filterTechnologiesByCategoryList(technologies, list) { function filterTechnologiesByCategoryList(technologies, list) {
console.log('looking for tech in categories: ', list); console.log('looking for tech in categories: ', list);
const included = []; const included = [];
technologies.forEach(function(tech, index) { technologies.forEach(function (tech, index) {
//console.log('tech(' + index + '): ', tech); //console.log('tech(' + index + '): ', tech);
if (hasInstances(tech)) { if (hasInstances(tech)) {
const intersection = inCommon(tech.categories, list); const intersection = inCommon(tech.categories, list);
@ -242,7 +280,7 @@
} }
} }
}); });
console.log('found ' + included.length + ' technologies.') console.log('found ' + included.length + ' technologies.');
return included; return included;
} }
@ -363,10 +401,69 @@
'GPL-2+', 'GPL-2+',
'GPL-3', 'GPL-3',
'GPL-3+', 'GPL-3+',
'LGPL-3', 'LGPL-3'
]; ];
const category_filter_list = [ const category_filter_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', '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', 'Social Media',
'Software Development Forge', 'Software Development Forge',
'Software Interface Designer', 'Software Interface Designer',
@ -382,15 +479,24 @@
'Website Analytics' 'Website Analytics'
]; ];
const filtered_technologies = filterTechnologiesByCategoryList(results.active_services, category_filter_list); const filtered_technologies = filterTechnologiesByCategoryList(
results.active_services,
category_filter_list
);
const technologies = sortTechnologies(filtered_technologies); const technologies = sortTechnologies(filtered_technologies);
</script> </script>
<div class="webservices"> <div class="webservices">
<h1>Web Services</h1> <h1>Web Services</h1>
<div class="introduction">
<p>
This sites exists to provide an 'always-up-to-date', in-depth description of the <a
href="https://tech.oeru.org/foss-libresoftware-its-about-clarity-and-values"
title="What do I mean by 'libre' software?">libre software</a> web services <a href="https://davelane.nz" title="Who is this Dave Lane character?">I</a> have set
up and maintain.
</p>
</div>
<div class="summary"> <div class="summary">
<ul> <ul>
<li>Total number of services: {results.total_instances}</li> <li>Total number of services: {results.total_instances}</li>
@ -398,35 +504,76 @@
</div> </div>
<div class="filters"> <div class="filters">
<div class="tag-list tech"> <div class="tag-list tech">
<div class="tags tech categories">Categories: {#each category_list as category}<span class="tag category">{category}</span> {/each}</div> <div class="tags tech categories">
Categories<br/> {#each category_list as category}<span class="tag category" style="white-space: nowrap; word-break: normal;">{category} xo</span><wbr/>{/each}
</div>
<!--<div class="tags tech analogues">{#each analogues as analogue}<span class="tag analogue">{analogue}</span> {/each}</div>--> <!--<div class="tags tech analogues">{#each analogues as analogue}<span class="tag analogue">{analogue}</span> {/each}</div>-->
<div class="tags tech licenses">Licenses: {#each license_list as license}<span class="tag license">{license}</span> {/each}</div> <div class="tags tech licenses">
Licenses<br/> {#each license_list as license}<span class="tag license">{license} xo</span><wbr/>{/each}
</div>
</div> </div>
<div class="tag-list instance"> <div class="tag-list instance">
<div class="tags instance statuses">Statuses: {#each status_list as status}<span class="tag status">{status}</span> {/each}</div> <div class="tags instance affiliates">Affiliates: {#each affiliate_list as affiliate}<span class="tag affiliate"><span class="marker circle" style="background-color: {affiliate_colours[affiliate].colour}"></span> {affiliate}</span> {/each}</div> <div class="tags instance statuses">
<div class="tags instance hosts">Hosts: {#each host_list as host}<span class="tag host"><span class="marker square" style="background-color: {host_colours[host].colour}"></span> {host}</span> {/each}</div> Statuses<br/> {#each status_list as status}<span class="tag status">{status} xo</span><wbr/>{/each}
</div>
<div class="tags instance affiliates">
Affiliates<br/> {#each affiliate_list as affiliate}<span class="tag affiliate"><span class="marker circle" style="background-color: {affiliate_colours[affiliate].colour}"></span> {affiliate} xo</span><wbr/>{/each}
</div>
<div class="tags instance hosts">
Hosts<br/> {#each host_list as host}<span class="tag host"><span class="marker square" style="background-color: {host_colours[host].colour}"></span> {host} xo</span><wbr/>{/each}
</div>
</div> </div>
</div> </div>
<div class="tiles"> <div class="tiles">
{#each filtered_technologies as technology} {#each filtered_technologies as technology}
<div class="tile technology"> <div class="tile technology">
<div class="links"> <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.repository}<span class="repository"
{#if (technology.wikipedia)}<span class="wikipedia"><a href="{technology.wikipedia}" title="Wikipedia Page for {technology.name}">W</a></span>{/if} ><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> </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
{#if (technology.categories)}<p class="categories">{pluraliser('Category','Categories',technology.categories.length)}: <span class="value">{toOxfordCommaString(technology.categories)}</span></p>{/if} class="license"
{#if (technology.analogues)}<p class="analogues">Alternative to <span class="value">{toOxfordCommaString(technology.analogues)}</span></p>{/if} title="The libre license for this project is {technology.license}"
<p class="description">{technology.description}</p> >
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}
<p class="description">
{technology.description}{#if technology.extended_description}<span
title={technology.extended_description}>i</span
>{/if}
</p>
{#if hasInstances(technology)} {#if hasInstances(technology)}
<div class="instances"><p>{#each technology.instances as instance} <div class="instances">
<a href="https://{instance.domain}" title="{technology.name} instance {instance.domain} hosted on '{instance.host}' by {instance.affiliation}"><span class="marker circle" style="background-color: {affiliate_colours[instance.affiliation].colour}"></span></a>{/each}</p> <p>
{#each technology.instances as instance}
<a
href="https://{instance.domain}"
title="{technology.name} instance {instance.domain} hosted on '{instance.host}' by {instance.affiliation}"
><span
class="marker circle"
style="background-color: {affiliate_colours[instance.affiliation].colour}"
></span></a
>{/each}
</p>
</div> </div>
{:else} {:else}
<div class="instances"> <div class="instances">Nothing here yet...</div>
Nothing here yet...
</div>
{/if} {/if}
</div> </div>
{/each} {/each}
@ -436,11 +583,15 @@
<style> <style>
.webservices { .webservices {
display: grid; display: grid;
width: 96%;
margin: 0 auto 3em auto; margin: 0 auto 3em auto;
padding: 0; padding: 0;
} }
.webservices h1 {
padding: 0;
}
.summary { .summary {
background-color: #ccc;
display: block;
} }
.filters { .filters {
/*background-color: #f1ff94; /*background-color: #f1ff94;
@ -455,9 +606,14 @@
display: block; display: block;
} }
.tag-list { .tag-list {
white-space: normal; /* white-space: normal;
word-break: normal; word-break: normal;
display: inline; display: inline;*/
}
.tags {
/* white-space: normal;
word-break: normal;
display: inline;*/
} }
.tag { .tag {
font-size: 80%; font-size: 80%;
@ -466,15 +622,16 @@
padding: 6px 8px; padding: 6px 8px;
border-radius: 14px; border-radius: 14px;
white-space: nowrap; white-space: nowrap;
word-break: keep-all; word-break: normal;
box-shadow: 3px 3px 3px #71ba71;
} }
.tag:after { content: "\00a0"; } .tag:hover { box-shadow: 4px 4px 3px #71ba71;}
.tag.category { .tag.category {
background-color: #a369a2; background-color: #9aa34d;
color: #fff; color: #fff;
} }
.tag.license { .tag.license {
background-color: #a369a2; background-color: #6498a3;
color: #fff; color: #fff;
} }
.tag.status { .tag.status {
@ -496,7 +653,7 @@
} }
/* 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: 500px; height: 515px;
min-width: 270px; min-width: 270px;
max-width: 400px; max-width: 400px;
overflow: hidden; overflow: hidden;
@ -515,18 +672,34 @@
padding: 2px 6px; padding: 2px 6px;
font-size: 90%; font-size: 90%;
} }
.tile .links a { color: #e6c4fc; } .tile:hover {
.tile .links a:visited { color: #ced0ff; } box-shadow: 10px 10px 6px #71ba71;
.tile .links a:hover { color: #fff; } }
.tile .links h2 a {
color: #e6c4fc;
}
.tile .links a:visited {
color: #ced0ff;
}
.tile .links a:hover,
.tile:hover h2 a {
color: #fff !important;
}
.tile h2 { .tile h2 {
background-color: #999; background-color: #999;
text-align: center; text-align: center;
padding: 0.5em; padding: 0.5em;
margin: 0; margin: 0;
} }
.tile h2 a { color: #e6c4fc; } .tile h2 a {
.tile h2 a:visited { color: #ced0ff; } color: #e6c4fc;
.tile h2 a:hover { color: #fff; } }
.tile h2 a:visited {
color: #ced0ff;
}
.tile h2 a:hover {
color: #fff;
}
.tile .description { .tile .description {
height: 260px; height: 260px;
text-overflow: ellipsis; text-overflow: ellipsis;
@ -547,17 +720,16 @@
.instances .marker { .instances .marker {
margin-right: 3px; margin-right: 3px;
} }
.webservices li { list-style-type: none; } .webservices li {
.value { font-weight: bold; color: #000; } list-style-type: none;
.marker { vertical-align: middle; } }
/* .triangle { .value {
--side-size: 20px; font-weight: bold;
border-left: var(--side-size) solid transparent; color: #000;
border-right: var(--side-size) solid transparent; }
border-bottom: calc(2 * var(--side-size) * 0.866) solid green; .marker {
border-top: var(--side-size) solid transparent; vertical-align: middle;
display: inline-block; }
}*/
.circle { .circle {
height: 20px; height: 20px;
width: 20px; width: 20px;

View file

@ -0,0 +1 @@
<h1>About this site... </h1>

View file

@ -0,0 +1 @@
<h1>Attribution for included media</h1>