Compare commits
23 commits
35151cea11
...
9d53e484b6
Author | SHA1 | Date | |
---|---|---|---|
|
9d53e484b6 | ||
|
79e01ffcbb | ||
|
62a935921a | ||
|
dfae48f943 | ||
|
9316c299f4 | ||
|
d9361fe85c | ||
|
ccd493148f | ||
|
8ace7f9d5a | ||
|
10c4734144 | ||
|
d043d90911 | ||
|
57e3add9bc | ||
|
1c7bff1433 | ||
|
b5429762bf | ||
|
2ccaf070b6 | ||
|
de841dfd78 | ||
|
f02372d382 | ||
|
2cea5f44fb | ||
|
37cd4fd17a | ||
|
b5cdfa0a09 | ||
|
d4b7cf52cc | ||
|
0052c00f03 | ||
|
26869a837b | ||
|
3068f5980d |
15 changed files with 767 additions and 614 deletions
|
@ -1,9 +1,14 @@
|
|||
# .dockerignore
|
||||
|
||||
node_modules
|
||||
README.md
|
||||
Dockerfile
|
||||
|
||||
.git
|
||||
.gitattributes
|
||||
.gitignore
|
||||
|
||||
.npmrc
|
||||
|
||||
.eslintignore
|
||||
.eslintrc.cjs
|
||||
|
@ -11,6 +16,8 @@ node_modules
|
|||
.prettierrc
|
||||
.pretieriignore
|
||||
|
||||
README.md
|
||||
.svelte-kit
|
||||
|
||||
Dockerfile
|
||||
build
|
||||
package
|
||||
**/.env
|
||||
|
|
23
Dockerfile
23
Dockerfile
|
@ -1,5 +1,7 @@
|
|||
# ref: https://khromov.se/dockerizing-your-sveltekit-applications-a-practical-guide/
|
||||
#
|
||||
# Use this image as the platform to build the app
|
||||
FROM node:20.11-alpine AS external-website
|
||||
FROM node:22-alpine AS builder
|
||||
|
||||
# A small line inside the image to show who made it
|
||||
LABEL Developers="Dave Lane, dave@laneventures.nz"
|
||||
|
@ -8,19 +10,28 @@ LABEL Developers="Dave Lane, dave@laneventures.nz"
|
|||
WORKDIR /app
|
||||
|
||||
# Copy all local files into the image
|
||||
COPY . .
|
||||
COPY package*.json .
|
||||
|
||||
# Clean install all node modules
|
||||
RUN npm ci
|
||||
|
||||
COPY . .
|
||||
|
||||
# Build SvelteKit app
|
||||
RUN npm run build
|
||||
|
||||
# Delete source code files that were used to build the app that are no longer needed
|
||||
RUN rm -rf src/ static/ emailTemplates/ docker-compose.yml
|
||||
RUN npm prune --production
|
||||
|
||||
FROM node:22-alpine
|
||||
WORKDIR /app
|
||||
COPY --from=builder /app/build build/
|
||||
COPY --from=builder /app/node_modules node_modules/
|
||||
COPY package.json .
|
||||
EXPOSE 5050
|
||||
ENV NODE_ENV=production
|
||||
# The USER instruction sets the user name to use as the default user for the remainder of the current stage
|
||||
USER node:node
|
||||
|
||||
# This is the command that will be run inside the image when you tell Docker to start the container
|
||||
CMD ["node","build/index.js"]
|
||||
CMD [ "node", "build" ]
|
||||
|
||||
|
||||
|
|
22
Plans.md
22
Plans.md
|
@ -1,8 +1,24 @@
|
|||
# Plans for Webservices App
|
||||
|
||||
High priority:
|
||||
I've got quite a few plans for this app... here's a rough sketch of what I'm planning.
|
||||
|
||||
First release:
|
||||
|
||||
* filtering of all properties like Categories, Analogues, Licenses, Statuses, Hosts, and Affiliations.
|
||||
* modal showing each technology in more detail. DONE
|
||||
* split out all constants (arrays) into separate files. ~DONE
|
||||
* split out functions into svelte components.
|
||||
|
||||
Future versions:
|
||||
|
||||
* upgrade to Svelte 5
|
||||
* a 'Technology' view (overlay), addressable by URL (i.e. going to, say, http://localhost:5173/ServiceName brings up the site with ServiceName shown in 'Technology' view, allowing people to reference a specific technology of interest to others).
|
||||
* split out all constants (arrays) into separate files.
|
||||
* split out functions into svelte modules.
|
||||
* ability to go 'prev' or 'next' in modals within filtered set of technologies.
|
||||
* ability to select a Hosts and view all Technologies with instances on that Host
|
||||
* ability to see the status (via UptimeKuma API) of each instance.
|
||||
|
||||
Possibly, eventually:
|
||||
|
||||
* move data into a database (rather than a fragile hand-crafted JSON file) with integrity checking.
|
||||
* Authentication for administration
|
||||
* CRUD interface for Technologies, Licenses, Hosts, and Affiliations.
|
||||
|
|
16
README.md
16
README.md
|
@ -49,20 +49,6 @@ and then run
|
|||
|
||||
npm install
|
||||
|
||||
(which runs these)
|
||||
|
||||
npm i -D @sveltejs/adapter-node
|
||||
npm i dotenv
|
||||
npm i --save-dev @sveltejs/enhanced-img
|
||||
|
||||
### pnpm installs
|
||||
|
||||
pnpm add -g pnpm
|
||||
source /home/${USER}/.bashrc
|
||||
pnpm setup
|
||||
pnpm add -g pnpm
|
||||
pnpm i -D flowbite-svelte flowbite
|
||||
|
||||
|
||||
### To build a new image
|
||||
|
||||
|
@ -81,7 +67,7 @@ At the location you're running production, pull the updated container
|
|||
|
||||
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 rm -sf && docker-compose up -d && docker-compose logs -f
|
||||
|
||||
### To clear out an old image (if it's running)
|
||||
|
||||
|
|
807
package-lock.json
generated
807
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "webservices",
|
||||
"version": "0.0.1",
|
||||
"version": "0.0.9",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
|
|
60
src/app.css
60
src/app.css
|
@ -10,7 +10,7 @@ body {
|
|||
min-height: 100%;
|
||||
padding: 1em;
|
||||
width: 100%;
|
||||
font-size: 1.2em;
|
||||
font-size: 1.15em;
|
||||
border: double 3px #ddd;
|
||||
border-top: none;
|
||||
border-bottom: none;
|
||||
|
@ -28,7 +28,7 @@ a:hover {
|
|||
text-decoration: underline;
|
||||
}
|
||||
a:visited {
|
||||
color: #437fad;
|
||||
color: #325f81;
|
||||
}
|
||||
.y-accordions,
|
||||
.y-accordion-header-button {
|
||||
|
@ -37,3 +37,59 @@ a:visited {
|
|||
.yaccordion .y-accordians .y-accordion-header-button {
|
||||
background-color: #eee;
|
||||
}
|
||||
/* modal styles */
|
||||
.y-modal .y-modal-container {
|
||||
background-color: #eee;
|
||||
border: solid 3px #3a8ea5;
|
||||
border-radius: 15px;
|
||||
box-shadow: 5px 5px 3px #6a6d6a;
|
||||
width: 90%;
|
||||
margin: 1em auto;
|
||||
max-width: 40em;
|
||||
height: auto;
|
||||
padding: 0;
|
||||
}
|
||||
.y-modal .y-modal-container .y-modal-content {
|
||||
width: 100%;
|
||||
border-width: 0;
|
||||
}
|
||||
.y-modal .y-modal-container .y-modal-header {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
background-color: #999;
|
||||
}
|
||||
.y-modal .y-modal-container .y-modal-header .y-modal-title {
|
||||
font-size: 1.5em;
|
||||
margin: auto;
|
||||
}
|
||||
.y-modal .y-modal-container .y-modal-body {
|
||||
background-color: #eee;
|
||||
}
|
||||
.y-modal .y-modal-container div.instances {
|
||||
background-color: #ddd;
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.y-modal .y-modal-container p.instances,
|
||||
.y-modal .y-modal-container li p {
|
||||
padding: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* end modals */
|
||||
/* popover styles */
|
||||
.y-popover {
|
||||
z-index: 10000;
|
||||
}
|
||||
.y-popover a {
|
||||
font-size: 110%;
|
||||
}
|
||||
.y-popover a:hover {
|
||||
color: #000;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.y-popover a:visited {
|
||||
color: #325f81;
|
||||
}
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
<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 class="indicator" 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='tree' hidden={!expanded}>
|
||||
<svg viewBox="0 0 60 60" fill="#ccc">
|
||||
<path class="open" d="M40 1 V69" stroke="#444" stroke-width="4" />
|
||||
</svg>
|
||||
</div> -->
|
||||
<div class='contents' hidden={!expanded}>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.collapsible {
|
||||
margin: 8px 0 4px 0;
|
||||
}
|
||||
.collapsible .collapsible h3 button {
|
||||
padding-left: 20px;
|
||||
}
|
||||
.tree { float: left; }
|
||||
.contents {
|
||||
float: left;
|
||||
clear: both;
|
||||
width: 100%;
|
||||
border-bottom: dashed #aaa 1px;
|
||||
padding-bottom: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
h3 {
|
||||
margin: 0;
|
||||
}
|
||||
button {
|
||||
background-color: var(--background, #fff);
|
||||
color: var(--gray-darkest, #282828);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
border: 1px #aaa solid;
|
||||
margin-left: 0px;
|
||||
padding: 0.7em 0.7em;
|
||||
}
|
||||
button[aria-expanded="true"] { border-bottom: 2px solid var(--gray-light, #eee); }
|
||||
button[aria-expanded="true"] .vert { display: none; }
|
||||
button:focus svg { outline: 1firtpx solid; }
|
||||
button[aria-expanded="true"] rect { fill: currentColor; }
|
||||
svg.indicator {
|
||||
height: 1.0em;
|
||||
width: 1.0em;
|
||||
margin-left: 6px;
|
||||
margin-right: 6px;
|
||||
margin-top: 4px;;
|
||||
}
|
||||
.tree:hover svg { outline: 1firtpx solid; }
|
||||
|
||||
</style>
|
|
@ -152,7 +152,7 @@
|
|||
|
||||
/* instance-specific styles */
|
||||
.filter {
|
||||
font-size: 80%;
|
||||
font-size: 0.8em;
|
||||
margin-right: 0.5em;
|
||||
line-height: 2.5;
|
||||
padding: 6px 8px;
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
<script>
|
||||
// a 'technology' object
|
||||
export let technology;
|
||||
export let licenses;
|
||||
export let affiliate_colours;
|
||||
import { Button, Modal, ModalBody, ModalFooter, Popover } from 'yesvelte';
|
||||
import { Button, ButtonGroup, El, Modal, ModalBody, ModalFooter,
|
||||
ModalHeader, ModalTitle, Popover, PopoverBody, PopoverHeader } from 'yesvelte';
|
||||
|
||||
let showModal = false;
|
||||
export let showModal = false;
|
||||
|
||||
const formatter = new Intl.ListFormat('en', {
|
||||
style: 'long',
|
||||
|
@ -31,58 +33,42 @@
|
|||
tech.hasOwnProperty('instances') &&
|
||||
tech.instances.constructor === Array &&
|
||||
tech.instances.length
|
||||
) return true;
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function handleClick() {
|
||||
console.log('clicked!');
|
||||
}
|
||||
|
||||
/*document.addEventListener('keydown', (event) => {
|
||||
const { activeElement } = document;
|
||||
const hasButtonRole = activeElement?.getAttribute('role') === 'button';
|
||||
|
||||
if (hasButtonRole) {
|
||||
// prevent default behaviour, including scrolling on spacebar
|
||||
if (['Spacebar', ' ', 'Enter'].includes(event.key)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
if (event.key === 'Enter') {
|
||||
activeElement.click();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keyup', (event) => {
|
||||
const { activeElement } = document;
|
||||
const hasButtonRole = activeElement?.getAttribute('role') === 'button';
|
||||
|
||||
if (hasButtonRole && ['Spacebar', ' '].includes(event.key)) {
|
||||
event.preventDefault();
|
||||
activeElement.click();
|
||||
}
|
||||
});*/
|
||||
|
||||
//onclick="handleClick()"
|
||||
</script>
|
||||
|
||||
<!-- making a dive act like a button: https://kvack.dev/blog/make-that-div-behave-like-a-button -->
|
||||
|
||||
<div on:click={() => (showModal = !showModal)} onclick="handleClick()" role="button" tabindex=0 class="tile technology">
|
||||
<div class="links">
|
||||
<span class="website"><a href="{technology.website}" title="Project site for {technology.name}">P</a></span>
|
||||
{#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>
|
||||
<button on:click={() => (showModal = !showModal)} tabindex=0 class="tile technology" col>
|
||||
<h2>{technology.name}</h2>
|
||||
<div class="links">
|
||||
<span class="website">P</span>
|
||||
<Popover trigger="hover">
|
||||
<PopoverHeader><strong>Project website</strong> for {technology.name}</PopoverHeader>
|
||||
<PopoverBody><a href="{technology.website}">{technology.website}</a></PopoverBody>
|
||||
</Popover>
|
||||
{#if technology.repository}<span class="repository">S</span>
|
||||
<Popover trigger="hover">
|
||||
<PopoverHeader><strong>Source code</strong> repository for {technology.name}</PopoverHeader>
|
||||
<PopoverBody><a href={technology.repository}>{technology.repository}</a></PopoverBody>
|
||||
</Popover>
|
||||
{/if}
|
||||
{#if technology.wikipedia}<span class="wikipedia">W</span>
|
||||
<Popover trigger="hover">
|
||||
<PopoverHeader><strong>Wikipedia entry</strong> for {technology.name}</PopoverHeader>
|
||||
<PopoverBody><a href={technology.wikipedia}>{technology.wikipedia}</a></PopoverBody>
|
||||
</Popover>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<p class="description">{technology.description}{#if technology.extended_description}<span
|
||||
title={technology.extended_description}>i</span>{/if}
|
||||
</p>
|
||||
<div class="details">
|
||||
{#if technology.categories}<p class="property categories">{pluraliser('Category',
|
||||
'Categories', technology.categories.length)}: <span class="value">{toOxfordCommaString(technology.categories)}</span></p>{/if}
|
||||
{#if technology.analogues}<p class="property analogues">Alternative to <span
|
||||
|
@ -90,114 +76,171 @@
|
|||
{#if technology.license}<p class="property license"
|
||||
title="The libre license for this project is {technology.license}">License: <span
|
||||
class="value">{technology.license}</span></p>{/if}
|
||||
</div>
|
||||
{#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"
|
||||
<p>
|
||||
{#each technology.instances as instance}
|
||||
<span class="marker circle"
|
||||
style="background-color: {affiliate_colours[instance.affiliation].colour}"
|
||||
></span></a>{/each}
|
||||
></span>
|
||||
<Popover trigger="hover">
|
||||
<PopoverHeader>{technology.name} Instance</PopoverHeader>
|
||||
<PopoverBody><a href="https://{instance.domain}">https://{instance.domain}</a> hosted on '{instance.host}' by {instance.affiliation}</PopoverBody>
|
||||
</Popover>
|
||||
{/each}
|
||||
</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="instances">Nothing here yet...</div>
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<Modal title="{technology.name}" bind:showModal>
|
||||
<Modal scrollable bind:show={showModal} autoClose dismissible>
|
||||
<ModalHeader>
|
||||
<ModalTitle>{technology.name}</ModalTitle>
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
Modal stuff...
|
||||
<p class="description">{technology.description}{#if technology.extended_description}{technology.extended_description}{/if}
|
||||
</p>
|
||||
<div class="links">
|
||||
<p class="link"><span class="label">Project website</span>: <span class="website"><a href="{technology.website}" title="Project site for {technology.name}">{technology.website}</a></span></p>
|
||||
{#if technology.repository}<p class="link"><span class="label">Source code repository</span>: <span class="repository"><a
|
||||
href={technology.repository} title="The source code repository for {technology.name}">{technology.repository}</a></span></p>{/if}
|
||||
{#if technology.wikipedia}<p class="link"><span class="label">Wikipedia entry</span>: <span class="wikipedia"><a
|
||||
href={technology.wikipedia}
|
||||
title="Wikipedia Page for {technology.name}">{technology.wikipedia}</a></span></p>{/if}
|
||||
</div>
|
||||
<div class="details">
|
||||
{#if technology.categories}<p class="property categories">{pluraliser('Category',
|
||||
'Categories', technology.categories.length)}: <span class="value">{toOxfordCommaString(technology.categories)}</span></p>{/if}
|
||||
{#if technology.analogues}<p class="property analogues">Alternative to <span
|
||||
class="value">{toOxfordCommaString(technology.analogues)}</span></p>{/if}
|
||||
{#if technology.license}<p class="property license"
|
||||
title="The libre license for this project is {technology.license}">License: <span
|
||||
class="value"><a href="{licenses[technology.license].url}">{licenses[technology.license].name}</a></span></p>{/if}
|
||||
</div>
|
||||
</ModalBody>
|
||||
{#if hasInstances(technology)}
|
||||
<div class="instances">
|
||||
<p class="instances">Instances of {technology.name}:</p>
|
||||
<ol>
|
||||
{#each technology.instances as instance}
|
||||
<li>
|
||||
<p class="instance"><a href="https://{instance.domain}">{instance.domain}</a> hosted on '{instance.host}' <span class="marker circle" style="background-color: {affiliate_colours[instance.affiliation].colour}"/> by {instance.affiliation}".
|
||||
</p>
|
||||
</li>
|
||||
{/each}
|
||||
</ol>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="instances">Nothing here yet...</div>
|
||||
{/if}
|
||||
<!--<ModalFooter>
|
||||
Previous and Next buttons to go here.
|
||||
</ModalFooter>-->
|
||||
</Modal>
|
||||
|
||||
<style>
|
||||
.tile {
|
||||
display: inline-block;
|
||||
height: 515px;
|
||||
/*min-width: 240px;
|
||||
max-width: 300px;*/
|
||||
width: 290px;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 5px 5px 3px #6a6d6a;
|
||||
.tile.technology {
|
||||
|
||||
background-color: #eee;
|
||||
border: solid 3px #3a8ea5;
|
||||
border-radius: 15px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background-color: #eee;
|
||||
margin-top: 8px;
|
||||
margin-right: 18px;
|
||||
box-shadow: 5px 5px 3px #6a6d6a;
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
height: 515px;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 10px;
|
||||
margin-right: 18px;
|
||||
margin-top: 8px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: top !important;
|
||||
width: 290px;
|
||||
}
|
||||
.tile .links {
|
||||
.tile.technology .links {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
padding: 2px 6px;
|
||||
font-size: 90%;
|
||||
}
|
||||
.tile:hover {
|
||||
box-shadow: 10px 10px 6px #727372;
|
||||
.tile.technology:hover {
|
||||
box-shadow: 6px 6px 5px #727372;
|
||||
}
|
||||
.tile:active {
|
||||
.tile.technology:active {
|
||||
box-shadow: 0 0 0;
|
||||
border-color: #000;
|
||||
}
|
||||
.tile .links h2 a {
|
||||
.tile.technology .links h2 a {
|
||||
color: #e6c4fc;
|
||||
}
|
||||
.tile .links a:visited {
|
||||
.tile.technology .links a:visited {
|
||||
color: #ced0ff;
|
||||
}
|
||||
.tile .links a:hover,
|
||||
.tile:hover h2 a {
|
||||
.tile.technology .links a:hover,
|
||||
.tile.technology:hover h2 a {
|
||||
color: #fff !important;
|
||||
}
|
||||
.tile h2 {
|
||||
.tile.technology h2 {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
background-color: #999;
|
||||
text-align: center;
|
||||
padding: 0.5em;
|
||||
margin: 0;
|
||||
}
|
||||
.tile h2 a {
|
||||
.tile.technology h2 a {
|
||||
color: #e6c4fc;
|
||||
}
|
||||
.tile h2 a:visited {
|
||||
.tile.technology h2 a:visited {
|
||||
color: #ced0ff;
|
||||
}
|
||||
.tile h2 a:hover {
|
||||
.tile.technology h2 a:hover {
|
||||
color: #fff;
|
||||
}
|
||||
.tile .description {
|
||||
.tile.technology .description {
|
||||
height: 200px;
|
||||
line-height: 1.2em;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
padding-top: 0.5em;
|
||||
vertical-align: top;
|
||||
}
|
||||
.tile p {
|
||||
.tile.technology p {
|
||||
padding: 0 0.7em 0.2em 0.7em;
|
||||
font-size: 80%;
|
||||
color: #555;
|
||||
}
|
||||
.tile p.property {
|
||||
.tile.technology p.property {
|
||||
padding: 0.2em 0.7em;
|
||||
margin: 0;
|
||||
}
|
||||
.instances {
|
||||
.tile.technology .details {
|
||||
vertical-align: bottom;
|
||||
}
|
||||
.tile.technology .instances {
|
||||
background-color: #ddd;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
height: 2.5em;
|
||||
width: 100%;
|
||||
width: 100% !important;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.instances p { padding-top: 0.7em; vertical-align: middle; }
|
||||
.instances .marker {
|
||||
margin-right: 3px;
|
||||
.tile.technology .instances p {
|
||||
width: 100%;
|
||||
padding-top: 0.7em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.webservices li {
|
||||
list-style-type: none;
|
||||
.tile.technology .instances .marker {
|
||||
margin-right: 3px;
|
||||
}
|
||||
.value {
|
||||
font-weight: bold;
|
||||
|
@ -217,10 +260,4 @@
|
|||
.circle:hover {
|
||||
border: 1px solid #000;
|
||||
}
|
||||
.square {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
background-color: #555;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,25 +1,20 @@
|
|||
<script>
|
||||
import tabler from 'yesvelte/css/tabler.min.css?url';
|
||||
import daisyui from 'yesvelte/css/daisyui.min.css?url';
|
||||
import '../app.css';
|
||||
import '../app.css';
|
||||
|
||||
import { Navbar, NavbarItem } from 'yesvelte';
|
||||
|
||||
// you can change theme of your project from here.
|
||||
let theme = 'tabler';
|
||||
// let theme = 'daisyui';
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
{#if theme === 'tabler'}
|
||||
<link rel="stylesheet" href={tabler} />
|
||||
{:else}
|
||||
<link rel="stylesheet" href={daisyui} />
|
||||
{/if}
|
||||
<link rel="stylesheet" href={tabler} />
|
||||
</svelte:head>
|
||||
|
||||
<nav>
|
||||
<a href="/">Web Services</a>
|
||||
<a href="/about">About</a>
|
||||
</nav>
|
||||
<Navbar theme='light' style="margin-bottom: 2em;">
|
||||
<a href="/"><NavbarItem title="Web Services" /></a>
|
||||
<a href="/about"><NavbarItem title="About" /></a>
|
||||
</Navbar>
|
||||
|
||||
<!-- content provided by the +page.svelte file in this directory - and sub-directories -->
|
||||
<slot />
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
// for multiple async fetches: https://youtu.be/EQy-AYhZIlE?si=FwyAPUjbixSUlc9q&t=490
|
||||
export const load = async ({ fetch }) => {
|
||||
const webservicesResult = await fetch('https://static.magnificent.nz/webservices/webservices.json');
|
||||
const webservicesData = await webservicesResult.json();
|
||||
|
||||
//console.log(webservicesData);
|
||||
return {
|
||||
webservices: {
|
||||
technologies: webservicesData.technologies,
|
||||
affiliates: webservicesData.affiliates,
|
||||
hosts: webservicesData.hosts
|
||||
}
|
||||
};
|
||||
}
|
||||
const webservicesResult = await fetch(
|
||||
'https://static.magnificent.nz/webservices/webservices.json'
|
||||
);
|
||||
const webservicesData = await webservicesResult.json();
|
||||
|
||||
//console.log(webservicesData);
|
||||
return {
|
||||
webservices: {
|
||||
technologies: webservicesData.technologies,
|
||||
affiliates: webservicesData.affiliates,
|
||||
hosts: webservicesData.hosts,
|
||||
licenses: webservicesData.licenses
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -8,14 +8,16 @@
|
|||
|
||||
// the filters
|
||||
import { setFilter, getFilter } from '$lib/components/filter.js';
|
||||
import { Accordions, Accordion, AccordionBody } from 'yesvelte';
|
||||
import { Accordions, Accordion, AccordionBody, El } from 'yesvelte';
|
||||
import Filterable from '$lib/components/Filterable.svelte';
|
||||
// collapsible sections
|
||||
//import CollapsibleSection from '$lib/components/CollapsibleSection.svelte';
|
||||
import TechnologyTile from '$lib/components/TechnologyTile.svelte';
|
||||
// URL-related stuff
|
||||
import { page } from '$app/stores';
|
||||
|
||||
let tabindex = 0; // a counter which facilitates keyboard navigation.
|
||||
|
||||
let showModal = true; // use to make a technology modal show when rendered
|
||||
|
||||
// return the % a is of b
|
||||
function percent(a, b) {
|
||||
|
@ -41,6 +43,7 @@
|
|||
// actual objects
|
||||
let hosts = [];
|
||||
let affiliates = [];
|
||||
let licenses = [];
|
||||
|
||||
//
|
||||
// just a trivial reassignment
|
||||
|
@ -117,6 +120,10 @@
|
|||
let affiliate = webservices.affiliates[key];
|
||||
affiliates[key] = affiliate;
|
||||
}
|
||||
for (let key in webservices.licenses) {
|
||||
let license = webservices.licenses[key];
|
||||
licenses[key] = license;
|
||||
}
|
||||
|
||||
// assign stats based on gathered data...
|
||||
stats.current = current.length;
|
||||
|
@ -148,7 +155,8 @@
|
|||
host_list: host_list
|
||||
},
|
||||
hosts: hosts,
|
||||
affiliates: affiliates
|
||||
affiliates: affiliates,
|
||||
licenses: licenses
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -279,6 +287,8 @@
|
|||
return technologies.sort((a, b) => a.name.localeCompare(b.name));
|
||||
}
|
||||
|
||||
// set previous and next on each technology
|
||||
|
||||
// generic function to find the intersection of two arrays
|
||||
// approach ref: https://bobbyhadz.com/blog/javascript-get-intersection-of-two-arrays
|
||||
function inCommon(a, b) {
|
||||
|
@ -356,20 +366,30 @@
|
|||
technologies.forEach(function (tech) {
|
||||
var to_include = false;
|
||||
if (hasInstances(tech)) {
|
||||
console.log('instances: ', tech.instances);
|
||||
for (let instance in tech.instances) {
|
||||
console.log('instance = ', instance);
|
||||
//console.log('instances: ', tech.instances);
|
||||
for (let id in tech.instances) {
|
||||
console.log('id = ', id);
|
||||
console.log('field = ', field);
|
||||
let instance = tech.instances[id];
|
||||
let value = instance[field];
|
||||
console.log('field value = ', value);
|
||||
console.log('list = ', list);
|
||||
if (instance.hasOwnProperty(field)) {
|
||||
const intersection = inCommon([instance[field]], list);
|
||||
//const intersection = inCommon([instance[field]], list);
|
||||
const intersection = inCommon([value], list);
|
||||
if (intersection && intersection.constructor === Array) {
|
||||
console.log('intersection length: ', intersection.length);
|
||||
// if any one instance belonging to the tech is in the set, add the tech.
|
||||
if (intersection.length > 0) {
|
||||
console.log('setting to include ',instance.domain);
|
||||
if (instance.hasOwnProperty('domain')) {
|
||||
console.log('setting to include ',instance.domain);
|
||||
}
|
||||
to_include = true;
|
||||
} else {
|
||||
// if an instance doesn't belong in the set, remove it.
|
||||
tech.instances[instance.domain].pop();
|
||||
/*if (instance.hasOwnProperty('domain')) {
|
||||
tech.instances[instance.domain].pop();
|
||||
}*/
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -389,6 +409,7 @@
|
|||
//
|
||||
// process the data received by the web request!
|
||||
const results = processData(webservices);
|
||||
const licenses = results.licenses;
|
||||
|
||||
//const technologies = webservices.technologies;
|
||||
//console.log(technologies);
|
||||
|
@ -422,10 +443,12 @@
|
|||
const affiliates = results.affiliates;
|
||||
//console.log('affiliate_data: ', results.affiliates);
|
||||
const host_colours = hostColours($hostFilter, colours, hosts);
|
||||
console.log('host_colours:', host_colours);
|
||||
//console.log('host_colours:', host_colours);
|
||||
colours.sort();
|
||||
const affiliate_colours = affiliateColours($affiliateFilter, colours, affiliates);
|
||||
console.log('affiliate_colours:',affiliate_colours);
|
||||
//console.log('affiliate_colours:',affiliate_colours);
|
||||
|
||||
//console.log('licenses:',licenses );
|
||||
|
||||
let filteredTechnologies;
|
||||
let technologies;
|
||||
|
@ -464,30 +487,35 @@
|
|||
'license',
|
||||
flattenFilter($licenseFilter)
|
||||
);
|
||||
/*console.log(
|
||||
'about to sort technologies alphabetically - starting with ' +
|
||||
console.log(
|
||||
'about to filter technologies by instance status - starting with ' +
|
||||
filteredTechnologies.length +
|
||||
' technologies...'
|
||||
);*/
|
||||
/*filteredTechnologies = filterTechnologiesByInstanceValue(
|
||||
);
|
||||
filteredTechnologies = filterTechnologiesByInstanceValue(
|
||||
filteredTechnologies,
|
||||
'status',
|
||||
flattenFilter($statusFilter)
|
||||
)*/
|
||||
/*filteredTechnologies = filterTechnologiesByInstanceValue(
|
||||
)
|
||||
filteredTechnologies = filterTechnologiesByInstanceValue(
|
||||
filteredTechnologies,
|
||||
'affiliate',
|
||||
'affiliation',
|
||||
flattenFilter($affiliateFilter)
|
||||
)
|
||||
filteredTechnologies = filterTechnologiesByInstanceValue(
|
||||
filteredTechnologies,
|
||||
'host',
|
||||
flattenFilter($hostFilter)
|
||||
)*/
|
||||
)
|
||||
|
||||
//filteredTechnologies = filterTechnologiesByCategoryList(results.all_services, flattenFilter($categoryFilter));
|
||||
console.log(
|
||||
'about to sort technologies alphabetically - starting with ' +
|
||||
filteredTechnologies.length +
|
||||
' technologies...'
|
||||
);
|
||||
const technologies = sortTechnologies(filteredTechnologies);
|
||||
console.log('finally have ' + technologies.length + ' technologies left to show...');
|
||||
//console.log('finally have ' + technologies.length + ' technologies left to show...');
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -582,8 +610,8 @@
|
|||
<AccordionBody>
|
||||
<p>
|
||||
You can filter the software services listed below by the following filtering dimensions.
|
||||
Click a tag to remove or re-add it or select all or none, or invert your selection if it's
|
||||
more useful.
|
||||
Click a tag to remove or re-add it, or use 'select all or none' to select none, or invert your selection if it's more useful.</p>
|
||||
<p><strong>Tip</strong>: if you want to see all technologies tagged with a specific tag, click that tag to <em>deselect</em> it, and then 'invert selection'. You'll see only the technologies that have the resulting tag.
|
||||
</p>
|
||||
<Accordions>
|
||||
<Accordion title="Software Catergory Filter">
|
||||
|
@ -595,9 +623,7 @@
|
|||
context={'categories'}>
|
||||
<h2 slot="title">Categories</h2>
|
||||
<p slot="description">
|
||||
You can These are general categories of software which help you understand the
|
||||
sort of software you're looking at, as well as identifying functionally similar
|
||||
software for comparison.
|
||||
These are general categories of software which help you understand the sort of software you're looking at, as well as identifying functionally similar software for comparison.
|
||||
</p>
|
||||
</Filterable>
|
||||
</div>
|
||||
|
@ -689,9 +715,16 @@
|
|||
</Accordion>
|
||||
</Accordions>
|
||||
<p>Showing {filteredTechnologies.length} technology tiles.</p>
|
||||
<!--<p>Current URL: {$page.url}...</p>-->
|
||||
<div class="tiles">
|
||||
{#each filteredTechnologies as technology}
|
||||
<TechnologyTile {technology} {affiliate_colours} />
|
||||
{#each filteredTechnologies as technology, index}
|
||||
<!--{#if (index != 1) }-->
|
||||
<TechnologyTile {technology} {affiliate_colours} {licenses}/>
|
||||
<!--{:else}
|
||||
<TechnologyTile {technology} {affiliate_colours} {licenses} {showModal}/>
|
||||
{/if}-->
|
||||
{:else}
|
||||
<p class='no-tiles'>No technologies are selected</p>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -724,7 +757,7 @@
|
|||
min-width: 30em;
|
||||
border: 2px #aaa solid;
|
||||
margin: 0 15px 20px 6px;
|
||||
padding: 0 20px;
|
||||
padding: 10px 20px;
|
||||
box-shadow: 6px 6px 20px #999;
|
||||
}
|
||||
.summary .label {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
|
||||
<h1>About this site... </h1>
|
||||
<p>This simple app is a work-in-progress app that will soon (I hope) include the ability to filter the services using the various vocabularies of tags, like categories of software, libre license used, instance status, which of my roles they're affiliated with, and on which host they reside... </p>
|
||||
<p>This simple app is a work-in-progress that allows visitors to see the various <a href="https://tech.oeru.org/foss-libresoftware-its-about-clarity-and-values">libre</a> web services I currently run, filter them on a variety of properties like functional categories, similar proprietary apps, libre license used, instance status, which of my roles they're affiliated with, and on which host they reside... </p>
|
||||
<p>The <a href="https://forge.magnificent.nz/lightweight/webservices">source code for this project is available</a> under an AGPL license.</p>
|
||||
<p>This app is built on a suite of technologies that are largely new to me, but that I wanted to try out, as I see great potential in them. The key technology is a Javascript 'framework' called <a href="https://kid.svelte.dev">SvelteKit</a>, built with <a href="https://svelte.dev">Svelte</a> (a compiled language that generates pure Javascript) and ecosystem of extensions created by the community which provides useful components designed for reuse.
|
||||
</p>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//import adapter from '@sveltejs/adapter-auto';
|
||||
import adapter from '@sveltejs/adapter-node'; // ref https://medium.com/@loic.joachim/dockerize-sveltekit-with-node-adapter-62c5dc6fc15a
|
||||
import sveltePreprocess from 'svelte-preprocess';
|
||||
//import sveltePreprocess from 'svelte-preprocess';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
|
@ -9,8 +9,8 @@ const config = {
|
|||
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
||||
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
|
||||
adapter: adapter()
|
||||
},
|
||||
preprocess: sveltePreprocess()
|
||||
} //,
|
||||
//preprocess: sveltePreprocess()
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
Loading…
Reference in a new issue