start visualization with cytoscape
This commit is contained in:
parent
77ea389b5c
commit
c15f5a0da7
|
|
@ -9,6 +9,7 @@
|
|||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"clipboard-copy": "^4.0.1",
|
||||
"cytoscape": "^3.23.0",
|
||||
"qrcode-svg": "^1.1.0",
|
||||
"uint8arrays": "^3.1.0",
|
||||
"webnative": "^0.34.1"
|
||||
|
|
@ -18,6 +19,7 @@
|
|||
"@sveltejs/kit": "1.0.0-next.489",
|
||||
"@tailwindcss/typography": "^0.5.2",
|
||||
"@terminusdb/terminusdb-client": "^10.0.25",
|
||||
"@types/cytoscape": "^3.19.9",
|
||||
"@types/qrcode-svg": "^1.1.1",
|
||||
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
||||
"@typescript-eslint/parser": "^4.33.0",
|
||||
|
|
@ -1663,6 +1665,12 @@
|
|||
"integrity": "sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/cytoscape": {
|
||||
"version": "3.19.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/cytoscape/-/cytoscape-3.19.9.tgz",
|
||||
"integrity": "sha512-oqCx0ZGiBO0UESbjgq052vjDAy2X53lZpMrWqiweMpvVwKw/2IiYDdzPFK6+f4tMfdv9YKEM9raO5bAZc3UYBg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/graceful-fs": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz",
|
||||
|
|
@ -3183,6 +3191,18 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cytoscape": {
|
||||
"version": "3.23.0",
|
||||
"resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.23.0.tgz",
|
||||
"integrity": "sha512-gRZqJj/1kiAVPkrVFvz/GccxsXhF3Qwpptl32gKKypO4IlqnKBjTOu+HbXtEggSGzC5KCaHp3/F7GgENrtsFkA==",
|
||||
"dependencies": {
|
||||
"heap": "^0.2.6",
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/daisyui": {
|
||||
"version": "2.19.0",
|
||||
"integrity": "sha512-lLOz4cHm3xpm0AfdFojDFrhiDu4hZTdEbcVJri+KzQn1HvxmZS4STVujn8tV4RQXjchGdnIsXFqxd6F7rVZBiA==",
|
||||
|
|
@ -4572,6 +4592,11 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/heap": {
|
||||
"version": "0.2.7",
|
||||
"resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz",
|
||||
"integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg=="
|
||||
},
|
||||
"node_modules/html-escaper": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
||||
|
|
@ -10250,6 +10275,12 @@
|
|||
"integrity": "sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/cytoscape": {
|
||||
"version": "3.19.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/cytoscape/-/cytoscape-3.19.9.tgz",
|
||||
"integrity": "sha512-oqCx0ZGiBO0UESbjgq052vjDAy2X53lZpMrWqiweMpvVwKw/2IiYDdzPFK6+f4tMfdv9YKEM9raO5bAZc3UYBg==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/graceful-fs": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz",
|
||||
|
|
@ -11298,6 +11329,15 @@
|
|||
"array-find-index": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"cytoscape": {
|
||||
"version": "3.23.0",
|
||||
"resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.23.0.tgz",
|
||||
"integrity": "sha512-gRZqJj/1kiAVPkrVFvz/GccxsXhF3Qwpptl32gKKypO4IlqnKBjTOu+HbXtEggSGzC5KCaHp3/F7GgENrtsFkA==",
|
||||
"requires": {
|
||||
"heap": "^0.2.6",
|
||||
"lodash": "^4.17.21"
|
||||
}
|
||||
},
|
||||
"daisyui": {
|
||||
"version": "2.19.0",
|
||||
"integrity": "sha512-lLOz4cHm3xpm0AfdFojDFrhiDu4hZTdEbcVJri+KzQn1HvxmZS4STVujn8tV4RQXjchGdnIsXFqxd6F7rVZBiA==",
|
||||
|
|
@ -12221,6 +12261,11 @@
|
|||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true
|
||||
},
|
||||
"heap": {
|
||||
"version": "0.2.7",
|
||||
"resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz",
|
||||
"integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg=="
|
||||
},
|
||||
"html-escaper": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
||||
|
|
|
|||
|
|
@ -12,10 +12,11 @@
|
|||
"format": "prettier --write --plugin-search-dir=. ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@terminusdb/terminusdb-client": "^10.0.25",
|
||||
"@sveltejs/adapter-static": "1.0.0-next.43",
|
||||
"@sveltejs/kit": "1.0.0-next.489",
|
||||
"@tailwindcss/typography": "^0.5.2",
|
||||
"@terminusdb/terminusdb-client": "^10.0.25",
|
||||
"@types/cytoscape": "^3.19.9",
|
||||
"@types/qrcode-svg": "^1.1.1",
|
||||
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
||||
"@typescript-eslint/parser": "^4.33.0",
|
||||
|
|
@ -55,6 +56,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"clipboard-copy": "^4.0.1",
|
||||
"cytoscape": "^3.23.0",
|
||||
"qrcode-svg": "^1.1.0",
|
||||
"uint8arrays": "^3.1.0",
|
||||
"webnative": "^0.34.1"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,223 @@
|
|||
<script lang="ts">
|
||||
import cytoscape from 'cytoscape';
|
||||
import { onMount } from 'svelte'
|
||||
|
||||
let cy;
|
||||
let cyDiv;
|
||||
|
||||
const createGraph = () => {
|
||||
cy = cytoscape({
|
||||
container: cyDiv,
|
||||
elements: [],
|
||||
style: [],
|
||||
});
|
||||
};
|
||||
|
||||
interface INodeData {
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface INode {
|
||||
data: INodeData;
|
||||
}
|
||||
|
||||
interface IEdgeData {
|
||||
id: string;
|
||||
source: string;
|
||||
target: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface IEdge {
|
||||
data: IEdgeData;
|
||||
}
|
||||
|
||||
let knowledgeGraphJson: any = {
|
||||
"entities": [{
|
||||
"label": "Organization",
|
||||
"title": "Neuralink"
|
||||
}, {
|
||||
"label": "Organization",
|
||||
"title": "SpaceX"
|
||||
}, {
|
||||
"label": "Organization",
|
||||
"title": "Pretoria"
|
||||
}, {
|
||||
"label": "Organization",
|
||||
"title": "The Boring Company"
|
||||
}, {
|
||||
"label": "Organization",
|
||||
"title": "University of Pretoria"
|
||||
}, {
|
||||
"label": "Organization",
|
||||
"title": "Stanford University"
|
||||
}, {
|
||||
"label": "Person",
|
||||
"title": "Jeff Bezos"
|
||||
}, {
|
||||
"label": "Organization",
|
||||
"title": "University of Pennsylvania"
|
||||
}, {
|
||||
"label": "Person",
|
||||
"title": "Kimbal Musk"
|
||||
}, {
|
||||
"label": "Organization",
|
||||
"title": "Tesla, Inc."
|
||||
}, {
|
||||
"label": "Person",
|
||||
"title": "Elon Musk"
|
||||
}],
|
||||
"relations": [{
|
||||
"source": "Elon Musk",
|
||||
"target": "Neuralink"
|
||||
}, {
|
||||
"source": "Tesla, Inc.",
|
||||
"target": "Elon Musk",
|
||||
"type": "owned by"
|
||||
}, {
|
||||
"source": "Elon Musk",
|
||||
"target": "University of Pennsylvania",
|
||||
"type": "residence"
|
||||
}, {
|
||||
"source": "Elon Musk",
|
||||
"target": "Tesla, Inc.",
|
||||
"type": "owned by"
|
||||
}, {
|
||||
"source": "The Boring Company",
|
||||
"target": "Tesla, Inc.",
|
||||
"type": "owned by"
|
||||
}, {
|
||||
"source": "Elon Musk",
|
||||
"target": "Kimbal Musk",
|
||||
"type": "sibling"
|
||||
}, {
|
||||
"source": "University of Pennsylvania",
|
||||
"target": "Elon Musk",
|
||||
"type": "residence"
|
||||
}, {
|
||||
"source": "The Boring Company",
|
||||
"target": "Neuralink",
|
||||
"type": "subsidiary"
|
||||
}, {
|
||||
"source": "Elon Musk",
|
||||
"target": "University of Pretoria",
|
||||
"type": "work location"
|
||||
}, {
|
||||
"source": "The Boring Company",
|
||||
"target": "Elon Musk",
|
||||
"type": "owned by"
|
||||
}, {
|
||||
"source": "Kimbal Musk",
|
||||
"target": "Elon Musk",
|
||||
"type": "sibling"
|
||||
}, {
|
||||
"source": "Neuralink",
|
||||
"target": "Elon Musk",
|
||||
"type": "owned by"
|
||||
}, {
|
||||
"source": "Elon Musk",
|
||||
"target": "The Boring Company",
|
||||
"type": "owned by"
|
||||
}, {
|
||||
"source": "Elon Musk",
|
||||
"target": "University of Pennsylvania",
|
||||
"type": "work location"
|
||||
}]
|
||||
};
|
||||
|
||||
async function fetchData() {
|
||||
const response = await fetch('./data.json');
|
||||
if (response.ok) {
|
||||
knowledgeGraphJson = await response.json();
|
||||
} else {
|
||||
alert(`HTTP-Error: ${response.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
let nodes: INode[] = [];
|
||||
let edges: IEdge[] = [];
|
||||
|
||||
onMount(async () => {
|
||||
console.log("1");
|
||||
cyDiv = document.getElementById('cy');
|
||||
console.log("2");
|
||||
|
||||
//await fetchData();
|
||||
|
||||
console.log(knowledgeGraphJson);
|
||||
|
||||
nodes = knowledgeGraphJson.entities.map((entity: any) => ({
|
||||
data: { id: entity.title },
|
||||
}));
|
||||
|
||||
edges = knowledgeGraphJson.relations.map((relation: any, index: string) => ({
|
||||
data: {
|
||||
id: index,
|
||||
source: relation.source,
|
||||
target: relation.target,
|
||||
label: relation.type,
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
console.log(edges);
|
||||
const cy = cytoscape({
|
||||
container: document.getElementById('cy'),
|
||||
elements: {
|
||||
nodes,
|
||||
edges,
|
||||
},
|
||||
style: [
|
||||
{
|
||||
selector: 'node',
|
||||
style: {
|
||||
'text-valign': 'center',
|
||||
'text-halign': 'center',
|
||||
label: 'data(id)',
|
||||
width: 100,
|
||||
height: 100,
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: 'edge',
|
||||
style: {
|
||||
width: 5,
|
||||
'line-color': 'light grey',
|
||||
'target-arrow-color': 'grey',
|
||||
'target-arrow-shape': 'triangle',
|
||||
'curve-style': 'bezier',
|
||||
label: 'data(label)',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
cy.layout({
|
||||
name: 'cose',
|
||||
}).run();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#cy {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 55px;
|
||||
left: 0px;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<section class="overflow-hidden text-gray-700">
|
||||
<div class="pt-8 p-6 md:p-8 mx-auto">
|
||||
|
||||
</div>
|
||||
|
||||
<div id="cy"></div>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
{
|
||||
"entities": [{
|
||||
"label": "Organization",
|
||||
"title": "Neuralink"
|
||||
}, {
|
||||
"label": "Organization",
|
||||
"title": "SpaceX"
|
||||
}, {
|
||||
"label": "Organization",
|
||||
"title": "Pretoria"
|
||||
}, {
|
||||
"label": "Organization",
|
||||
"title": "The Boring Company"
|
||||
}, {
|
||||
"label": "Organization",
|
||||
"title": "University of Pretoria"
|
||||
}, {
|
||||
"label": "Organization",
|
||||
"title": "Stanford University"
|
||||
}, {
|
||||
"label": "Person",
|
||||
"title": "Jeff Bezos"
|
||||
}, {
|
||||
"label": "Organization",
|
||||
"title": "University of Pennsylvania"
|
||||
}, {
|
||||
"label": "Person",
|
||||
"title": "Kimbal Musk"
|
||||
}, {
|
||||
"label": "Organization",
|
||||
"title": "Tesla, Inc."
|
||||
}, {
|
||||
"label": "Person",
|
||||
"title": "Elon Musk"
|
||||
}],
|
||||
"relations": [{
|
||||
"source": "Elon Musk",
|
||||
"target": "Neuralink"
|
||||
}, {
|
||||
"source": "Tesla, Inc.",
|
||||
"target": "Elon Musk",
|
||||
"type": "owned by"
|
||||
}, {
|
||||
"source": "Elon Musk",
|
||||
"target": "University of Pennsylvania",
|
||||
"type": "residence"
|
||||
}, {
|
||||
"source": "Elon Musk",
|
||||
"target": "Tesla, Inc.",
|
||||
"type": "owned by"
|
||||
}, {
|
||||
"source": "The Boring Company",
|
||||
"target": "Tesla, Inc.",
|
||||
"type": "owned by"
|
||||
}, {
|
||||
"source": "Elon Musk",
|
||||
"target": "Kimbal Musk",
|
||||
"type": "sibling"
|
||||
}, {
|
||||
"source": "University of Pennsylvania",
|
||||
"target": "Elon Musk",
|
||||
"type": "residence"
|
||||
}, {
|
||||
"source": "The Boring Company",
|
||||
"target": "Neuralink",
|
||||
"type": "subsidiary"
|
||||
}, {
|
||||
"source": "Elon Musk",
|
||||
"target": "University of Pretoria",
|
||||
"type": "work location"
|
||||
}, {
|
||||
"source": "The Boring Company",
|
||||
"target": "Elon Musk",
|
||||
"type": "owned by"
|
||||
}, {
|
||||
"source": "Kimbal Musk",
|
||||
"target": "Elon Musk",
|
||||
"type": "sibling"
|
||||
}, {
|
||||
"source": "Neuralink",
|
||||
"target": "Elon Musk",
|
||||
"type": "owned by"
|
||||
}, {
|
||||
"source": "Elon Musk",
|
||||
"target": "The Boring Company",
|
||||
"type": "owned by"
|
||||
}, {
|
||||
"source": "Elon Musk",
|
||||
"target": "University of Pennsylvania",
|
||||
"type": "work location"
|
||||
}]
|
||||
}
|
||||
|
|
@ -20,6 +20,11 @@
|
|||
href: '/gallery/',
|
||||
icon: PhotoGallery
|
||||
},
|
||||
{
|
||||
label: 'Explore',
|
||||
href: '/explore/',
|
||||
icon: PhotoGallery
|
||||
},
|
||||
{
|
||||
label: 'About This Template',
|
||||
href: '/about/',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
<script lang="ts">
|
||||
import Explore from '$components/explore/Explore.svelte'
|
||||
</script>
|
||||
|
||||
<Explore />
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="66" fill="none">
|
||||
<path
|
||||
stroke="#171717"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M14.167 45.667C7.17 45.667 1.5 39.996 1.5 33c0-6.039 4.226-11.09 9.882-12.36A15.89 15.89 0 0 1 11 17.167c0-8.745 7.089-15.834 15.833-15.834 7.662 0 14.052 5.441 15.518 12.67.105-.002.21-.003.316-.003C51.41 14 58.5 21.089 58.5 29.833c0 7.66-5.44 14.05-12.667 15.517M39.5 36.167l-9.5-9.5m0 0-9.5 9.5m9.5-9.5v38"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 510 B |
|
|
@ -0,0 +1,24 @@
|
|||
<script lang="ts">
|
||||
import type { Image } from '$routes/gallery/lib/gallery'
|
||||
|
||||
export let image: Image
|
||||
export let openModal: (image: Image) => void
|
||||
|
||||
const handleOpenModal = () => openModal(image)
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="relative group w-full aspect-[22/23] rounded-lg border-2 border-transparent hover:border-base-content box-border overflow-hidden transition-colors ease-in"
|
||||
on:click={handleOpenModal}
|
||||
>
|
||||
<div
|
||||
class="flex items-center justify-center absolute z-10 top-0 right-0 bottom-0 left-0 bg-[#00000035] opacity-0 group-hover:opacity-100 transition-opacity ease-in"
|
||||
/>
|
||||
<div class="relative pb-[105%]">
|
||||
<img
|
||||
class="absolute block object-cover object-center w-full h-full"
|
||||
alt={`Gallery Image: ${image.name}`}
|
||||
src={image.src}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
<script lang="ts">
|
||||
import { onDestroy } from 'svelte'
|
||||
import { onMount } from 'svelte'
|
||||
|
||||
import TerminusClient from "@terminusdb/terminusdb-client"
|
||||
import { filesystemStore, sessionStore } from '$src/stores'
|
||||
import { AREAS, galleryStore } from '$routes/gallery/stores'
|
||||
import { getImagesFromWNFS, type Image } from '$routes/gallery/lib/gallery'
|
||||
import FileUploadCard from '$routes/gallery/components/upload/FileUploadCard.svelte'
|
||||
import ImageCard from '$routes/gallery/components/imageGallery/ImageCard.svelte'
|
||||
import ImageModal from '$routes/gallery/components/imageGallery/ImageModal.svelte'
|
||||
|
||||
const client = new TerminusClient.WOQLClient(
|
||||
"https://cloud.terminusdb.com/Myseelia",{
|
||||
user:"zaldarren@gmail.com",
|
||||
organization:"Myseelia",
|
||||
db: "Myseelia",
|
||||
token: "dGVybWludXNkYjovLy9kYXRhL2tleXNfYXBpLzg5OTY0ZGI5OWFlYjQ1Zjc5OGM5ZTRiZWI2MzExOGJhZjhiOWRiOWNlOTJiNmU2NGI0NDEzZjIzNDFmOGVkMjc=_869e9bd2465ad84126151962994fcfa22d4b7ec9375edf16b4182e7f36e4b2b820075ba22e78f629e0691eddbeae6998a6504d5ce287aa1df2602cb556b58e1730b0b93feb0e9304"
|
||||
}
|
||||
);
|
||||
|
||||
let username = $sessionStore.username;
|
||||
let bioregion = '';
|
||||
let ecozone = '';
|
||||
let affiliatedOrganizations = "Organization/8c8368b55dc80f18ba254771701f6d1bc79a3a90f127c28b3145a2c2204e97ce";
|
||||
let givenName = '';
|
||||
let hasCredential = {};
|
||||
|
||||
|
||||
onMount(async () => {
|
||||
try{
|
||||
await client.connect()
|
||||
const schema = await client.getSchema("myseelia", "main")
|
||||
// console.log("Schema");
|
||||
// console.log(schema);
|
||||
// console.log("result");
|
||||
|
||||
// const result = await client.getDocument({as_list:true,type:"Person",query: { userName: "tester" }})
|
||||
// console.log(result);
|
||||
}catch(err){
|
||||
console.error("this is it" + err.message)
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Open the ImageModal and pass it the selected `image` from the gallery
|
||||
* @param image
|
||||
*/
|
||||
let selectedImage: Image
|
||||
const setSelectedImage: (image: Image) => void = image =>
|
||||
(selectedImage = image)
|
||||
|
||||
const clearSelectedImage = () => (selectedImage = null)
|
||||
|
||||
// If galleryStore.selectedArea changes from private to public, re-run getImagesFromWNFS
|
||||
let selectedArea = null
|
||||
const unsubscribeGalleryStore = galleryStore.subscribe(async updatedStore => {
|
||||
// Get initial selectedArea
|
||||
if (!selectedArea) {
|
||||
selectedArea = updatedStore.selectedArea
|
||||
}
|
||||
|
||||
if (selectedArea !== updatedStore.selectedArea) {
|
||||
selectedArea = updatedStore.selectedArea
|
||||
await getImagesFromWNFS()
|
||||
}
|
||||
})
|
||||
|
||||
// Once the user has been authed, fetch the images from their file system
|
||||
let imagesFetched = false
|
||||
const unsubscribeSessionStore = sessionStore.subscribe((newState) => {
|
||||
if (newState.authed && $filesystemStore && !imagesFetched) {
|
||||
imagesFetched = true
|
||||
// Get images from the user's public WNFS
|
||||
getImagesFromWNFS()
|
||||
}
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
unsubscribeGalleryStore()
|
||||
unsubscribeSessionStore()
|
||||
})
|
||||
|
||||
|
||||
|
||||
function handleSubmit() {
|
||||
makeConnection();
|
||||
}
|
||||
|
||||
export async function makeConnection(){
|
||||
try{
|
||||
const entryObj =
|
||||
{
|
||||
"@type" : "Person",
|
||||
"userName" : username,
|
||||
"givenName" : givenName,
|
||||
"bioregion": bioregion,
|
||||
"ecozone": ecozone,
|
||||
"hasCredential": hasCredential,
|
||||
"affiliation": affiliatedOrganizations
|
||||
};
|
||||
if (username == entryObj.userName){
|
||||
await client.updateDocument(entryObj)
|
||||
} else{
|
||||
await client.addDocument(entryObj);
|
||||
}
|
||||
const entries2 = await client.getDocument({"graph_type":"instance","as_list":true,"type":"Person"})
|
||||
console.log(entries2);
|
||||
}catch(err){
|
||||
console.error(err.message)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
form {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 3fr;
|
||||
}
|
||||
|
||||
label {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
input{
|
||||
background-color: rgb(255, 255, 255);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
button{
|
||||
background-color: #4CAF50; /* Green */
|
||||
padding: 12px 20px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<section class="overflow-hidden text-gray-700">
|
||||
<div class="pt-8 p-6 md:p-8 mx-auto">
|
||||
<div
|
||||
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 xl:lg:grid-cols-6 gap-4"
|
||||
>
|
||||
{#each $galleryStore.selectedArea === AREAS.PRIVATE ? $galleryStore.privateImages : $galleryStore.publicImages as image}{/each}
|
||||
</div>
|
||||
<form on:submit|preventDefault={handleSubmit}>
|
||||
<label class="label dark:text-white">
|
||||
Given Name:
|
||||
<input class="input text-white dark:text-black" type="text" bind:value={givenName} />
|
||||
</label>
|
||||
<br />
|
||||
<label class="label dark:text-white">
|
||||
Bioregion:
|
||||
<input class="input text-white dark:text-black" type="text" bind:value={bioregion} />
|
||||
</label>
|
||||
<br />
|
||||
<label class="label dark:text-white">
|
||||
Ecozone:
|
||||
<input class="input text-white dark:text-black" type="text" bind:value={ecozone} />
|
||||
</label >
|
||||
<br />
|
||||
<label class="label dark:text-white">
|
||||
Has Credential:
|
||||
<input class="input text-white dark:text-black" type="text" bind:value={hasCredential} />
|
||||
</label >
|
||||
<br />
|
||||
<label class="label dark:text-white">
|
||||
Affiliated organizations:
|
||||
<input class="input text-white dark:text-black" type="text" bind:value={affiliatedOrganizations}/>
|
||||
</label>
|
||||
<br />
|
||||
<button class="bg-blue-500 text-white dark:text-black" type="submit">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{#if selectedImage}
|
||||
<ImageModal
|
||||
image={selectedImage}
|
||||
isModalOpen={!!selectedImage}
|
||||
on:close={clearSelectedImage}
|
||||
/>
|
||||
{/if}
|
||||
</section>
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
<script lang="ts">
|
||||
import { createEventDispatcher, onDestroy, onMount } from 'svelte'
|
||||
|
||||
import { ipfsGatewayUrl } from '$lib/app-info';
|
||||
import { galleryStore } from '$routes/gallery/stores'
|
||||
import { deleteImageFromWNFS, type Gallery, type Image } from '$routes/gallery/lib/gallery'
|
||||
|
||||
export let image: Image
|
||||
export let isModalOpen: boolean = false
|
||||
let previousImage: Image | undefined
|
||||
let nextImage: Image | undefined
|
||||
let showPreviousArrow: boolean
|
||||
let showNextArrow: boolean
|
||||
let gallery: Gallery
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const unsubcribe = galleryStore.subscribe(newState => (gallery = newState))
|
||||
|
||||
/**
|
||||
* Close the modal, clear the image state vars, set `isModalOpen` to false
|
||||
* and dispatch the close event to clear the image from the parent's state
|
||||
*/
|
||||
const handleCloseModal: () => void = () => {
|
||||
image = null
|
||||
previousImage = null
|
||||
nextImage = null
|
||||
isModalOpen = false
|
||||
dispatch('close')
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an image from the user's WNFS
|
||||
*/
|
||||
const handleDeleteImage: () => Promise<void> = async () => {
|
||||
await deleteImageFromWNFS(image.name)
|
||||
handleCloseModal()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the previous and next images to be toggled to when the arrows are clicked
|
||||
*/
|
||||
const setCarouselState = () => {
|
||||
const imageList = image.private
|
||||
? gallery.privateImages
|
||||
: gallery.publicImages
|
||||
const currentIndex = imageList.findIndex(val => val.cid === image.cid)
|
||||
previousImage =
|
||||
imageList[currentIndex - 1] ?? imageList[imageList.length - 1]
|
||||
nextImage = imageList[currentIndex + 1] ?? imageList[0]
|
||||
|
||||
showPreviousArrow = imageList.length > 1 && !!previousImage
|
||||
showNextArrow = imageList.length > 1 && !!nextImage
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the correct image when a user clicks the Next or Previous arrows
|
||||
* @param direction
|
||||
*/
|
||||
const handleNextOrPrevImage: (
|
||||
direction: 'next' | 'prev'
|
||||
) => void = direction => {
|
||||
image = direction === 'prev' ? previousImage : nextImage
|
||||
setCarouselState()
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect `Escape` key presses to close the modal or `ArrowRight`/`ArrowLeft`
|
||||
* presses to navigate the carousel
|
||||
* @param event
|
||||
*/
|
||||
const handleKeyDown: (event: KeyboardEvent) => void = event => {
|
||||
if (event.key === 'Escape') handleCloseModal()
|
||||
|
||||
if (showNextArrow && event.key === 'ArrowRight')
|
||||
handleNextOrPrevImage('next')
|
||||
|
||||
if (showPreviousArrow && event.key === 'ArrowLeft')
|
||||
handleNextOrPrevImage('prev')
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
setCarouselState()
|
||||
})
|
||||
|
||||
// Unsubscribe from galleryStore updates
|
||||
onDestroy(unsubcribe)
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={handleKeyDown} />
|
||||
|
||||
{#if !!image}
|
||||
<!-- bind:checked can't be set to !!image, so we need to set it to a boolean(casting image as a boolean throws a svelte error, so we're using isModalOpen) -->
|
||||
<input
|
||||
type="checkbox"
|
||||
id={`image-modal-${image.cid}`}
|
||||
class="modal-toggle"
|
||||
bind:checked={isModalOpen}
|
||||
/>
|
||||
<label
|
||||
for={`image-modal-${image.cid}`}
|
||||
class="modal cursor-pointer z-50"
|
||||
on:click|self={handleCloseModal}
|
||||
>
|
||||
<div class="modal-box relative text-center text-base-content">
|
||||
<label
|
||||
for={`image-modal-${image.cid}`}
|
||||
class="btn btn-xs btn-circle absolute right-2 top-2"
|
||||
on:click={handleCloseModal}
|
||||
>
|
||||
✕
|
||||
</label>
|
||||
<div>
|
||||
<h3 class="mb-7 text-lg break-all">{image.name}</h3>
|
||||
|
||||
<div class="relative">
|
||||
{#if showPreviousArrow}
|
||||
<button
|
||||
class="absolute top-1/2 -left-[25px] -translate-y-1/2 inline-block text-center text-[40px]"
|
||||
on:click={() => handleNextOrPrevImage('prev')}
|
||||
>
|
||||
‹
|
||||
</button>
|
||||
{/if}
|
||||
<img
|
||||
class="block object-cover object-center border-2 border-base-content w-full h-full mb-4 rounded-[1rem]"
|
||||
alt={`Image: ${image.name}`}
|
||||
src={image.src}
|
||||
/>
|
||||
{#if showNextArrow}
|
||||
<button
|
||||
class="absolute top-1/2 -right-[25px] -translate-y-1/2 inline-block text-center text-[40px]"
|
||||
on:click={() => handleNextOrPrevImage('next')}
|
||||
>
|
||||
›
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
<a
|
||||
href={`https://ipfs.${ipfsGatewayUrl}/ipfs/${image.cid}/userland`}
|
||||
target="_blank"
|
||||
class="underline mb-4 hover:text-slate-500"
|
||||
>
|
||||
View on IPFS
|
||||
</a>
|
||||
<p class="mb-4">
|
||||
Created at {new Date(image.ctime).toDateString()}
|
||||
</p>
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<a href={image.src} download={image.name} class="btn btn-primary">
|
||||
Download Image
|
||||
</a>
|
||||
<button class="btn btn-outline" on:click={handleDeleteImage}>
|
||||
Delete Image
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
{/if}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
<script lang="ts">
|
||||
import TerminusClient from "@terminusdb/terminusdb-client";
|
||||
import { onDestroy } from 'svelte'
|
||||
|
||||
import { filesystemStore, sessionStore } from '$src/stores'
|
||||
import { AREAS, galleryStore } from '$routes/gallery/stores'
|
||||
import { getImagesFromWNFS, type Image } from '$routes/gallery/lib/gallery'
|
||||
import FileUploadCard from '$routes/gallery/components/upload/FileUploadCard.svelte'
|
||||
import ImageCard from '$routes/gallery/components/imageGallery/ImageCard.svelte'
|
||||
import ImageModal from '$routes/gallery/components/imageGallery/ImageModal.svelte'
|
||||
|
||||
/**
|
||||
* Open the ImageModal and pass it the selected `image` from the gallery
|
||||
* @param image
|
||||
*/
|
||||
let selectedImage: Image
|
||||
const setSelectedImage: (image: Image) => void = image =>
|
||||
(selectedImage = image)
|
||||
|
||||
// If galleryStore.selectedArea changes from private to public, re-run getImagesFromWNFS
|
||||
let selectedArea = null
|
||||
const unsubscribeGalleryStore = galleryStore.subscribe(async updatedStore => {
|
||||
// Get initial selectedArea
|
||||
if (!selectedArea) {
|
||||
selectedArea = updatedStore.selectedArea
|
||||
}
|
||||
|
||||
if (selectedArea !== updatedStore.selectedArea) {
|
||||
selectedArea = updatedStore.selectedArea
|
||||
}
|
||||
})
|
||||
|
||||
// Once the user has been authed, fetch the images from their file system
|
||||
let imagesFetched = false
|
||||
const unsubscribeSessionStore = sessionStore.subscribe((newState) => {
|
||||
if (newState.authed && $filesystemStore && !imagesFetched) {
|
||||
imagesFetched = true
|
||||
// Get images from the user's public WNFS
|
||||
getImagesFromWNFS()
|
||||
}
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
unsubscribeGalleryStore()
|
||||
unsubscribeSessionStore()
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<section class="overflow-hidden text-gray-700">
|
||||
<div class="pt-8 p-6 md:p-8 mx-auto">
|
||||
<div
|
||||
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 xl:lg:grid-cols-6 gap-4"
|
||||
>
|
||||
<FileUploadCard />
|
||||
{#each $galleryStore.selectedArea === AREAS.PRIVATE ? $galleryStore.privateImages : $galleryStore.publicImages as image}
|
||||
<ImageCard {image} openModal={setSelectedImage} />
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if selectedImage}
|
||||
<ImageModal
|
||||
image={selectedImage}
|
||||
isModalOpen={!!selectedImage}
|
||||
on:close={clearSelectedImage}
|
||||
/>
|
||||
{/if}
|
||||
</section>
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
<script lang="ts">
|
||||
import {
|
||||
getImagesFromWNFS,
|
||||
uploadImageToWNFS
|
||||
} from '$routes/gallery/lib/gallery'
|
||||
import { addNotification } from '$lib/notifications'
|
||||
|
||||
/**
|
||||
* Detect when a user drags a file in or out of the dropzone to change the styles
|
||||
*/
|
||||
let isDragging = false
|
||||
const handleDragEnter: () => void = () => (isDragging = true)
|
||||
const handleDragLeave: () => void = () => (isDragging = false)
|
||||
|
||||
/**
|
||||
* Process files being dropped in the drop zone and ensure they are images
|
||||
* @param event
|
||||
*/
|
||||
const handleDrop: (event: DragEvent) => Promise<void> = async event => {
|
||||
// Prevent default behavior (Prevent file from being opened)
|
||||
event.preventDefault()
|
||||
|
||||
const files = Array.from(event.dataTransfer.items)
|
||||
|
||||
// Iterate over the dropped files and upload them to WNFS
|
||||
await Promise.all(
|
||||
files.map(async item => {
|
||||
if (item.kind === 'file') {
|
||||
const file: File = item.getAsFile()
|
||||
|
||||
// If the dropped files aren't images, we don't want them!
|
||||
if (!file.type.match('image/*')) {
|
||||
addNotification('Please upload images only', 'error')
|
||||
console.error('Please upload images only')
|
||||
} else {
|
||||
await uploadImageToWNFS(file)
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
// Refetch images and update galleryStore
|
||||
await getImagesFromWNFS()
|
||||
|
||||
// Disable isDragging state
|
||||
isDragging = false
|
||||
}
|
||||
|
||||
/**
|
||||
* This is needed to prevent the default behaviour of the file opening in browser
|
||||
* when it is dropped
|
||||
* @param event
|
||||
*/
|
||||
const handleDragOver: (event: DragEvent) => void = event =>
|
||||
event.preventDefault()
|
||||
</script>
|
||||
|
||||
<label
|
||||
on:drop={handleDrop}
|
||||
on:dragover={handleDragOver}
|
||||
on:dragenter={handleDragEnter}
|
||||
on:dragleave={handleDragLeave}
|
||||
for="dropzone-file"
|
||||
class="block w-full min-h-[calc(100vh-190px)] rounded-lg border-2 border-solid border-base-content transition ease-in cursor-pointer {isDragging
|
||||
? 'border-dashed !border-orange-700 bg-orange-50'
|
||||
: ''}"
|
||||
>
|
||||
<slot />
|
||||
</label>
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<script lang="ts">
|
||||
import { galleryStore } from '$routes/gallery/stores'
|
||||
import { handleFileInput } from '$routes/gallery/lib/gallery'
|
||||
import FileUploadIcon from '$routes/gallery/components/icons/FileUploadIcon.svelte'
|
||||
|
||||
// Handle files uploaded directly through the file input
|
||||
let files: FileList
|
||||
$: if (files) {
|
||||
handleFileInput(files)
|
||||
}
|
||||
</script>
|
||||
|
||||
<label
|
||||
for="upload-file"
|
||||
class="group btn !p-0 !h-auto flex flex-col justify-center items-center aspect-[22/23] object-cover rounded-lg shadow-orange hover:border-neutral-50 overflow-hidden transition-colors ease-in bg-base-100 border-2 box-content border-neutral cursor-pointer text-neutral bg-gradient-to-r from-orange-600 to-orange-300"
|
||||
>
|
||||
{#if $galleryStore.loading}
|
||||
<div class="flex justify-center items-center p-12">
|
||||
<div
|
||||
class="loader ease-linear rounded-full border-4 border-t-4 border-t-orange-300 border-neutral h-16 w-16 animate-spin"
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-col justify-center items-center pt-5 pb-6">
|
||||
<FileUploadIcon />
|
||||
<p class="mt-4 mb-2 text-sm">
|
||||
<span class="font-bold text-sm">Upload a photo</span>
|
||||
</p>
|
||||
<p class="text-xxs">SVG, PNG, JPG or GIF</p>
|
||||
</div>
|
||||
<input
|
||||
bind:files
|
||||
id="upload-file"
|
||||
type="file"
|
||||
multiple
|
||||
accept="image/*"
|
||||
class="hidden"
|
||||
/>
|
||||
{/if}
|
||||
</label>
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
{
|
||||
"entities": [{
|
||||
"label": "Organization",
|
||||
"title": "Neuralink"
|
||||
}, {
|
||||
"label": "Organization",
|
||||
"title": "SpaceX"
|
||||
}, {
|
||||
"label": "Organization",
|
||||
"title": "Pretoria"
|
||||
}, {
|
||||
"label": "Organization",
|
||||
"title": "The Boring Company"
|
||||
}, {
|
||||
"label": "Organization",
|
||||
"title": "University of Pretoria"
|
||||
}, {
|
||||
"label": "Organization",
|
||||
"title": "Stanford University"
|
||||
}, {
|
||||
"label": "Person",
|
||||
"title": "Jeff Bezos"
|
||||
}, {
|
||||
"label": "Organization",
|
||||
"title": "University of Pennsylvania"
|
||||
}, {
|
||||
"label": "Person",
|
||||
"title": "Kimbal Musk"
|
||||
}, {
|
||||
"label": "Organization",
|
||||
"title": "Tesla, Inc."
|
||||
}, {
|
||||
"label": "Person",
|
||||
"title": "Elon Musk"
|
||||
}],
|
||||
"relations": [{
|
||||
"source": "Elon Musk",
|
||||
"target": "Neuralink"
|
||||
}, {
|
||||
"source": "Tesla, Inc.",
|
||||
"target": "Elon Musk",
|
||||
"type": "owned by"
|
||||
}, {
|
||||
"source": "Elon Musk",
|
||||
"target": "University of Pennsylvania",
|
||||
"type": "residence"
|
||||
}, {
|
||||
"source": "Elon Musk",
|
||||
"target": "Tesla, Inc.",
|
||||
"type": "owned by"
|
||||
}, {
|
||||
"source": "The Boring Company",
|
||||
"target": "Tesla, Inc.",
|
||||
"type": "owned by"
|
||||
}, {
|
||||
"source": "Elon Musk",
|
||||
"target": "Kimbal Musk",
|
||||
"type": "sibling"
|
||||
}, {
|
||||
"source": "University of Pennsylvania",
|
||||
"target": "Elon Musk",
|
||||
"type": "residence"
|
||||
}, {
|
||||
"source": "The Boring Company",
|
||||
"target": "Neuralink",
|
||||
"type": "subsidiary"
|
||||
}, {
|
||||
"source": "Elon Musk",
|
||||
"target": "University of Pretoria",
|
||||
"type": "work location"
|
||||
}, {
|
||||
"source": "The Boring Company",
|
||||
"target": "Elon Musk",
|
||||
"type": "owned by"
|
||||
}, {
|
||||
"source": "Kimbal Musk",
|
||||
"target": "Elon Musk",
|
||||
"type": "sibling"
|
||||
}, {
|
||||
"source": "Neuralink",
|
||||
"target": "Elon Musk",
|
||||
"type": "owned by"
|
||||
}, {
|
||||
"source": "Elon Musk",
|
||||
"target": "The Boring Company",
|
||||
"type": "owned by"
|
||||
}, {
|
||||
"source": "Elon Musk",
|
||||
"target": "University of Pennsylvania",
|
||||
"type": "work location"
|
||||
}]
|
||||
}
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
import { get as getStore } from 'svelte/store'
|
||||
import * as wn from 'webnative'
|
||||
import * as uint8arrays from 'uint8arrays'
|
||||
import type { CID } from 'multiformats/cid'
|
||||
import type { PuttableUnixTree, File as WNFile } from 'webnative/fs/types'
|
||||
import type { Metadata } from 'webnative/fs/metadata'
|
||||
|
||||
import { filesystemStore } from '$src/stores'
|
||||
import { AREAS, galleryStore } from '$routes/gallery/stores'
|
||||
import { addNotification } from '$lib/notifications'
|
||||
|
||||
export type Image = {
|
||||
cid: string
|
||||
ctime: number
|
||||
name: string
|
||||
private: boolean
|
||||
size: number
|
||||
src: string
|
||||
}
|
||||
|
||||
export type Gallery = {
|
||||
publicImages: Image[] | null
|
||||
privateImages: Image[] | null
|
||||
selectedArea: AREAS
|
||||
loading: boolean
|
||||
}
|
||||
|
||||
interface GalleryFile extends PuttableUnixTree, WNFile {
|
||||
cid: CID
|
||||
content: Uint8Array
|
||||
header: {
|
||||
content: Uint8Array
|
||||
metadata: Metadata
|
||||
}
|
||||
}
|
||||
|
||||
type Link = {
|
||||
size: number
|
||||
}
|
||||
|
||||
export const GALLERY_DIRS = {
|
||||
[AREAS.PUBLIC]: ['public', 'gallery'],
|
||||
[AREAS.PRIVATE]: ['private', 'gallery']
|
||||
}
|
||||
const FILE_SIZE_LIMIT = 5
|
||||
|
||||
/**
|
||||
* Get images from the user's WNFS and construct the `src` value for the images
|
||||
*/
|
||||
export const getImagesFromWNFS: () => Promise<void> = async () => {
|
||||
try {
|
||||
// Set loading: true on the galleryStore
|
||||
galleryStore.update(store => ({ ...store, loading: true }))
|
||||
|
||||
const { selectedArea } = getStore(galleryStore)
|
||||
const isPrivate = selectedArea === AREAS.PRIVATE
|
||||
const fs = getStore(filesystemStore)
|
||||
|
||||
// Set path to either private or public gallery dir
|
||||
const path = wn.path.directory(...GALLERY_DIRS[selectedArea])
|
||||
|
||||
// Get list of links for files in the gallery dir
|
||||
const links = await fs.ls(path)
|
||||
|
||||
const images = await Promise.all(
|
||||
Object.entries(links).map(async ([name]) => {
|
||||
const file = await fs.get(
|
||||
wn.path.file(...GALLERY_DIRS[selectedArea], `${name}`)
|
||||
)
|
||||
|
||||
// The CID for private files is currently located in `file.header.content`,
|
||||
// whereas the CID for public files is located in `file.cid`
|
||||
const cid = isPrivate
|
||||
? (file as GalleryFile).header.content.toString()
|
||||
: (file as GalleryFile).cid.toString()
|
||||
|
||||
// Create a base64 string to use as the image `src`
|
||||
const src = `data:image/jpeg;base64, ${uint8arrays.toString(
|
||||
(file as GalleryFile).content,
|
||||
'base64'
|
||||
)}`
|
||||
|
||||
return {
|
||||
cid,
|
||||
ctime: (file as GalleryFile).header.metadata.unixMeta.ctime,
|
||||
name,
|
||||
private: isPrivate,
|
||||
size: (links[name] as Link).size,
|
||||
src
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
// Sort images by ctime(created at date)
|
||||
// NOTE: this will eventually be controlled via the UI
|
||||
images.sort((a, b) => b.ctime - a.ctime)
|
||||
|
||||
// Push images to the galleryStore
|
||||
galleryStore.update(store => ({
|
||||
...store,
|
||||
...(isPrivate
|
||||
? {
|
||||
privateImages: images
|
||||
}
|
||||
: {
|
||||
publicImages: images
|
||||
}),
|
||||
loading: false
|
||||
}))
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
galleryStore.update(store => ({
|
||||
...store,
|
||||
loading: false
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload an image to the user's private or public WNFS
|
||||
* @param image
|
||||
*/
|
||||
export const uploadImageToWNFS: (
|
||||
image: File
|
||||
) => Promise<void> = async image => {
|
||||
try {
|
||||
const { selectedArea } = getStore(galleryStore)
|
||||
const fs = getStore(filesystemStore)
|
||||
|
||||
// Reject files over 5MB
|
||||
const imageSizeInMB = image.size / (1024 * 1024)
|
||||
if (imageSizeInMB > FILE_SIZE_LIMIT) {
|
||||
throw new Error('Image can be no larger than 5MB')
|
||||
}
|
||||
|
||||
// Reject the upload if the image already exists in the directory
|
||||
const imageExists = await fs.exists(
|
||||
wn.path.file(...GALLERY_DIRS[selectedArea], image.name)
|
||||
)
|
||||
if (imageExists) {
|
||||
throw new Error(`${image.name} image already exists`)
|
||||
}
|
||||
|
||||
// Create a sub directory and add some content
|
||||
await fs.write(
|
||||
wn.path.file(...GALLERY_DIRS[selectedArea], image.name),
|
||||
image
|
||||
)
|
||||
|
||||
// Announce the changes to the server
|
||||
await fs.publish()
|
||||
|
||||
addNotification(`${image.name} image has been published`, 'success')
|
||||
} catch (error) {
|
||||
addNotification(error.message, 'error')
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an image from the user's private or public WNFS
|
||||
* @param name
|
||||
*/
|
||||
export const deleteImageFromWNFS: (
|
||||
name: string
|
||||
) => Promise<void> = async name => {
|
||||
try {
|
||||
const { selectedArea } = getStore(galleryStore)
|
||||
const fs = getStore(filesystemStore)
|
||||
|
||||
const imageExists = await fs.exists(
|
||||
wn.path.file(...GALLERY_DIRS[selectedArea], name)
|
||||
)
|
||||
|
||||
if (imageExists) {
|
||||
// Remove images from server
|
||||
await fs.rm(wn.path.file(...GALLERY_DIRS[selectedArea], name))
|
||||
|
||||
// Announce the changes to the server
|
||||
await fs.publish()
|
||||
|
||||
addNotification(`${name} image has been deleted`, 'success')
|
||||
|
||||
// Refetch images and update galleryStore
|
||||
await getImagesFromWNFS()
|
||||
} else {
|
||||
throw new Error(`${name} image has already been deleted`)
|
||||
}
|
||||
} catch (error) {
|
||||
addNotification(error.message, 'error')
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle uploads made by interacting with the file input directly
|
||||
*/
|
||||
export const handleFileInput: (
|
||||
files: FileList
|
||||
) => Promise<void> = async files => {
|
||||
await Promise.all(
|
||||
Array.from(files).map(async file => {
|
||||
await uploadImageToWNFS(file)
|
||||
})
|
||||
)
|
||||
|
||||
// Refetch images and update galleryStore
|
||||
await getImagesFromWNFS()
|
||||
}
|
||||
|
|
@ -1,7 +1,13 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "node",
|
||||
"module": "es2020",
|
||||
"jsx": "react",
|
||||
"allowSyntheticDefaultImports": true, // that is probably the important one
|
||||
"esModuleInterop": true, // this also
|
||||
"typeRoots": [
|
||||
"./node_modules/@types/"
|
||||
],
|
||||
"module": "esnext",
|
||||
"lib": ["es2020"],
|
||||
"target": "es2019",
|
||||
/**
|
||||
|
|
@ -16,7 +22,6 @@
|
|||
enable source maps by default.
|
||||
*/
|
||||
"sourceMap": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"baseUrl": ".",
|
||||
|
|
|
|||
Loading…
Reference in New Issue