initial commit with collapsible filters and filtering on categories
This commit is contained in:
parent
a15ad889d1
commit
d2aab294bd
6 changed files with 150 additions and 145 deletions
11
README.md
11
README.md
|
@ -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
|
||||
|
||||
|
|
43
src/lib/components/CollapsibleSection.svelte
Normal file
43
src/lib/components/CollapsibleSection.svelte
Normal 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>
|
|
@ -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;
|
||||
|
|
19
src/lib/components/filter.js
Normal file
19
src/lib/components/filter.js
Normal 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;
|
||||
}
|
|
@ -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']
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
<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>
|
||||
</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"
|
||||
|
|
Loading…
Reference in a new issue