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>
<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 />
<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;
// 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) {
@ -30,14 +61,14 @@
// pull out relevant info in useful chunks.
for (let key in webservices.technologies) {
let tech = webservices.technologies[key];
if (tech.hasOwnProperty('categories') && tech.categories.constructor === Array ) {
tech.categories.forEach(function(category, index) {
if (tech.hasOwnProperty('categories') && tech.categories.constructor === Array) {
tech.categories.forEach(function (category, index) {
if (category_list.hasOwnProperty(category)) category_list[category]++;
else category_list[category] = 1;
});
}
if (tech.hasOwnProperty('analogues') && tech.analogues.constructor === Array ) {
tech.analogues.forEach(function(analogue, index) {
if (tech.hasOwnProperty('analogues') && tech.analogues.constructor === Array) {
tech.analogues.forEach(function (analogue, index) {
if (analogue_list.hasOwnProperty(analogue)) analogue_list[analogue]++;
else analogue_list[analogue] = 1;
});
@ -52,7 +83,7 @@
tech['name'] = key;
current.push(tech);
//console.log(tech.name + ': ' + tech.instances.length + ' instances...');
tech.instances.forEach(function(instance, i) {
tech.instances.forEach(function (instance, i) {
instances++;
if (instance.hasOwnProperty('status')) {
let tag = instance.status;
@ -73,14 +104,16 @@
} else {
future[key] = tech;
}
};
}
for (let key in webservices.hosts) {
//console.log('key: ', key);
let host = webservices.hosts[key];
host['name'] = key;
//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;
}
}
@ -114,10 +147,15 @@
};
}
// console.log(technologies);
// console.log(technologies);
// return true if a tech object includes valid instances
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;
}
@ -125,8 +163,9 @@
function getKeys(ob) {
let keys = [];
for (let key in ob) {
//keys.push(key.replace(/ /g, '\u00a0'));
keys.push(key);
};
}
return keys;
}
@ -135,9 +174,7 @@
const set1 = new Set(a);
const set2 = new Set(b);
const intersection = [...set1].filter(
element => set2.has(element)
);
const intersection = [...set1].filter((element) => set2.has(element));
return intersection;
}
@ -167,14 +204,14 @@
//console.log('hosts:', hosts);
host_list.forEach(function(host) {
host_list.forEach(function (host) {
//console.log(host);
if (hosts[host].hasOwnProperty('domain') && hosts[host].hasOwnProperty('affiliation')) {
host_array[host] = {
"colour": colours[i++],
"domain": hosts[host].domain,
"affiliation": hosts[host].affiliation
}
colour: colours[i++],
domain: hosts[host].domain,
affiliation: hosts[host].affiliation
};
}
});
return host_array;
@ -191,12 +228,15 @@
for (const affiliate of affiliate_list) {
//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] = {
"colour": colours[i++],
"name": affiliates[affiliate].name,
"website": affiliates[affiliate].website
}
colour: colours[i++],
name: affiliates[affiliate].name,
website: affiliates[affiliate].website
};
}
}
return affiliate_result;
@ -204,7 +244,7 @@
// sort technologies alphabetically by name
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
@ -213,9 +253,7 @@
const set1 = new Set(a);
const set2 = new Set(b);
const intersection = [...set1].filter(
element => set2.has(element)
);
const intersection = [...set1].filter((element) => set2.has(element));
console.log('intersection = ', intersection);
return intersection;
}
@ -224,7 +262,7 @@
function filterTechnologiesByCategoryList(technologies, list) {
console.log('looking for tech in categories: ', list);
const included = [];
technologies.forEach(function(tech, index) {
technologies.forEach(function (tech, index) {
//console.log('tech(' + index + '): ', tech);
if (hasInstances(tech)) {
const intersection = inCommon(tech.categories, list);
@ -242,7 +280,7 @@
}
}
});
console.log('found ' + included.length + ' technologies.')
console.log('found ' + included.length + ' technologies.');
return included;
}
@ -363,10 +401,69 @@
'GPL-2+',
'GPL-3',
'GPL-3+',
'LGPL-3',
'LGPL-3'
];
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',
'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',
@ -382,15 +479,24 @@
'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);
</script>
<div class="webservices">
<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">
<ul>
<li>Total number of services: {results.total_instances}</li>
@ -398,35 +504,76 @@
</div>
<div class="filters">
<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 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 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 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>
<div class="tags instance statuses">
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 class="tiles">
{#each filtered_technologies as 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}
{#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>
{#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}
<p class="description">{technology.description}</p>
<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.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)}
<div class="instances"><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 class="instances">
<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>
{:else}
<div class="instances">
Nothing here yet...
</div>
<div class="instances">Nothing here yet...</div>
{/if}
</div>
{/each}
@ -436,11 +583,15 @@
<style>
.webservices {
display: grid;
width: 96%;
margin: 0 auto 3em auto;
padding: 0;
}
.webservices h1 {
padding: 0;
}
.summary {
background-color: #ccc;
display: block;
}
.filters {
/*background-color: #f1ff94;
@ -455,9 +606,14 @@
display: block;
}
.tag-list {
white-space: normal;
/* white-space: normal;
word-break: normal;
display: inline;
display: inline;*/
}
.tags {
/* white-space: normal;
word-break: normal;
display: inline;*/
}
.tag {
font-size: 80%;
@ -466,15 +622,16 @@
padding: 6px 8px;
border-radius: 14px;
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 {
background-color: #a369a2;
background-color: #9aa34d;
color: #fff;
}
.tag.license {
background-color: #a369a2;
background-color: #6498a3;
color: #fff;
}
.tag.status {
@ -496,7 +653,7 @@
}
/* flip card stuff: https://www.w3schools.com/howto/howto_css_flip_card.asp */
.tile {
height: 500px;
height: 515px;
min-width: 270px;
max-width: 400px;
overflow: hidden;
@ -515,18 +672,34 @@
padding: 2px 6px;
font-size: 90%;
}
.tile .links a { color: #e6c4fc; }
.tile .links a:visited { color: #ced0ff; }
.tile .links a:hover { color: #fff; }
.tile:hover {
box-shadow: 10px 10px 6px #71ba71;
}
.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 {
background-color: #999;
text-align: center;
padding: 0.5em;
margin: 0;
}
.tile h2 a { color: #e6c4fc; }
.tile h2 a:visited { color: #ced0ff; }
.tile h2 a:hover { color: #fff; }
.tile h2 a {
color: #e6c4fc;
}
.tile h2 a:visited {
color: #ced0ff;
}
.tile h2 a:hover {
color: #fff;
}
.tile .description {
height: 260px;
text-overflow: ellipsis;
@ -547,17 +720,16 @@
.instances .marker {
margin-right: 3px;
}
.webservices li { list-style-type: none; }
.value { font-weight: bold; color: #000; }
.marker { vertical-align: middle; }
/* .triangle {
--side-size: 20px;
border-left: var(--side-size) solid transparent;
border-right: var(--side-size) solid transparent;
border-bottom: calc(2 * var(--side-size) * 0.866) solid green;
border-top: var(--side-size) solid transparent;
display: inline-block;
}*/
.webservices li {
list-style-type: none;
}
.value {
font-weight: bold;
color: #000;
}
.marker {
vertical-align: middle;
}
.circle {
height: 20px;
width: 20px;

View file

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

View file

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