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
|
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
|
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
|
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('joinActive');
|
||||||
console.log('filterValues: ', filterValues);
|
console.log('filterValues: ', filterValues);
|
||||||
if (isActive.length) return formatter.format(isActive);
|
if (isActive.length) return formatter.format(isActive);
|
||||||
return 'None';
|
return 'No';
|
||||||
}
|
}
|
||||||
|
|
||||||
function getActive($f) {
|
function getActive($f) {
|
||||||
|
@ -88,11 +88,11 @@
|
||||||
<span class="filter {context}" class:active={filter.active}
|
<span class="filter {context}" class:active={filter.active}
|
||||||
on:click="{() => { filter.active = !filter.active; updateFilterValues(filter);}}"
|
on:click="{() => { filter.active = !filter.active; updateFilterValues(filter);}}"
|
||||||
on:keypress="{() => {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}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p>{activeFiltersString} {filterText} active.</p>
|
<!--<p>{activeFiltersString} {filterText} active.</p>-->
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
h2 {
|
h2 {
|
||||||
|
@ -122,6 +122,7 @@
|
||||||
grid-template-columns: repeat(auto-fit, minmax(80px, 1fr));*/
|
grid-template-columns: repeat(auto-fit, minmax(80px, 1fr));*/
|
||||||
}
|
}
|
||||||
.filter {
|
.filter {
|
||||||
|
font-size: 80%;
|
||||||
width: 80px;
|
width: 80px;
|
||||||
height: auto;
|
height: auto;
|
||||||
border: 1px #aaa solid;
|
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',
|
'Webserver',
|
||||||
'Website Analytics'
|
'Website Analytics'
|
||||||
],
|
],
|
||||||
copyleft_list: ['AGPL-3', 'AGPL-3+', 'GPL', 'GPL-2', 'GPL-2+', 'GPL-3', 'GPL-3+', 'LGPL-3'],
|
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'
|
|
||||||
]
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,8 +6,11 @@
|
||||||
import { references } from '$lib/references.js';
|
import { references } from '$lib/references.js';
|
||||||
import { colours } from '$lib/colours.js';
|
import { colours } from '$lib/colours.js';
|
||||||
|
|
||||||
|
// the filters
|
||||||
import { setFilter, getFilter } from '$lib/components/filter.js';
|
import { setFilter, getFilter } from '$lib/components/filter.js';
|
||||||
import Filterable from '$lib/components/Filterable.svelte';
|
import Filterable from '$lib/components/Filterable.svelte';
|
||||||
|
// collapsible sections
|
||||||
|
import CollapsibleSection from '$lib/components/CollapsibleSection.svelte'
|
||||||
|
|
||||||
//console.log('colours: ', colours);
|
//console.log('colours: ', colours);
|
||||||
|
|
||||||
|
@ -93,15 +96,9 @@
|
||||||
hosts[key] = host;
|
hosts[key] = host;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//console.log('webservices.affiliates: ', webservices.affiliates);
|
|
||||||
for (let key in webservices.affiliates) {
|
for (let key in webservices.affiliates) {
|
||||||
//console.log('key: ', key);
|
|
||||||
let affiliate = webservices.affiliates[key];
|
let affiliate = webservices.affiliates[key];
|
||||||
//console.log('affiliate assignment: ', affiliate);
|
|
||||||
affiliates[key] = affiliate;
|
affiliates[key] = affiliate;
|
||||||
// if (affiliate.hasOwnProperty('name')) {
|
|
||||||
// affiliate_data[key] = affiliate;
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -242,29 +239,38 @@
|
||||||
const set2 = new Set(b);
|
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);
|
console.log('intersection = ', intersection);
|
||||||
return 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
|
// filter technologies based on a list of Categories
|
||||||
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) {
|
||||||
//console.log('tech(' + index + '): ', tech);
|
|
||||||
if (hasInstances(tech)) {
|
if (hasInstances(tech)) {
|
||||||
const intersection = inCommon(tech.categories, list);
|
const intersection = inCommon(tech.categories, list);
|
||||||
//console.log('categories: ', tech.categories);
|
//console.log('categories: ', tech.categories);
|
||||||
//console.log('list: ', list);
|
//console.log('list: ', list);
|
||||||
//console.log('intersection: ', intersection);
|
console.log('intersection: ', intersection);
|
||||||
if (intersection && intersection.constructor === Array) {
|
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) {
|
if (intersection.length > 0) {
|
||||||
//console.log('including tech: ', tech);
|
//console.log('including tech: ', tech);
|
||||||
included.push(tech);
|
included.push(tech);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
//console.log('intersection not array');
|
console.log('intersection not array');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -273,7 +279,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const results = processData(webservices);
|
const results = processData(webservices);
|
||||||
//console.log('results: ', results);
|
|
||||||
|
|
||||||
//const technologies = webservices.technologies;
|
//const technologies = webservices.technologies;
|
||||||
//console.log(technologies);
|
//console.log(technologies);
|
||||||
|
@ -312,29 +317,20 @@
|
||||||
const affiliate_colours = affiliateColours($affiliateFilter, colours, affiliates);
|
const affiliate_colours = affiliateColours($affiliateFilter, colours, affiliates);
|
||||||
console.log('affiliate_colours:',affiliate_colours);
|
console.log('affiliate_colours:',affiliate_colours);
|
||||||
|
|
||||||
//console.log('categories array: ', results.tech_tags.categories);
|
let filteredTechnologies;
|
||||||
//console.log('categories keys: ', categories);
|
let technologies;
|
||||||
|
|
||||||
//console.log('references: ', references);
|
// reactive stuff...
|
||||||
/*console.log('category_list: ', category_list);
|
$: {
|
||||||
//console.log('analogue_list: ', analogue_list);
|
console.log('about to filterTechnologiesByCategoryList');
|
||||||
console.log('license_list: ', license_list);
|
filteredTechnologies = filterTechnologiesByCategoryList(results.active_services, flattenFilter($categoryFilter));
|
||||||
console.log('status_list: ', status_list);
|
const technologies = sortTechnologies(filteredTechnologies);
|
||||||
console.log('affiliate_list: ', affiliate_list);
|
}
|
||||||
console.log('hosts_list: ', host_list);*/
|
|
||||||
|
|
||||||
|
/*function flipOn(list, id) {
|
||||||
const filtered_technologies = filterTechnologiesByCategoryList(
|
|
||||||
results.active_services,
|
|
||||||
references.full_category_list,
|
|
||||||
// references.category_filter_list
|
|
||||||
);
|
|
||||||
const technologies = sortTechnologies(filtered_technologies);
|
|
||||||
|
|
||||||
function flipOn(list, id) {
|
|
||||||
console.log('list: ' + list + ', id: ' + id);
|
console.log('list: ' + list + ', id: ' + id);
|
||||||
return true;
|
return true;
|
||||||
}
|
}*/
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -354,40 +350,54 @@
|
||||||
<li>Total number of services: {results.total_instances}</li>
|
<li>Total number of services: {results.total_instances}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
<CollapsibleSection headerText={'Show or Hide Filters'}>
|
||||||
<div class="filters">
|
<div class="filters">
|
||||||
|
<CollapsibleSection headerText={'Show or Hide Software Catergory Filter'}>
|
||||||
<div class="filter categories">
|
<div class="filter categories">
|
||||||
<Filterable filterValues={categoryFilter} context={'categories'}>
|
<Filterable filterValues={categoryFilter} context={'categories'}>
|
||||||
<h2 slot="h2">Categories</h2>
|
<h2 slot="h2">Categories</h2>
|
||||||
</Filterable>
|
</Filterable>
|
||||||
</div>
|
</div>
|
||||||
|
</CollapsibleSection>
|
||||||
|
<CollapsibleSection headerText={'Show or Hide Software Analogue Filter'}>
|
||||||
<div class="filter analogues">
|
<div class="filter analogues">
|
||||||
<Filterable filterValues={analogueFilter} context={'analogues'}>
|
<Filterable filterValues={analogueFilter} context={'analogues'}>
|
||||||
<h2 slot="h2">Analogues</h2>
|
<h2 slot="h2">Analogues</h2>
|
||||||
</Filterable>
|
</Filterable>
|
||||||
</div>
|
</div>
|
||||||
|
</CollapsibleSection>
|
||||||
|
<CollapsibleSection headerText={'Show or Hide License Filter'}>
|
||||||
<div class="filter licenses">
|
<div class="filter licenses">
|
||||||
<Filterable filterValues={licenseFilter} context={'licenses'}>
|
<Filterable filterValues={licenseFilter} context={'licenses'}>
|
||||||
<h2 slot="h2">Open Source & Copyleft Licenses</h2>
|
<h2 slot="h2">Open Source & Copyleft Licenses</h2>
|
||||||
</Filterable>
|
</Filterable>
|
||||||
</div>
|
</div>
|
||||||
|
</CollapsibleSection>
|
||||||
|
<CollapsibleSection headerText={'Show or Hide Service Status Filter'}>
|
||||||
<div class="filter statuses">
|
<div class="filter statuses">
|
||||||
<Filterable filterValues={statusFilter} context={'statuses'}>
|
<Filterable filterValues={statusFilter} context={'statuses'}>
|
||||||
<h2 slot="h2">Statuses</h2>
|
<h2 slot="h2">Statuses</h2>
|
||||||
</Filterable>
|
</Filterable>
|
||||||
</div>
|
</div>
|
||||||
|
</CollapsibleSection>
|
||||||
|
<CollapsibleSection headerText={'Show or Hide Service Affiliates Filter'}>
|
||||||
<div class="filter affilates">
|
<div class="filter affilates">
|
||||||
<Filterable filterValues={affiliateFilter} context={'affiliates'}>
|
<Filterable filterValues={affiliateFilter} context={'affiliates'}>
|
||||||
<h2 slot="h2">Affiliates</h2>
|
<h2 slot="h2">Affiliates</h2>
|
||||||
</Filterable>
|
</Filterable>
|
||||||
</div>
|
</div>
|
||||||
|
</CollapsibleSection>
|
||||||
|
<CollapsibleSection headerText={'Show or Hide Service Host filter'}>
|
||||||
<div class="filter hosts">
|
<div class="filter hosts">
|
||||||
<Filterable filterValues={hostFilter} context={'hosts'}>
|
<Filterable filterValues={hostFilter} context={'hosts'}>
|
||||||
<h2 slot="h2">Hosts</h2>
|
<h2 slot="h2">Hosts</h2>
|
||||||
</Filterable>
|
</Filterable>
|
||||||
</div>
|
</div>
|
||||||
|
</CollapsibleSection>
|
||||||
</div>
|
</div>
|
||||||
|
</CollapsibleSection>
|
||||||
<div class="tiles">
|
<div class="tiles">
|
||||||
{#each filtered_technologies as technology}
|
{#each filteredTechnologies as technology}
|
||||||
<div class="tile technology">
|
<div class="tile technology">
|
||||||
<div class="links">
|
<div class="links">
|
||||||
{#if technology.repository}<span class="repository"
|
{#if technology.repository}<span class="repository"
|
||||||
|
|
Loading…
Reference in a new issue