initial commit with collapsible filters and filtering on categories

This commit is contained in:
Dave Lane 2024-09-19 16:23:30 +12:00
parent a15ad889d1
commit d2aab294bd
6 changed files with 150 additions and 145 deletions

View file

@ -59,11 +59,18 @@ and then run
docker-compose build
### You can push your new container to a docker container repository (I use my Forgejo instance!)
You can push your new container to a docker container repository (I use my Forgejo instance!)
docker push forge.magnificent.nz/lightweight/webservices-app:latest
### To start it up (and tail the logs - CTRL-C to quit out of the logs without affecting running container)
### To deploy the new container
At the location you're running production, pull the updated container
docker-compose pull
To start it up (and tail the logs - CTRL-C to quit out of the logs without affecting running container)
docker-compose up -d && docker-compose logs -f

View file

@ -0,0 +1,43 @@
<script>
// Ref: https://svelte.dev/repl/a5f4d395b15a44d48a6b2239ef705fc4?version=3.35.0
// based on suggestions from:
// Inclusive Components by Heydon Pickering https://inclusive-components.design/collapsible-sections/
export let headerText;
let expanded = false
</script>
<div class="collapsible">
<h3>
<button aria-expanded={expanded} on:click={() => expanded = !expanded}>{headerText}
<svg viewBox="0 0 20 20" fill="none" >
<path class="vert" d="M10 1V19" stroke="black" stroke-width="2"/>
<path d="M1 10L19 10" stroke="black" stroke-width="2"/>
</svg>
</button>
</h3>
<div class='contents' hidden={!expanded}>
<slot></slot>
</div>
</div>
<style>
.collapsible { border-bottom: 1px solid var(--gray-light, #eee); }
h3 { margin: 0; }
button {
background-color: var(--background, #fff);
color: var(--gray-darkest, #282828);
display: flex;
justify-content: space-between;
width: 100%;
border: none;
margin: 0;
padding: 1em 0.5em;
}
button[aria-expanded="true"] { border-bottom: 1px solid var(--gray-light, #eee); }
button[aria-expanded="true"] .vert { display: none; }
button:focus svg { outline: 2px solid; }
button [aria-expanded="true"] rect { fill: currentColor; }
svg { height: 0.7em; width: 0.7em; }
</style>

View file

@ -23,7 +23,7 @@
console.log('joinActive');
console.log('filterValues: ', filterValues);
if (isActive.length) return formatter.format(isActive);
return 'None';
return 'No';
}
function getActive($f) {
@ -88,11 +88,11 @@
<span class="filter {context}" class:active={filter.active}
on:click="{() => { filter.active = !filter.active; updateFilterValues(filter);}}"
on:keypress="{() => {filter.active = !filter.active; updateFilterValues(filter);}}"
name="{filter.id}" role="button" tabindex={tabindex + filter.id}>{filter.name} ({filter.id})</span><wbr/>
name="{filter.id}" role="button" tabindex={tabindex + filter.id}>{filter.name}</span><wbr/>
{/each}
</div>
<p>{activeFiltersString} {filterText} active.</p>
<!--<p>{activeFiltersString} {filterText} active.</p>-->
<style>
h2 {
@ -122,6 +122,7 @@
grid-template-columns: repeat(auto-fit, minmax(80px, 1fr));*/
}
.filter {
font-size: 80%;
width: 80px;
height: auto;
border: 1px #aaa solid;

View file

@ -0,0 +1,19 @@
import { writable } from 'svelte/store';
import { getContext, setContext } from 'svelte';
export function setFilter(context) {
let contextFilter = writable(0);
console.log('setFilter: ', contextFilter);
contextFilter.subscribe((value) => {
console.log('initial value: ', value);
});
setContext(context, contextFilter);
console.log('contextFilter (' + context + '): ', contextFilter);
}
export function getFilter(context) {
console.log('looking for writable for context ', context);
let f = getContext(context);
console.log('returning filter writable: ', f);
return f;
}

View file

@ -75,80 +75,5 @@ export const references = {
'Webserver',
'Website Analytics'
],
copyleft_list: ['AGPL-3', 'AGPL-3+', 'GPL', 'GPL-2', 'GPL-2+', 'GPL-3', 'GPL-3+', 'LGPL-3'],
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',
'Streaming',
'Surveys',
'TURN/STUN Server',
'Video',
'Video Conferencing',
'Video Surveillance',
'Visualisation of Time Series Data',
'Webmail',
'Webserver',
'Website Analytics'
]
copyleft_list: ['AGPL-3', 'AGPL-3+', 'GPL', 'GPL-2', 'GPL-2+', 'GPL-3', 'GPL-3+', 'LGPL-3']
};

View file

@ -6,8 +6,11 @@
import { references } from '$lib/references.js';
import { colours } from '$lib/colours.js';
// the filters
import { setFilter, getFilter } from '$lib/components/filter.js';
import Filterable from '$lib/components/Filterable.svelte';
// collapsible sections
import CollapsibleSection from '$lib/components/CollapsibleSection.svelte'
//console.log('colours: ', colours);
@ -93,15 +96,9 @@
hosts[key] = host;
}
}
//console.log('webservices.affiliates: ', webservices.affiliates);
for (let key in webservices.affiliates) {
//console.log('key: ', key);
let affiliate = webservices.affiliates[key];
//console.log('affiliate assignment: ', affiliate);
affiliates[key] = affiliate;
// if (affiliate.hasOwnProperty('name')) {
// affiliate_data[key] = affiliate;
// }
}
return {
@ -242,29 +239,38 @@
const set2 = new Set(b);
const intersection = [...set1].filter((element) => set2.has(element));
//console.log('intersection = ', intersection);
console.log('intersection = ', intersection);
return intersection;
}
// converts a filter with id, name, and active fields into an array of names
function flattenFilter(filter) {
let flat = [];
if (filter.constructor === Array) {
filter.forEach(function(e) { if (e.active) flat.push(e.name) });
}
return flat;
}
// 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);
technologies.forEach(function (tech) {
if (hasInstances(tech)) {
const intersection = inCommon(tech.categories, list);
//console.log('categories: ', tech.categories);
//console.log('list: ', list);
//console.log('intersection: ', intersection);
console.log('intersection: ', intersection);
if (intersection && intersection.constructor === Array) {
//console.log('intersection length: '. intersection.length);
//console.log('found intersection!', intersection.constructor);
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('intersection not array');
}
}
});
@ -273,7 +279,6 @@
}
const results = processData(webservices);
//console.log('results: ', results);
//const technologies = webservices.technologies;
//console.log(technologies);
@ -312,29 +317,20 @@
const affiliate_colours = affiliateColours($affiliateFilter, colours, affiliates);
console.log('affiliate_colours:',affiliate_colours);
//console.log('categories array: ', results.tech_tags.categories);
//console.log('categories keys: ', categories);
let filteredTechnologies;
let technologies;
//console.log('references: ', references);
/*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);*/
// reactive stuff...
$: {
console.log('about to filterTechnologiesByCategoryList');
filteredTechnologies = filterTechnologiesByCategoryList(results.active_services, flattenFilter($categoryFilter));
const technologies = sortTechnologies(filteredTechnologies);
}
const filtered_technologies = filterTechnologiesByCategoryList(
results.active_services,
references.full_category_list,
// references.category_filter_list
);
const technologies = sortTechnologies(filtered_technologies);
function flipOn(list, id) {
/*function flipOn(list, id) {
console.log('list: ' + list + ', id: ' + id);
return true;
}
}*/
</script>
@ -354,40 +350,54 @@
<li>Total number of services: {results.total_instances}</li>
</ul>
</div>
<div class="filters">
<div class="filter categories">
<Filterable filterValues={categoryFilter} context={'categories'}>
<h2 slot="h2">Categories</h2>
</Filterable>
<CollapsibleSection headerText={'Show or Hide Filters'}>
<div class="filters">
<CollapsibleSection headerText={'Show or Hide Software Catergory Filter'}>
<div class="filter categories">
<Filterable filterValues={categoryFilter} context={'categories'}>
<h2 slot="h2">Categories</h2>
</Filterable>
</div>
</CollapsibleSection>
<CollapsibleSection headerText={'Show or Hide Software Analogue Filter'}>
<div class="filter analogues">
<Filterable filterValues={analogueFilter} context={'analogues'}>
<h2 slot="h2">Analogues</h2>
</Filterable>
</div>
</CollapsibleSection>
<CollapsibleSection headerText={'Show or Hide License Filter'}>
<div class="filter licenses">
<Filterable filterValues={licenseFilter} context={'licenses'}>
<h2 slot="h2">Open Source & Copyleft Licenses</h2>
</Filterable>
</div>
</CollapsibleSection>
<CollapsibleSection headerText={'Show or Hide Service Status Filter'}>
<div class="filter statuses">
<Filterable filterValues={statusFilter} context={'statuses'}>
<h2 slot="h2">Statuses</h2>
</Filterable>
</div>
</CollapsibleSection>
<CollapsibleSection headerText={'Show or Hide Service Affiliates Filter'}>
<div class="filter affilates">
<Filterable filterValues={affiliateFilter} context={'affiliates'}>
<h2 slot="h2">Affiliates</h2>
</Filterable>
</div>
</CollapsibleSection>
<CollapsibleSection headerText={'Show or Hide Service Host filter'}>
<div class="filter hosts">
<Filterable filterValues={hostFilter} context={'hosts'}>
<h2 slot="h2">Hosts</h2>
</Filterable>
</div>
</CollapsibleSection>
</div>
<div class="filter analogues">
<Filterable filterValues={analogueFilter} context={'analogues'}>
<h2 slot="h2">Analogues</h2>
</Filterable>
</div>
<div class="filter licenses">
<Filterable filterValues={licenseFilter} context={'licenses'}>
<h2 slot="h2">Open Source & Copyleft Licenses</h2>
</Filterable>
</div>
<div class="filter statuses">
<Filterable filterValues={statusFilter} context={'statuses'}>
<h2 slot="h2">Statuses</h2>
</Filterable>
</div>
<div class="filter affilates">
<Filterable filterValues={affiliateFilter} context={'affiliates'}>
<h2 slot="h2">Affiliates</h2>
</Filterable>
</div>
<div class="filter hosts">
<Filterable filterValues={hostFilter} context={'hosts'}>
<h2 slot="h2">Hosts</h2>
</Filterable>
</div>
</div>
</CollapsibleSection>
<div class="tiles">
{#each filtered_technologies as technology}
{#each filteredTechnologies as technology}
<div class="tile technology">
<div class="links">
{#if technology.repository}<span class="repository"