add updating and deleting of porfile data with terminus and mermurations, and indexing with Meilisearch
This commit is contained in:
parent
16522fb46b
commit
56671bdae2
|
|
@ -1,40 +1,2 @@
|
|||
import csv
|
||||
from itertools import islice
|
||||
from schema import ImpactArea, Blockchain, Topic, Web3, Organization
|
||||
from terminusdb_client import WOQLClient
|
||||
from datetime import datetime
|
||||
import pytz
|
||||
import re
|
||||
import emoji
|
||||
|
||||
# we keep all the information in dictionaries with Employee id as keys
|
||||
orgs = {}
|
||||
|
||||
client = WOQLClient("https://cloud.terminusdb.com/Myseelia/")
|
||||
client.connect(db="playground3", team="Myseelia", use_token=True)
|
||||
|
||||
import re
|
||||
|
||||
|
||||
orgs[0] = Organization(
|
||||
# assignee = "",
|
||||
# blockchainecosystem = set(),
|
||||
# description = "",
|
||||
# logo = "",
|
||||
name = "darren"
|
||||
# preJan20thUpvotes = 0,
|
||||
# reviewed = "",
|
||||
# submittedbyemail = "",
|
||||
# submittedbyname = "",
|
||||
# submittedbyowner = "",
|
||||
# subscribed = "",
|
||||
# topic = set(),
|
||||
# upvotes = 0,
|
||||
# web3 = set(),
|
||||
# impactarea = set(),
|
||||
# datecreated = datetime.min
|
||||
)
|
||||
|
||||
client.insert_document(list(orgs.values()), commit_msg="Adding 4 orgs")
|
||||
|
||||
|
||||
[{'@id': 'Organization/85405b13178f8e090cbcd58fd22111c0f72f77b59cbe00bb389ecd04c41d0e90', '@type': 'Organization', 'assignee': 'https://2local.io/', 'blockchainecosystem': ['BinanceSmartChain'], 'datecreated': '2022-05-07T11:03:00Z', 'description': '2local loyalty platform with the goal to achieve a sustainable world with prosperity for all. The cashback system supports sustainable and local-to-local working businesses. This cashback is generated from the profit from Yield Farms and Staking Pools. 2local doesn’t profit from its users but creates value with its users.', 'impactarea': ['SocialJustice'], 'name': '2local.io',
|
||||
'preJan20thUpvotes': '4', 'reviewed': 'checked', 'topic': ['Marketplace'], 'upvotes': '4'}, {'@id': 'Organization/f2dd1e11327e68d3ca3550cdec338186032d4fc4de4e9924ff97a8260a549be7', '@type': 'Organization', 'assignee': 'https://www.acredaos.com/', 'blockchainecosystem': ['Ethereum'], 'datecreated': '2022-05-07T11:03:00Z', 'description': 'ACRE DAOs is a decentralized impact investment club and Web3 access portal to the community of ACRE Invest token holders.', 'impactarea': ['SocialJustice'], 'name': 'ACRE DAOs\n', 'preJan20thUpvotes': '2', 'reviewed': 'checked', 'topic': ['Investing', 'Land'], 'upvotes': '2', 'web3': ['DAO']}]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,290 @@
|
|||
<script lang="ts">
|
||||
import cytoscape from 'cytoscape'
|
||||
import { onMount } from 'svelte'
|
||||
import { bubble } from 'svelte/internal'
|
||||
import TerminusClient from '@terminusdb/terminusdb-client'
|
||||
import { MeiliSearch } from 'meilisearch'
|
||||
import { generateKnowledgeGraph } from './cytoscape.ts'
|
||||
|
||||
let cy
|
||||
|
||||
interface INodeData {
|
||||
id: string
|
||||
}
|
||||
|
||||
interface INode {
|
||||
data: INodeData
|
||||
}
|
||||
|
||||
interface IEdgeData {
|
||||
id: string
|
||||
source: string
|
||||
target: string
|
||||
label: string
|
||||
}
|
||||
|
||||
interface IEdge {
|
||||
data: IEdgeData
|
||||
}
|
||||
|
||||
import json_graph from './knowledge_graph.json'
|
||||
|
||||
let knowledgeGraphJson: any = json_graph
|
||||
|
||||
// knowledgeGraphJson = await response.json()
|
||||
// } else {
|
||||
// alert(`HTTP-Error: ${response.status}`)
|
||||
// }
|
||||
// }
|
||||
|
||||
let nodes: INode[] = []
|
||||
let edges: IEdge[] = []
|
||||
|
||||
onMount(async () => {
|
||||
nodes = knowledgeGraphJson.entities.map((entity: any) => ({
|
||||
data: { id: entity.id, label: entity.label }
|
||||
}))
|
||||
|
||||
edges = knowledgeGraphJson.relations.map(
|
||||
(relation: any, index: string) => ({
|
||||
data: {
|
||||
id: index,
|
||||
source: relation.source,
|
||||
target: relation.target,
|
||||
label: relation.type
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
cy = cytoscape({
|
||||
container: document.getElementById('cy'),
|
||||
elements: {
|
||||
nodes,
|
||||
edges
|
||||
},
|
||||
style: [
|
||||
{
|
||||
selector: 'node',
|
||||
style: {
|
||||
'text-valign': 'center',
|
||||
'text-halign': 'center',
|
||||
'text-wrap': 'wrap',
|
||||
'text-max-width': function (ele) {
|
||||
return Math.max(1, Math.ceil(ele.degree() / 2)) * 30
|
||||
},
|
||||
'font-size': function (ele) {
|
||||
return Math.max(1, Math.ceil(ele.degree() / 2)) * 6
|
||||
},
|
||||
'background-color': '#75f6df',
|
||||
'border-color': '#223152',
|
||||
'border-width': function (ele) {
|
||||
return Math.max(1, Math.ceil(ele.degree() / 2))
|
||||
},
|
||||
label: 'data(label)',
|
||||
width: function (ele) {
|
||||
return Math.max(1, Math.ceil(ele.degree() / 2)) * 40
|
||||
},
|
||||
height: function (ele) {
|
||||
return Math.max(1, Math.ceil(ele.degree() / 2)) * 40
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: 'edge',
|
||||
style: {
|
||||
'font-size': 20,
|
||||
width: 5,
|
||||
'line-color': '#223152',
|
||||
'target-arrow-color': '#223152',
|
||||
'target-arrow-shape': 'triangle',
|
||||
'curve-style': 'bezier',
|
||||
'text-rotation': 'autorotate',
|
||||
'text-offset': { x: 20, y: -20 },
|
||||
'text-background-opacity': 1,
|
||||
'text-background-color': '#fafafa',
|
||||
'text-background-shape': 'roundrectangle',
|
||||
label: 'data(label)'
|
||||
}
|
||||
}
|
||||
],
|
||||
layout: {
|
||||
name: 'cose'
|
||||
// infinite: true,
|
||||
}
|
||||
})
|
||||
|
||||
cy.nodes().forEach(function (node) {
|
||||
node.data({
|
||||
degree: node.connectedEdges().length
|
||||
})
|
||||
})
|
||||
|
||||
var nodes = cy.nodes()
|
||||
nodes = nodes.sort(function (a, b) {
|
||||
return b.data('degree') - a.data('degree')
|
||||
})
|
||||
|
||||
var top100 = nodes.slice(0, 1000)
|
||||
|
||||
//console.log(top100)
|
||||
|
||||
cy.nodes().forEach(function (node) {
|
||||
if (!top100.includes(node)) {
|
||||
node.hide()
|
||||
}
|
||||
})
|
||||
|
||||
let toggle = true
|
||||
|
||||
// cy.off('tap', 'node', event => {
|
||||
// const node = event.target;
|
||||
// const nodeId = node.data('id');
|
||||
// alert('unDisplay info for ' + nodeId);
|
||||
// });
|
||||
|
||||
cy.on('tap', 'node', function (evt) {
|
||||
var node = evt.target
|
||||
var connectedEdges = node.connectedEdges()
|
||||
var connectedNodes = node.neighborhood().nodes()
|
||||
var allElements = cy.elements()
|
||||
var allNodes = cy.nodes()
|
||||
var allEdges = cy.edges()
|
||||
|
||||
if (node.style('display') == 'element') {
|
||||
// hide all nodes and edges except the selected node and its neighbors
|
||||
allNodes.style('display', 'none')
|
||||
allEdges.style('display', 'none')
|
||||
connectedNodes.style('display', 'element')
|
||||
node.style('display', 'element')
|
||||
connectedEdges.style('display', 'element')
|
||||
} else {
|
||||
// show all nodes and edges
|
||||
allNodes.style('display', 'element')
|
||||
allEdges.style('display', 'element')
|
||||
}
|
||||
})
|
||||
|
||||
// Reset the state when clicking away from the node
|
||||
cy.on('tap', function (e) {
|
||||
if (e.target === cy) {
|
||||
cy.nodes().style('display', 'element')
|
||||
cy.edges().style('display', 'element')
|
||||
cy.nodes().data('highlighted', false)
|
||||
}
|
||||
})
|
||||
|
||||
cy.on('tap', 'edge', event => {
|
||||
const edge = event.target
|
||||
const edgeId = edge.data('id')
|
||||
alert('Display info for ' + edgeId)
|
||||
})
|
||||
|
||||
// cy.on('tap', 'node', function(){
|
||||
// alert("put code here"));
|
||||
// });
|
||||
|
||||
// cy.layout({
|
||||
// name: 'cola'
|
||||
// }).run();
|
||||
})
|
||||
|
||||
var searchTerm = ''
|
||||
function updateSearchTerm(e) {
|
||||
searchTerm = e.target.value
|
||||
// Perform search in real timebased on searchTerm here
|
||||
}
|
||||
|
||||
async function entered(e) {
|
||||
const searchclient = new MeiliSearch({
|
||||
host: 'https://ms-9ea4a96f02a8-1969.sfo.meilisearch.io',
|
||||
apiKey: '117c691a34b21a6651798479ebffd181eb276958'
|
||||
})
|
||||
const index = searchclient.index('people')
|
||||
// this will search both keys and values
|
||||
// const search = await index.search(e.target.value.toString(), { q: '*' });
|
||||
// const searchResult = await index.search('orgs', {
|
||||
// attributesToRetrieve: ['id']
|
||||
// })
|
||||
const searchResult = await index.search(e.target.value.toString())
|
||||
// need to turn the search results into an array of ids which can be used to query the knowledge graph
|
||||
const resultsgraph = await generateKnowledgeGraph(searchResult.hits).then(
|
||||
resultsgraph => {
|
||||
// console.log(resultsgraph)
|
||||
const allNodes = resultsgraph.entities.map((entity: any) => ({
|
||||
data: { id: entity.id, label: entity.label }
|
||||
}))
|
||||
|
||||
const allEdges = resultsgraph.relations.map(
|
||||
(relation: any, index: string) => ({
|
||||
data: {
|
||||
id: index,
|
||||
source: relation.source,
|
||||
target: relation.target,
|
||||
label: relation.type
|
||||
}
|
||||
})
|
||||
)
|
||||
cy.remove(cy.elements())
|
||||
cy.add(allNodes)
|
||||
cy.add(allEdges)
|
||||
cy.layout({
|
||||
name: 'cose'
|
||||
// other layout options here
|
||||
}).run()
|
||||
}
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="pt-8 p-6 md:p-8 mx-auto">
|
||||
<input
|
||||
id="search"
|
||||
type="text"
|
||||
placeholder="Search..."
|
||||
on:input={updateSearchTerm}
|
||||
on:keydown={event => {
|
||||
if (event.keyCode === 13) {
|
||||
entered(event)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<section class="overflow-hidden text-gray-700">
|
||||
<div class="cyDiv" />
|
||||
|
||||
<div id="cy" />
|
||||
</section>
|
||||
|
||||
<style>
|
||||
#search {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
z-index: 100;
|
||||
background-color: white;
|
||||
width: 50%;
|
||||
height: 40px;
|
||||
border-radius: 20px;
|
||||
padding: 10px 20px 10px 40px;
|
||||
}
|
||||
|
||||
#search input[type='text'] {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 50px;
|
||||
width: 80%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
font-size: 18px;
|
||||
}
|
||||
#cy-div {
|
||||
z-index: 99;
|
||||
}
|
||||
#cy {
|
||||
width: 100%;
|
||||
height: 95%;
|
||||
position: absolute;
|
||||
top: 55px;
|
||||
left: 0px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -41,20 +41,6 @@
|
|||
let edges: IEdge[] = []
|
||||
|
||||
onMount(async () => {
|
||||
nodes = knowledgeGraphJson.entities.map((entity: any) => ({
|
||||
data: { id: entity.id, label: entity.label }
|
||||
}))
|
||||
|
||||
edges = knowledgeGraphJson.relations.map(
|
||||
(relation: any, index: string) => ({
|
||||
data: {
|
||||
id: index,
|
||||
source: relation.source,
|
||||
target: relation.target,
|
||||
label: relation.type
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
cy = cytoscape({
|
||||
container: document.getElementById('cy'),
|
||||
|
|
@ -113,6 +99,51 @@
|
|||
}
|
||||
})
|
||||
|
||||
const searchclient = new MeiliSearch({
|
||||
host: 'https://ms-9ea4a96f02a8-1969.sfo.meilisearch.io',
|
||||
apiKey: '117c691a34b21a6651798479ebffd181eb276958'
|
||||
})
|
||||
const index = searchclient.index('people')
|
||||
// this will search both keys and values
|
||||
// const search = await index.search(e.target.value.toString(), { q: '*' });
|
||||
// const searchResult = await index.search('orgs', {
|
||||
// attributesToRetrieve: ['id']
|
||||
// })
|
||||
const searchResult = await index.getDocuments(
|
||||
{
|
||||
limit: 1000
|
||||
}
|
||||
)
|
||||
console.log(searchResult)
|
||||
// need to turn the search results into an array of ids which can be used to query the knowledge graph
|
||||
const resultsgraph = await generateKnowledgeGraph(searchResult.results).then(
|
||||
resultsgraph => {
|
||||
|
||||
console.log(resultsgraph)
|
||||
const allNodes = resultsgraph.entities.map((entity: any) => ({
|
||||
data: { id: entity.id, label: entity.label }
|
||||
}))
|
||||
|
||||
const allEdges = resultsgraph.relations.map(
|
||||
(relation: any, index: string) => ({
|
||||
data: {
|
||||
id: index,
|
||||
source: relation.source,
|
||||
target: relation.target,
|
||||
label: relation.type
|
||||
}
|
||||
})
|
||||
)
|
||||
cy.remove(cy.elements())
|
||||
cy.add(allNodes)
|
||||
cy.add(allEdges)
|
||||
cy.layout({
|
||||
name: 'cose'
|
||||
// other layout options here
|
||||
}).run()
|
||||
}
|
||||
)
|
||||
|
||||
cy.nodes().forEach(function (node) {
|
||||
node.data({
|
||||
degree: node.connectedEdges().length
|
||||
|
|
|
|||
|
|
@ -5,17 +5,24 @@ import * as fs from 'fs'
|
|||
|
||||
const WOQL = TerminusClient.WOQL
|
||||
|
||||
function findNameById(ids: object[], id: string): string {
|
||||
console.log('finding id ' + id);
|
||||
const document = ids.find(doc => 'person/' + doc['id'] === id);
|
||||
console.log('found name ' + JSON.parse(document['name']));
|
||||
return document ? JSON.parse(document['name']) : '';
|
||||
}
|
||||
|
||||
export async function generateKnowledgeGraph(ids: object[]): Promise<object> {
|
||||
console.log(ids)
|
||||
const entities: { id: string; label: string; type: string }[] = []
|
||||
const relations: { source: string; target: string; type: string }[] = []
|
||||
|
||||
for (const document of ids) {
|
||||
const personid = 'Person/' + document['id']
|
||||
const personid = 'person/' + document['id']
|
||||
let personEntity = entities.find(entity => entity.id === personid)
|
||||
if (!personEntity) {
|
||||
entities.push({
|
||||
id: personid,
|
||||
label: document['name'],
|
||||
label: typeof document['name'] === 'string' && document['name'].startsWith('"') && document['name'].endsWith('"') ? JSON.parse(document['name']) : document['name'],
|
||||
type: 'person'
|
||||
})
|
||||
personEntity = entities[entities.length - 1]
|
||||
|
|
@ -23,25 +30,27 @@ export async function generateKnowledgeGraph(ids: object[]): Promise<object> {
|
|||
|
||||
const linktypes = ['vouches_for', 'LI']
|
||||
for (const link of linktypes) {
|
||||
let linkValues = document[link]
|
||||
try {
|
||||
linkValues = JSON.parse(linkValues)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
let linkValues = document[link];
|
||||
if (typeof linkValues === 'string') {
|
||||
try {
|
||||
linkValues = JSON.parse(linkValues);
|
||||
} catch (error) {
|
||||
console.error(`Error parsing JSON for link "${link}":`, error);
|
||||
}
|
||||
}
|
||||
if (Array.isArray(linkValues)) {
|
||||
for (const linkValue of linkValues) {
|
||||
if (linkValue === undefined) continue
|
||||
const linkId = linkValue.replace(/^"|"$/g, '')
|
||||
if (linkId !== undefined && linkId !== '') {
|
||||
if (linkId !== undefined && linkId !== '' && linkId.startsWith('person/')) {
|
||||
let linkEntity = entities.find(
|
||||
entity => entity.id === linkId
|
||||
)
|
||||
if (!linkEntity) {
|
||||
entities.push({
|
||||
id: linkId,
|
||||
label: linkValue,
|
||||
type: 'attribute'
|
||||
label: findNameById(ids, linkId),
|
||||
type: 'person'
|
||||
})
|
||||
linkEntity = entities[entities.length - 1]
|
||||
}
|
||||
|
|
@ -60,8 +69,7 @@ export async function generateKnowledgeGraph(ids: object[]): Promise<object> {
|
|||
}
|
||||
} else {
|
||||
let linkId = linkValues
|
||||
// console.log(linkId)
|
||||
if (linkId !== undefined && linkId !== '') {
|
||||
if (linkId !== undefined && linkId !== '' && linkId.startsWith('person/')) {
|
||||
linkId = linkId.replace(/^"|"$/g, '')
|
||||
let linkEntity = entities.find(
|
||||
entity => entity.id === linkId
|
||||
|
|
@ -70,7 +78,7 @@ export async function generateKnowledgeGraph(ids: object[]): Promise<object> {
|
|||
entities.push({
|
||||
id: linkId,
|
||||
label: document[link],
|
||||
type: 'attribute'
|
||||
type: 'person'
|
||||
})
|
||||
linkEntity = entities[entities.length - 1]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -256,6 +256,7 @@
|
|||
top: 10px;
|
||||
z-index: 100;
|
||||
background-color: white;
|
||||
color: black;
|
||||
width: 50%;
|
||||
height: 40px;
|
||||
border-radius: 20px;
|
||||
|
|
|
|||
|
|
@ -5,10 +5,11 @@
|
|||
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 { getJSONFromWNFS, 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'
|
||||
import { ipfsGatewayUrl } from '$lib/app-info';
|
||||
|
||||
const client = new TerminusClient.WOQLClient(
|
||||
"https://cloud.terminusdb.com/Myseelia",{
|
||||
|
|
@ -42,7 +43,7 @@
|
|||
|
||||
const clearSelectedImage = () => (selectedImage = null)
|
||||
|
||||
// If galleryStore.selectedArea changes from private to public, re-run getImagesFromWNFS
|
||||
// If galleryStore.selectedArea changes from private to public, re-run getJSONFromWNFS
|
||||
let selectedArea = null
|
||||
const unsubscribeGalleryStore = galleryStore.subscribe(async updatedStore => {
|
||||
// Get initial selectedArea
|
||||
|
|
@ -52,7 +53,7 @@
|
|||
|
||||
if (selectedArea !== updatedStore.selectedArea) {
|
||||
selectedArea = updatedStore.selectedArea
|
||||
await getImagesFromWNFS()
|
||||
await getJSONFromWNFS()
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -62,7 +63,7 @@
|
|||
if (newState.authed && $filesystemStore && !imagesFetched) {
|
||||
imagesFetched = true
|
||||
// Get images from the user's public WNFS
|
||||
getImagesFromWNFS()
|
||||
getJSONFromWNFS()
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -162,6 +163,13 @@ button{
|
|||
<br />
|
||||
<button class="bg-blue-500 text-white dark:text-black" type="submit">Submit</button>
|
||||
</form>
|
||||
<!-- <a
|
||||
href={`https://ipfs.${ipfsGatewayUrl}/ipfs/${image.cid}/userland`}
|
||||
target="_blank"
|
||||
class="underline mb-4 hover:text-slate-500"
|
||||
>
|
||||
View on IPFS
|
||||
</a> -->
|
||||
</div>
|
||||
|
||||
{#if selectedImage}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
import { filesystemStore, sessionStore } from '$src/stores'
|
||||
import { AREAS, galleryStore } from '$routes/gallery/stores'
|
||||
import { getImagesFromWNFS, type Image } from '$routes/gallery/lib/gallery'
|
||||
import { getJSONFromWNFS, 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'
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
const setSelectedImage: (image: Image) => void = image =>
|
||||
(selectedImage = image)
|
||||
|
||||
// If galleryStore.selectedArea changes from private to public, re-run getImagesFromWNFS
|
||||
// If galleryStore.selectedArea changes from private to public, re-run getJSONFromWNFS
|
||||
let selectedArea = null
|
||||
const unsubscribeGalleryStore = galleryStore.subscribe(async updatedStore => {
|
||||
// Get initial selectedArea
|
||||
|
|
@ -36,7 +36,7 @@
|
|||
if (newState.authed && $filesystemStore && !imagesFetched) {
|
||||
imagesFetched = true
|
||||
// Get images from the user's public WNFS
|
||||
getImagesFromWNFS()
|
||||
getJSONFromWNFS()
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import {
|
||||
getImagesFromWNFS,
|
||||
uploadImageToWNFS
|
||||
getJSONFromWNFS,
|
||||
uploadJSONToWNFS
|
||||
} from '$routes/gallery/lib/gallery'
|
||||
import { addNotification } from '$lib/notifications'
|
||||
|
||||
|
|
@ -33,14 +33,14 @@
|
|||
addNotification('Please upload images only', 'error')
|
||||
console.error('Please upload images only')
|
||||
} else {
|
||||
await uploadImageToWNFS(file)
|
||||
await uploadJSONToWNFS(file)
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
// Refetch images and update galleryStore
|
||||
await getImagesFromWNFS()
|
||||
await getJSONFromWNFS()
|
||||
|
||||
// Disable isDragging state
|
||||
isDragging = false
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ 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 () => {
|
||||
export const getJSONFromWNFS: () => Promise<void> = async () => {
|
||||
try {
|
||||
// Set loading: true on the galleryStore
|
||||
galleryStore.update(store => ({ ...store, loading: true }))
|
||||
|
|
@ -182,7 +182,7 @@ export const deleteImageFromWNFS: (
|
|||
addNotification(`${name} image has been deleted`, 'success')
|
||||
|
||||
// Refetch images and update galleryStore
|
||||
await getImagesFromWNFS()
|
||||
await getJSONFromWNFS()
|
||||
} else {
|
||||
throw new Error(`${name} image has already been deleted`)
|
||||
}
|
||||
|
|
@ -205,5 +205,5 @@ export const handleFileInput: (
|
|||
)
|
||||
|
||||
// Refetch images and update galleryStore
|
||||
await getImagesFromWNFS()
|
||||
await getJSONFromWNFS()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
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 { getJSONFromWNFS, 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'
|
||||
|
|
@ -42,7 +42,7 @@
|
|||
|
||||
const clearSelectedImage = () => (selectedImage = null)
|
||||
|
||||
// If galleryStore.selectedArea changes from private to public, re-run getImagesFromWNFS
|
||||
// If galleryStore.selectedArea changes from private to public, re-run getJSONFromWNFS
|
||||
let selectedArea = null
|
||||
const unsubscribeGalleryStore = galleryStore.subscribe(async updatedStore => {
|
||||
// Get initial selectedArea
|
||||
|
|
@ -52,7 +52,7 @@
|
|||
|
||||
if (selectedArea !== updatedStore.selectedArea) {
|
||||
selectedArea = updatedStore.selectedArea
|
||||
await getImagesFromWNFS()
|
||||
await getJSONFromWNFS()
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -62,7 +62,7 @@
|
|||
if (newState.authed && $filesystemStore && !imagesFetched) {
|
||||
imagesFetched = true
|
||||
// Get images from the user's public WNFS
|
||||
getImagesFromWNFS()
|
||||
getJSONFromWNFS()
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
import { filesystemStore, sessionStore } from '$src/stores'
|
||||
import { AREAS, galleryStore } from '$routes/gallery/stores'
|
||||
import { getImagesFromWNFS, type Image } from '$routes/gallery/lib/gallery'
|
||||
import { getJSONFromWNFS, 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'
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
const setSelectedImage: (image: Image) => void = image =>
|
||||
(selectedImage = image)
|
||||
|
||||
// If galleryStore.selectedArea changes from private to public, re-run getImagesFromWNFS
|
||||
// If galleryStore.selectedArea changes from private to public, re-run getJSONFromWNFS
|
||||
let selectedArea = null
|
||||
const unsubscribeGalleryStore = galleryStore.subscribe(async updatedStore => {
|
||||
// Get initial selectedArea
|
||||
|
|
@ -36,7 +36,7 @@
|
|||
if (newState.authed && $filesystemStore && !imagesFetched) {
|
||||
imagesFetched = true
|
||||
// Get images from the user's public WNFS
|
||||
getImagesFromWNFS()
|
||||
getImagesFrgetJSONFromWNFSomWNFS()
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import {
|
||||
getImagesFromWNFS,
|
||||
uploadImageToWNFS
|
||||
getJSONFromWNFS,
|
||||
uploadJSONToWNFS
|
||||
} from '$routes/gallery/lib/gallery'
|
||||
import { addNotification } from '$lib/notifications'
|
||||
|
||||
|
|
@ -33,14 +33,14 @@
|
|||
addNotification('Please upload images only', 'error')
|
||||
console.error('Please upload images only')
|
||||
} else {
|
||||
await uploadImageToWNFS(file)
|
||||
await uploadJSONToWNFS(file)
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
// Refetch images and update galleryStore
|
||||
await getImagesFromWNFS()
|
||||
await getJSONFromWNFS()
|
||||
|
||||
// Disable isDragging state
|
||||
isDragging = false
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ 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 () => {
|
||||
export const getJSONFromWNFS: () => Promise<void> = async () => {
|
||||
try {
|
||||
// Set loading: true on the galleryStore
|
||||
galleryStore.update(store => ({ ...store, loading: true }))
|
||||
|
|
@ -182,7 +182,7 @@ export const deleteImageFromWNFS: (
|
|||
addNotification(`${name} image has been deleted`, 'success')
|
||||
|
||||
// Refetch images and update galleryStore
|
||||
await getImagesFromWNFS()
|
||||
await getJSONFromWNFS()
|
||||
} else {
|
||||
throw new Error(`${name} image has already been deleted`)
|
||||
}
|
||||
|
|
@ -205,5 +205,5 @@ export const handleFileInput: (
|
|||
)
|
||||
|
||||
// Refetch images and update galleryStore
|
||||
await getImagesFromWNFS()
|
||||
await getJSONFromWNFS()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,31 +5,75 @@
|
|||
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 { getJSONFromWNFS, uploadJSONToWNFS, deleteAllJSONFromWNFS, 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'
|
||||
import { ipfsGatewayUrl } from '$lib/app-info';
|
||||
import { addNotification } from '$lib/notifications'
|
||||
import { MeiliSearch } from 'meilisearch'
|
||||
|
||||
function extractPersonId(url: string): string {
|
||||
const personIdPattern = /person\/[a-zA-Z0-9]+/;
|
||||
const match = url.match(personIdPattern);
|
||||
|
||||
return match ? match[0] : null;
|
||||
}
|
||||
|
||||
interface Document {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
type KeyValuePair = [string, unknown];
|
||||
|
||||
const client = new TerminusClient.WOQLClient(
|
||||
"https://cloud.terminusdb.com/Myseelia",{
|
||||
user:"zaldarren@gmail.com",
|
||||
organization:"Myseelia",
|
||||
db: "Myseelia",
|
||||
db: "murmurations",
|
||||
token: "dGVybWludXNkYjovLy9kYXRhL2tleXNfYXBpLzg5OTY0ZGI5OWFlYjQ1Zjc5OGM5ZTRiZWI2MzExOGJhZjhiOWRiOWNlOTJiNmU2NGI0NDEzZjIzNDFmOGVkMjc=_869e9bd2465ad84126151962994fcfa22d4b7ec9375edf16b4182e7f36e4b2b820075ba22e78f629e0691eddbeae6998a6504d5ce287aa1df2602cb556b58e1730b0b93feb0e9304"
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
let username = $sessionStore.username;
|
||||
let bioregion = '';
|
||||
let ecozone = '';
|
||||
let affiliatedOrganizations = "Organization/8c8368b55dc80f18ba254771701f6d1bc79a3a90f127c28b3145a2c2204e97ce";
|
||||
let givenName = '';
|
||||
let Name = '';
|
||||
let hasCredential = {};
|
||||
|
||||
let cid
|
||||
let primary_url, profileImage, Description
|
||||
|
||||
onMount(async () => {
|
||||
await client.connect()
|
||||
const schema = await client.getSchema("myseelia", "main")
|
||||
// const schema = await client.getSchema("myseelia", "main")
|
||||
const queryTemplate = { "name": username }
|
||||
const tdbresult = await client.getDocument({"type":"person","as_list":true,"query":queryTemplate});
|
||||
console.log("Query Documents",tdbresult)
|
||||
if (tdbresult && tdbresult.length > 0){
|
||||
console.log("found documents")
|
||||
const doc = tdbresult[0]
|
||||
bioregion = doc.locality
|
||||
Name = doc.name
|
||||
primary_url = doc.primary_url
|
||||
profileImage = doc.image
|
||||
Description = doc.description
|
||||
} else {
|
||||
console.log("no documents found")
|
||||
}
|
||||
try {
|
||||
const jsonObjects = await getJSONFromWNFS()
|
||||
console.log("jsonObjects", jsonObjects)
|
||||
// Find the JSON object with the username "tester"
|
||||
const usernameJSONObject = jsonObjects.find(obj => obj.name === 'tester')
|
||||
|
||||
// Log the CID of the "username.json" file to the console
|
||||
cid = usernameJSONObject ? usernameJSONObject.cid : null
|
||||
console.log("found it", cid)
|
||||
} catch (error) {
|
||||
console.error("no files fond on WNFS")
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
@ -42,7 +86,7 @@
|
|||
|
||||
const clearSelectedImage = () => (selectedImage = null)
|
||||
|
||||
// If galleryStore.selectedArea changes from private to public, re-run getImagesFromWNFS
|
||||
// If galleryStore.selectedArea changes from private to public, re-run getJSONFromWNFS
|
||||
let selectedArea = null
|
||||
const unsubscribeGalleryStore = galleryStore.subscribe(async updatedStore => {
|
||||
// Get initial selectedArea
|
||||
|
|
@ -52,7 +96,7 @@
|
|||
|
||||
if (selectedArea !== updatedStore.selectedArea) {
|
||||
selectedArea = updatedStore.selectedArea
|
||||
await getImagesFromWNFS()
|
||||
await getJSONFromWNFS()
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -62,7 +106,7 @@
|
|||
if (newState.authed && $filesystemStore && !imagesFetched) {
|
||||
imagesFetched = true
|
||||
// Get images from the user's public WNFS
|
||||
getImagesFromWNFS()
|
||||
getJSONFromWNFS()
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -79,54 +123,146 @@
|
|||
|
||||
export async function makeConnection(){
|
||||
try{
|
||||
const entryObj =
|
||||
{
|
||||
"@type" : "Person",
|
||||
"userName" : username,
|
||||
"givenName" : givenName,
|
||||
"bioregion": bioregion,
|
||||
"ecozone": ecozone,
|
||||
"hasCredential": hasCredential,
|
||||
"affiliation": affiliatedOrganizations
|
||||
let entryObj: {
|
||||
"@type": string;
|
||||
name: string;
|
||||
locality?: string;
|
||||
image?: string;
|
||||
primary_url?: string;
|
||||
description?: string;
|
||||
} = {
|
||||
"@type": "person",
|
||||
name: username,
|
||||
...(bioregion && { locality: String(bioregion) }),
|
||||
...(profileImage && { image: String(profileImage) }),
|
||||
...(primary_url && { primary_url: String(primary_url) }),
|
||||
...(Description && { description: String(Description) })
|
||||
};
|
||||
|
||||
const murmurationSchema = {
|
||||
linked_schemas: ['person_schema-v0.1.0'],
|
||||
name: username,
|
||||
primary_url: primary_url,
|
||||
description: Description,
|
||||
image: profileImage,
|
||||
locality: bioregion,
|
||||
};
|
||||
if (username == entryObj.userName){
|
||||
await client.updateDocument(entryObj)
|
||||
|
||||
console.log(JSON.stringify(murmurationSchema));
|
||||
|
||||
//upload to IPFS
|
||||
await uploadJSONToWNFS(murmurationSchema)
|
||||
try {
|
||||
const jsonObjects = await getJSONFromWNFS()
|
||||
console.log(jsonObjects)
|
||||
// Find the JSON object with the username "tester"
|
||||
const usernameJSONObject = jsonObjects.find(obj => obj.name === 'tester')
|
||||
|
||||
// Log the CID of the "username.json" file to the console
|
||||
cid = usernameJSONObject ? usernameJSONObject.cid : null
|
||||
console.log("found it", cid)
|
||||
|
||||
const url = "https://test-index.murmurations.network/v2/nodes";
|
||||
const data = {
|
||||
profile_url: `https://ipfs.${ipfsGatewayUrl}/ipfs/${cid}/userland`,
|
||||
};
|
||||
console.log(data.profile_url)
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
} else {
|
||||
addNotification(`profile has been indexed on murmurations`, 'success')
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
console.log("result from murmurations indexing: " + JSON.stringify(result));
|
||||
} catch (error) {
|
||||
console.error("Error posting data:", error);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
|
||||
const queryTemplate = { "name": username }
|
||||
const tdbresult = await client.getDocument({"type":"person","as_list":true,"query":queryTemplate});
|
||||
console.log("Query Documents",tdbresult)
|
||||
const searchclient = new MeiliSearch({
|
||||
host: 'https://ms-9ea4a96f02a8-1969.sfo.meilisearch.io',
|
||||
apiKey: '117c691a34b21a6651798479ebffd181eb276958'
|
||||
})
|
||||
const index = searchclient.index('people')
|
||||
if (tdbresult && tdbresult.length > 0){
|
||||
await client.deleteDocument({id:tdbresult[0]["@id"]});
|
||||
// const searchResult = await index.search(`id="${tdbresult[0]["@id"]}"`);
|
||||
|
||||
// // Return the document if found, otherwise return null
|
||||
// return searchResult.hits.length > 0 ? searchResult.hits[0] : null;
|
||||
//update document
|
||||
console.log("updating")
|
||||
const result = await client.addDocument(entryObj);
|
||||
addNotification(`data has been added to knowledge graph`, 'success')
|
||||
console.log("added document", result)
|
||||
const addedID = extractPersonId(String(result))
|
||||
const queryTemplate = { "@id": addedID }
|
||||
const terminusperson = await client.getDocument({ type: 'person', as_list: true, query: queryTemplate });
|
||||
console.log("result ", terminusperson);
|
||||
const real_id: string = terminusperson[0]['@id'];
|
||||
const num_id: string = real_id.split('/').pop() || '';
|
||||
|
||||
// Create a new object without the '@id' key
|
||||
const newDocument: Document = Object.entries(terminusperson[0])
|
||||
.filter(([key]) => key !== '@id')
|
||||
.reduce<Document>((acc, [key, value]: KeyValuePair) => {
|
||||
acc[key] = value;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
// Update the document with the new 'id'
|
||||
newDocument['id'] = num_id;
|
||||
|
||||
const newDocumentsArray = [newDocument];
|
||||
|
||||
const searchResult = await index.addDocuments(newDocumentsArray);
|
||||
console.log("searchResult", searchResult)
|
||||
} else{
|
||||
await client.addDocument(entryObj);
|
||||
console.log('adding doc')
|
||||
const addedPerson = await client.addDocument(entryObj);
|
||||
console.log("added document", addedPerson)
|
||||
addNotification(`profile has been added to knowledge graph`, 'success')
|
||||
}
|
||||
const entries2 = await client.getDocument({"graph_type":"instance","as_list":true,"type":"Person"})
|
||||
const entries2 = await client.getDocument({"graph_type":"instance","as_list":true,"type":"person"})
|
||||
console.log(entries2);
|
||||
}catch(err){
|
||||
console.error(err.message)
|
||||
}
|
||||
}
|
||||
|
||||
const onDelete = async (e) => {
|
||||
e.preventDefault();
|
||||
deleteAllJSONFromWNFS();
|
||||
try {
|
||||
const jsonObjects = await getJSONFromWNFS()
|
||||
// Find the JSON object with the username "tester"
|
||||
const usernameJSONObject = jsonObjects.find(obj => obj.userName === 'tester')
|
||||
|
||||
// Log the CID of the "username.json" file to the console
|
||||
cid = usernameJSONObject ? usernameJSONObject.cid : null
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
</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
|
||||
|
|
@ -136,31 +272,87 @@ button{
|
|||
</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} />
|
||||
Name:
|
||||
<input
|
||||
class="input text-white dark:text-black"
|
||||
type="text"
|
||||
bind:value={username}
|
||||
readonly
|
||||
/>
|
||||
</label>
|
||||
<br />
|
||||
<label class="label dark:text-white">
|
||||
Bioregion:
|
||||
<input class="input text-white dark:text-black" type="text" bind:value={bioregion} />
|
||||
Image:
|
||||
<input
|
||||
class="input text-white dark:text-black"
|
||||
type="text"
|
||||
bind:value={profileImage}
|
||||
/>
|
||||
</label>
|
||||
<br />
|
||||
<label class="label dark:text-white">
|
||||
Bioregion / Locality:
|
||||
<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 >
|
||||
<input
|
||||
class="input text-white dark:text-black"
|
||||
type="text"
|
||||
bind:value={ecozone}
|
||||
/>
|
||||
</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 />
|
||||
<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}/>
|
||||
Primary URL:
|
||||
<input
|
||||
class="input text-white dark:text-black"
|
||||
type="text"
|
||||
bind:value={primary_url}
|
||||
/>
|
||||
</label>
|
||||
<br />
|
||||
<button class="bg-blue-500 text-white dark:text-black" type="submit">Submit</button>
|
||||
<label class="label dark:text-white">
|
||||
Description:
|
||||
<input
|
||||
class="input text-white dark:text-black"
|
||||
type="text"
|
||||
bind:value={Description}
|
||||
/>
|
||||
</label>
|
||||
<br />
|
||||
<button class="bg-blue-500 text-white dark:text-black" type="submit">
|
||||
Submit
|
||||
</button>
|
||||
<br />
|
||||
{#if cid !== null && cid !== undefined}
|
||||
<div class="my-2">
|
||||
<a
|
||||
href={`https://ipfs.${ipfsGatewayUrl}/ipfs/${cid}/userland`}
|
||||
target="_blank"
|
||||
class="underline mb-4 hover:text-slate-500 dark:text-white"
|
||||
title="Note, the data will be cached for a while on IPFS after deleting, Fission is not 'tombstoning' on their gateway and there is no 'hard delete' on IPFS."
|
||||
>
|
||||
View on IPFS
|
||||
</a>
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="delete" on:click={onDelete}>Delete</button>
|
||||
{/if}
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
|
@ -172,3 +364,37 @@ button{
|
|||
/>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<style>
|
||||
form {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 3fr;
|
||||
}
|
||||
|
||||
label {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
input {
|
||||
background-color: rgb(255, 255, 255);
|
||||
border-radius: 4px;
|
||||
color: rgb(0, 0, 0);
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #4caf50; /* Green */
|
||||
padding: 12px 20px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
}
|
||||
.delete {
|
||||
background-color: red;
|
||||
padding: 12px 20px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
import { filesystemStore, sessionStore } from '$src/stores'
|
||||
import { AREAS, galleryStore } from '$routes/gallery/stores'
|
||||
import { getImagesFromWNFS, type Image } from '$routes/gallery/lib/gallery'
|
||||
import { getJSONFromWNFS, 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'
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
const setSelectedImage: (image: Image) => void = image =>
|
||||
(selectedImage = image)
|
||||
|
||||
// If galleryStore.selectedArea changes from private to public, re-run getImagesFromWNFS
|
||||
// If galleryStore.selectedArea changes from private to public, re-run getImaggetJSONFromWNFSesFromWNFS
|
||||
let selectedArea = null
|
||||
const unsubscribeGalleryStore = galleryStore.subscribe(async updatedStore => {
|
||||
// Get initial selectedArea
|
||||
|
|
@ -36,7 +36,7 @@
|
|||
if (newState.authed && $filesystemStore && !imagesFetched) {
|
||||
imagesFetched = true
|
||||
// Get images from the user's public WNFS
|
||||
getImagesFromWNFS()
|
||||
getJSONFromWNFS()
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import {
|
||||
getImagesFromWNFS,
|
||||
uploadImageToWNFS
|
||||
getJSONFromWNFS,
|
||||
uploadJSONToWNFS
|
||||
} from '$routes/gallery/lib/gallery'
|
||||
import { addNotification } from '$lib/notifications'
|
||||
|
||||
|
|
@ -33,14 +33,14 @@
|
|||
addNotification('Please upload images only', 'error')
|
||||
console.error('Please upload images only')
|
||||
} else {
|
||||
await uploadImageToWNFS(file)
|
||||
await uploadJSONToWNFS(file)
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
// Refetch images and update galleryStore
|
||||
await getImagesFromWNFS()
|
||||
await getJSONFromWNFS()
|
||||
|
||||
// Disable isDragging state
|
||||
isDragging = false
|
||||
|
|
|
|||
|
|
@ -1,14 +1,21 @@
|
|||
import { get as getStore } from 'svelte/store'
|
||||
import * as wn from 'webnative'
|
||||
import * as uint8arrays from 'uint8arrays'
|
||||
import type { CID } from 'multiformats/cid'
|
||||
import { 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 { filesystemStore, sessionStore } from '$src/stores'
|
||||
import { AREAS, galleryStore } from '$routes/gallery/stores'
|
||||
import { addNotification } from '$lib/notifications'
|
||||
|
||||
let username: string | null = null
|
||||
|
||||
// Subscribe to changes in the session store
|
||||
sessionStore.subscribe(session => {
|
||||
username = session.username
|
||||
})
|
||||
|
||||
export type Image = {
|
||||
cid: string
|
||||
ctime: number
|
||||
|
|
@ -45,112 +52,105 @@ export const GALLERY_DIRS = {
|
|||
const FILE_SIZE_LIMIT = 5
|
||||
|
||||
/**
|
||||
* Get images from the user's WNFS and construct the `src` value for the images
|
||||
* Get JSON objects from the user's WNFS and return them as an array
|
||||
*/
|
||||
export const getImagesFromWNFS: () => Promise<void> = async () => {
|
||||
export const getJSONFromWNFS: () => Promise<Array<Record<string, any>>> = 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
|
||||
// Set path to 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)
|
||||
console.log('links', links)
|
||||
|
||||
let cid
|
||||
Object.keys(links).forEach(name => {
|
||||
const link = links[name]
|
||||
console.log(link.cid.toString())
|
||||
if (link.toString() == 'tester.json') {
|
||||
cid = link.cid.toString()
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const images = await Promise.all(
|
||||
Object.entries(links).map(async ([name]) => {
|
||||
const jsonObjects = await Promise.all(
|
||||
Object.entries(links).map(async ([name, link]) => {
|
||||
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()
|
||||
// The content of the file is a JSON-formatted string
|
||||
const jsonString = uint8arrays.toString((file as GalleryFile).content, 'utf8')
|
||||
|
||||
// Create a base64 string to use as the image `src`
|
||||
const src = `data:image/jpeg;base64, ${uint8arrays.toString(
|
||||
(file as GalleryFile).content,
|
||||
'base64'
|
||||
)}`
|
||||
// Parse the JSON-formatted string into a JavaScript object
|
||||
const jsonObject = JSON.parse(jsonString)
|
||||
|
||||
return {
|
||||
cid,
|
||||
ctime: (file as GalleryFile).header.metadata.unixMeta.ctime,
|
||||
name,
|
||||
private: isPrivate,
|
||||
size: (links[name] as Link).size,
|
||||
src
|
||||
}
|
||||
// Get the CID for the current link and append it to the corresponding JSON object
|
||||
const cid = link.cid.toString()
|
||||
jsonObject.cid = cid
|
||||
console.log(jsonObject)
|
||||
|
||||
return jsonObject
|
||||
})
|
||||
)
|
||||
|
||||
// Sort images by ctime(created at date)
|
||||
// Sort JSON objects by ctime(created at date)
|
||||
// NOTE: this will eventually be controlled via the UI
|
||||
images.sort((a, b) => b.ctime - a.ctime)
|
||||
jsonObjects.sort((a, b) => b.ctime - a.ctime)
|
||||
|
||||
// Push images to the galleryStore
|
||||
galleryStore.update(store => ({
|
||||
...store,
|
||||
...(isPrivate
|
||||
? {
|
||||
privateImages: images
|
||||
}
|
||||
: {
|
||||
publicImages: images
|
||||
}),
|
||||
loading: false
|
||||
}))
|
||||
// Set loading: false and return the JSON objects
|
||||
galleryStore.update(store => ({ ...store, loading: false }))
|
||||
return jsonObjects
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
galleryStore.update(store => ({
|
||||
...store,
|
||||
loading: false
|
||||
}))
|
||||
galleryStore.update(store => ({ ...store, loading: false }))
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload an image to the user's private or public WNFS
|
||||
* @param image
|
||||
* Upload a JSON object to the user's private or public WNFS
|
||||
* @param json
|
||||
*/
|
||||
export const uploadImageToWNFS: (
|
||||
image: File
|
||||
) => Promise<void> = async image => {
|
||||
export const uploadJSONToWNFS: (
|
||||
json: Record<string, any>
|
||||
) => Promise<void> = async json => {
|
||||
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')
|
||||
// Convert the JSON object to a string
|
||||
const jsonString = JSON.stringify(json)
|
||||
|
||||
// Reject strings over 5MB
|
||||
const stringSizeInMB = new globalThis.Blob([jsonString]).size / (1024 * 1024)
|
||||
if (stringSizeInMB > FILE_SIZE_LIMIT) {
|
||||
throw new Error('JSON object 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`)
|
||||
}
|
||||
// Reject the upload if the file already exists in the directory
|
||||
// const fileExists = await fs.exists(
|
||||
// wn.path.file(...GALLERY_DIRS[selectedArea], `${username}.json`)
|
||||
// )
|
||||
// if (fileExists) {
|
||||
// throw new Error(`${username}.json file already exists`)
|
||||
// }
|
||||
|
||||
// Create a sub directory and add some content
|
||||
// Create a sub directory and add the JSON string as a file
|
||||
await fs.write(
|
||||
wn.path.file(...GALLERY_DIRS[selectedArea], image.name),
|
||||
image
|
||||
wn.path.file(...GALLERY_DIRS[selectedArea], `${username}.json`),
|
||||
new globalThis.Blob([jsonString], { type: 'application/json' })
|
||||
)
|
||||
|
||||
// Announce the changes to the server
|
||||
await fs.publish()
|
||||
|
||||
addNotification(`${image.name} image has been published`, 'success')
|
||||
addNotification(`${username}.json file has been published`, 'success')
|
||||
} catch (error) {
|
||||
addNotification(error.message, 'error')
|
||||
console.error(error)
|
||||
|
|
@ -182,7 +182,7 @@ export const deleteImageFromWNFS: (
|
|||
addNotification(`${name} image has been deleted`, 'success')
|
||||
|
||||
// Refetch images and update galleryStore
|
||||
await getImagesFromWNFS()
|
||||
await getJSONFromWNFS()
|
||||
} else {
|
||||
throw new Error(`${name} image has already been deleted`)
|
||||
}
|
||||
|
|
@ -192,6 +192,42 @@ export const deleteImageFromWNFS: (
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all JSON files from the user's private or public WNFS
|
||||
*/
|
||||
export const deleteAllJSONFromWNFS: () => Promise<void> = async () => {
|
||||
try {
|
||||
const { selectedArea } = getStore(galleryStore)
|
||||
const fs = getStore(filesystemStore)
|
||||
// Retrieve all JSON files in the selected directory
|
||||
const fileList = await fs.ls(wn.path.directory(...GALLERY_DIRS[selectedArea]))
|
||||
// Filter JSON files
|
||||
const jsonFiles = Object.keys(fileList).filter(key => key.endsWith('.json'))
|
||||
|
||||
if (jsonFiles.length > 0) {
|
||||
// Remove JSON files from the server
|
||||
for (const fileName of jsonFiles) {
|
||||
console.log(fileName)
|
||||
await fs.rm(wn.path.file(...GALLERY_DIRS[selectedArea], fileName))
|
||||
}
|
||||
|
||||
// Announce the changes to the server
|
||||
await fs.publish()
|
||||
|
||||
addNotification(`Data has been deleted from IPFS`, 'success')
|
||||
|
||||
// Refetch JSON files and update galleryStore
|
||||
await getJSONFromWNFS()
|
||||
} else {
|
||||
throw new Error(`No JSON files found to delete`)
|
||||
}
|
||||
} catch (error) {
|
||||
addNotification(error.message, 'error')
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle uploads made by interacting with the file input directly
|
||||
*/
|
||||
|
|
@ -200,10 +236,10 @@ export const handleFileInput: (
|
|||
) => Promise<void> = async files => {
|
||||
await Promise.all(
|
||||
Array.from(files).map(async file => {
|
||||
await uploadImageToWNFS(file)
|
||||
await uploadJSONToWNFS(file)
|
||||
})
|
||||
)
|
||||
|
||||
// Refetch images and update galleryStore
|
||||
await getImagesFromWNFS()
|
||||
await getJSONFromWNFS()
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue