commit
17a7ea706c
@ -0,0 +1,24 @@ |
|||||||
|
# Logs |
||||||
|
logs |
||||||
|
*.log |
||||||
|
npm-debug.log* |
||||||
|
yarn-debug.log* |
||||||
|
yarn-error.log* |
||||||
|
pnpm-debug.log* |
||||||
|
lerna-debug.log* |
||||||
|
|
||||||
|
node_modules |
||||||
|
dist |
||||||
|
dist-ssr |
||||||
|
*.local |
||||||
|
|
||||||
|
# Editor directories and files |
||||||
|
.vscode/* |
||||||
|
!.vscode/extensions.json |
||||||
|
.idea |
||||||
|
.DS_Store |
||||||
|
*.suo |
||||||
|
*.ntvs* |
||||||
|
*.njsproj |
||||||
|
*.sln |
||||||
|
*.sw? |
@ -0,0 +1,36 @@ |
|||||||
|
# demo-berlin |
||||||
|
|
||||||
|
Example of a Web app made with NextGraph, using React and LDO, and Vite. |
||||||
|
|
||||||
|
It demonstrate the feature of Social Queries. |
||||||
|
|
||||||
|
## NextGraph |
||||||
|
|
||||||
|
> NextGraph brings about the convergence of P2P and Semantic Web technologies, towards a decentralized, secure and privacy-preserving cloud, based on CRDTs. |
||||||
|
> |
||||||
|
> This open source ecosystem provides solutions for end-users (a platform) and software developers (a framework), wishing to use or create **decentralized** apps featuring: **live collaboration** on rich-text documents, peer to peer communication with **end-to-end encryption**, offline-first, **local-first**, portable and interoperable data, total ownership of data and software, security and privacy. Centered on repositories containing **semantic data** (RDF), **rich text**, and structured data formats like **JSON**, synced between peers belonging to permissioned groups of users, it offers strong eventual consistency, thanks to the use of **CRDTs**. Documents can be linked together, signed, shared securely, queried using the **SPARQL** language and organized into sites and containers. |
||||||
|
> |
||||||
|
> More info here [https://nextgraph.org](https://nextgraph.org) |
||||||
|
|
||||||
|
## For developing against a public Broker |
||||||
|
|
||||||
|
``` |
||||||
|
npm install |
||||||
|
npm run dev |
||||||
|
``` |
||||||
|
|
||||||
|
You will have to use a Wallet that was created on one of our public Broker Service Providers ([nextgraph.eu](https://nextgraph.eu) by example) before you can actually login. We didn't implement yet the option to create a Wallet while you are using or developing a 3rd party app. |
||||||
|
|
||||||
|
## License |
||||||
|
|
||||||
|
Licensed under either of |
||||||
|
|
||||||
|
- Apache License, Version 2.0 ([LICENSE-APACHE2](LICENSE-APACHE2) or http://www.apache.org/licenses/LICENSE-2.0) |
||||||
|
- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) |
||||||
|
at your option. |
||||||
|
|
||||||
|
`SPDX-License-Identifier: Apache-2.0 OR MIT` |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
NextGraph received funding through the [NGI Assure Fund](https://nlnet.nl/assure) and the [NGI Zero Commons Fund](https://nlnet.nl/commonsfund/), both funds established by [NLnet](https://nlnet.nl/) Foundation with financial support from the European Commission's [Next Generation Internet](https://ngi.eu/) programme, under the aegis of DG Communications Networks, Content and Technology under grant agreements No 957073 and No 101092990, respectively. |
@ -0,0 +1,28 @@ |
|||||||
|
import js from '@eslint/js' |
||||||
|
import globals from 'globals' |
||||||
|
import reactHooks from 'eslint-plugin-react-hooks' |
||||||
|
import reactRefresh from 'eslint-plugin-react-refresh' |
||||||
|
import tseslint from 'typescript-eslint' |
||||||
|
|
||||||
|
export default tseslint.config( |
||||||
|
{ ignores: ['dist'] }, |
||||||
|
{ |
||||||
|
extends: [js.configs.recommended, ...tseslint.configs.recommended], |
||||||
|
files: ['**/*.{ts,tsx}'], |
||||||
|
languageOptions: { |
||||||
|
ecmaVersion: 2020, |
||||||
|
globals: globals.browser, |
||||||
|
}, |
||||||
|
plugins: { |
||||||
|
'react-hooks': reactHooks, |
||||||
|
'react-refresh': reactRefresh, |
||||||
|
}, |
||||||
|
rules: { |
||||||
|
...reactHooks.configs.recommended.rules, |
||||||
|
'react-refresh/only-export-components': [ |
||||||
|
'warn', |
||||||
|
{ allowConstantExport: true }, |
||||||
|
], |
||||||
|
}, |
||||||
|
}, |
||||||
|
) |
@ -0,0 +1,12 @@ |
|||||||
|
<!doctype html> |
||||||
|
<html lang="en"> |
||||||
|
<head> |
||||||
|
<meta charset="UTF-8" /> |
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
||||||
|
<title>NextGraph WebApp React SocialQuery example</title> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<div id="app"></div> |
||||||
|
<script type="module" src="/src/main.tsx"></script> |
||||||
|
</body> |
||||||
|
</html> |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,45 @@ |
|||||||
|
{ |
||||||
|
"name": "example-webapp-react-socialquery", |
||||||
|
"private": true, |
||||||
|
"version": "0.0.1", |
||||||
|
"type": "module", |
||||||
|
"scripts": { |
||||||
|
"dev": "vite --host", |
||||||
|
"build": "tsc --noEmit && vite build", |
||||||
|
"lint": "eslint .", |
||||||
|
"preview": "vite preview", |
||||||
|
"build:ldo": "ldo build --input src/.shapes --output src/.ldo" |
||||||
|
}, |
||||||
|
"dependencies": { |
||||||
|
"@heroicons/react": "^2.2.0", |
||||||
|
"@ldo/connected-nextgraph": "^1.0.0-alpha.15", |
||||||
|
"@ldo/ldo": "^1.0.0-alpha.14", |
||||||
|
"@ldo/react": "^1.0.0-alpha.15", |
||||||
|
"@rdfjs/data-model": "^1.2.0", |
||||||
|
"@rdfjs/types": "^1.0.1", |
||||||
|
"nextgraphweb": "^0.1.1-alpha.4", |
||||||
|
"nextgraph-react": "^0.1.1-alpha.1", |
||||||
|
"react": "^19.0.0", |
||||||
|
"react-dom": "^19.0.0", |
||||||
|
"react-router": "^7.6.0" |
||||||
|
}, |
||||||
|
"devDependencies": { |
||||||
|
"@eslint/js": "^9.22.0", |
||||||
|
"@ldo/cli": "^1.0.0-alpha.15", |
||||||
|
"@types/jsonld": "^1.5.15", |
||||||
|
"@types/react": "^19.0.10", |
||||||
|
"@types/react-dom": "^19.0.4", |
||||||
|
"@types/shexj": "^2.1.7", |
||||||
|
"@vitejs/plugin-react": "^4.3.4", |
||||||
|
"autoprefixer": "^10.4.21", |
||||||
|
"eslint": "^9.22.0", |
||||||
|
"eslint-plugin-react-hooks": "^5.2.0", |
||||||
|
"eslint-plugin-react-refresh": "^0.4.19", |
||||||
|
"globals": "^16.0.0", |
||||||
|
"postcss": "^8.5.3", |
||||||
|
"tailwindcss": "^3.4.17", |
||||||
|
"typescript": "~5.7.2", |
||||||
|
"typescript-eslint": "^8.26.1", |
||||||
|
"vite": "^6.3.1" |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,6 @@ |
|||||||
|
export default { |
||||||
|
plugins: { |
||||||
|
tailwindcss: {}, |
||||||
|
autoprefixer: {}, |
||||||
|
}, |
||||||
|
} |
@ -0,0 +1,35 @@ |
|||||||
|
|
||||||
|
# Platform ontologies: |
||||||
|
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> |
||||||
|
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> |
||||||
|
PREFIX owl: <http://www.w3.org/2002/07/owl#> |
||||||
|
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> |
||||||
|
PREFIX dc: <http://purl.org/dc/terms/> |
||||||
|
|
||||||
|
# Domain ontology for Contacts in vcard-like form |
||||||
|
PREFIX vcard: <http://www.w3.org/2006/vcard/ns#> |
||||||
|
PREFIX schem: <http://schema.org/> |
||||||
|
PREFIX foaf: <http://xmlns.com/foaf/0.1/> |
||||||
|
PREFIX ngc: <did:ng:x:class#> |
||||||
|
PREFIX xskills: <did:ng:x:skills#> |
||||||
|
PREFIX ksp: <did:ng:k:skills:programming:> |
||||||
|
|
||||||
|
ngc:SocialContact EXTRA a { |
||||||
|
a [ vcard:Individual ] |
||||||
|
// rdfs:comment "Defines the node as an Individual (from vcard)" ; |
||||||
|
a [ schem:Person ] |
||||||
|
// rdfs:comment "Defines the node as a Person (from Schema.org)" ; |
||||||
|
a [ foaf:Person ] |
||||||
|
// rdfs:comment "Defines the node as a Person (from foaf)" ; |
||||||
|
vcard:fn xsd:string |
||||||
|
// rdfs:comment "The formatted name of a person. Example: John Smith" ; |
||||||
|
vcard:hasEmail xsd:string ? |
||||||
|
// rdfs:comment "The person's email." ; |
||||||
|
xskills:hasRating @ngc:HasRating *; |
||||||
|
} |
||||||
|
|
||||||
|
ngc:HasRating { |
||||||
|
a [ xskills:Rating ]; |
||||||
|
xskills:rated xsd:integer; |
||||||
|
xskills:skill [ ksp:svelte ksp:nextjs ksp:react ksp:vuejs ksp:tailwind ksp:rdf ksp:rust ksp:yjs ksp:automerge ] |
||||||
|
} |
@ -0,0 +1,24 @@ |
|||||||
|
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> |
||||||
|
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> |
||||||
|
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> |
||||||
|
PREFIX ldp: <http://www.w3.org/ns/ldp#> |
||||||
|
PREFIX ldps: <http://www.w3.org/ns/lddps#> |
||||||
|
PREFIX dct: <http://purl.org/dc/terms/> |
||||||
|
PREFIX stat: <http://www.w3.org/ns/posix/stat#> |
||||||
|
PREFIX tur: <http://www.w3.org/ns/iana/media-types/text/turtle#> |
||||||
|
PREFIX pim: <http://www.w3.org/ns/pim/space#> |
||||||
|
|
||||||
|
ldps:Container EXTRA a { |
||||||
|
$ldps:ContainerShape ( |
||||||
|
a [ ldp:Container ldp:Resource ]* |
||||||
|
// rdfs:comment "A container"; |
||||||
|
dct:modified xsd:string? |
||||||
|
// rdfs:comment "Date modified"; |
||||||
|
ldp:contains IRI * |
||||||
|
// rdfs:comment "Defines a Resource"; |
||||||
|
stat:mtime xsd:decimal? |
||||||
|
// rdfs:comment "?"; |
||||||
|
stat:size xsd:integer? |
||||||
|
// rdfs:comment "size of this container"; |
||||||
|
) |
||||||
|
} |
@ -0,0 +1,18 @@ |
|||||||
|
|
||||||
|
# Platform ontologies: |
||||||
|
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> |
||||||
|
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> |
||||||
|
PREFIX owl: <http://www.w3.org/2002/07/owl#> |
||||||
|
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> |
||||||
|
PREFIX dc: <http://purl.org/dc/terms/> |
||||||
|
|
||||||
|
PREFIX ngs: <did:ng:x:shape#> |
||||||
|
PREFIX ngc: <did:ng:x:class#> |
||||||
|
PREFIX ng: <did:ng:x:ng#> |
||||||
|
|
||||||
|
ngs:SocialQuery EXTRA a { |
||||||
|
a [ ngc:SocialQuery ]; |
||||||
|
ng:social_query_sparql xsd:string ?; |
||||||
|
ng:social_query_forwarder IRI ?; |
||||||
|
ng:social_query_ended xsd:dateTime ?; |
||||||
|
} |
@ -0,0 +1,46 @@ |
|||||||
|
|
||||||
|
|
||||||
|
.centered { |
||||||
|
/*max-width: 1280px;*/ |
||||||
|
margin: 0 auto; |
||||||
|
padding: 0rem; |
||||||
|
text-align: center; |
||||||
|
width: fit-content; |
||||||
|
} |
||||||
|
|
||||||
|
.contact { |
||||||
|
width: 300px; |
||||||
|
height: 300px; |
||||||
|
background-color: #f6f6f6; |
||||||
|
position: relative; |
||||||
|
overflow-wrap: anywhere; |
||||||
|
} |
||||||
|
|
||||||
|
.name { |
||||||
|
padding: 5px; |
||||||
|
height: 35px; |
||||||
|
overflow: hidden; |
||||||
|
background-color: #e0e0e0d0; |
||||||
|
overflow: hidden; |
||||||
|
text-overflow: ellipsis; |
||||||
|
white-space: nowrap; |
||||||
|
} |
||||||
|
|
||||||
|
.email-logo { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
.email { |
||||||
|
padding: 5px; |
||||||
|
overflow-wrap: anywhere; |
||||||
|
} |
||||||
|
|
||||||
|
input { |
||||||
|
margin: 5px; |
||||||
|
} |
||||||
|
|
||||||
|
.button { |
||||||
|
background-color:rgb(73, 114, 165); |
||||||
|
color:white; |
||||||
|
cursor:pointer; |
||||||
|
} |
@ -0,0 +1,29 @@ |
|||||||
|
|
||||||
|
import React, { FunctionComponent } from 'react'; |
||||||
|
import { Header } from './Header'; |
||||||
|
import { Contacts } from './Contacts'; |
||||||
|
import Query from './Query'; |
||||||
|
import { BrowserNGLdoProvider } from './reactMethods'; |
||||||
|
import { BrowserRouter, Routes, Route } from "react-router"; |
||||||
|
|
||||||
|
import './App.css' |
||||||
|
import "./styles.css"; |
||||||
|
|
||||||
|
const App: FunctionComponent = () => { |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="App"> |
||||||
|
<BrowserNGLdoProvider> |
||||||
|
<Header /> |
||||||
|
<BrowserRouter> |
||||||
|
<Routes> |
||||||
|
<Route path="/" element={<Contacts />} /> |
||||||
|
<Route path="/query" element={<Query />} /> |
||||||
|
</Routes> |
||||||
|
</BrowserRouter> |
||||||
|
</BrowserNGLdoProvider> |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
export default App |
@ -0,0 +1,19 @@ |
|||||||
|
import { default as React, FunctionComponent } from "react"; |
||||||
|
import { SocialContactShapeType } from "./.ldo/contact.shapeTypes.ts"; |
||||||
|
import { useSubscribeToResource, useResource, useSubject, useLdo, useNextGraphAuth } from "./reactMethods.ts"; |
||||||
|
import { StarIcon } from '@heroicons/react/24/solid' |
||||||
|
import { StarIcon as StarIconOutline } from '@heroicons/react/24/outline' |
||||||
|
import { |
||||||
|
startTransaction, |
||||||
|
transactionChanges, |
||||||
|
toSparqlUpdate, |
||||||
|
commitTransaction, |
||||||
|
} from "@ldo/ldo"; |
||||||
|
|
||||||
|
export const Contact: FunctionComponent = ({nuri}) => { |
||||||
|
|
||||||
|
|
||||||
|
return <> |
||||||
|
|
||||||
|
</>; |
||||||
|
}; |
@ -0,0 +1,17 @@ |
|||||||
|
import { FunctionComponent } from "react"; |
||||||
|
import { useNextGraphAuth } from "./reactMethods"; |
||||||
|
import { ContainerShapeType } from "./.ldo/container.shapeTypes.ts"; |
||||||
|
import { useSubscribeToResource, useResource, useSubject } from "./reactMethods.ts"; |
||||||
|
import { Contact } from "./Contact"; |
||||||
|
import { MakeContact } from "./MakeContact"; |
||||||
|
import { Link } from "react-router"; |
||||||
|
import { LifebuoyIcon } from '@heroicons/react/24/outline' |
||||||
|
|
||||||
|
export const Contacts: FunctionComponent = () => { |
||||||
|
const { session } = useNextGraphAuth(); |
||||||
|
|
||||||
|
return <> |
||||||
|
|
||||||
|
</>; |
||||||
|
}; |
||||||
|
|
@ -0,0 +1,46 @@ |
|||||||
|
import { FunctionComponent } from "react"; |
||||||
|
import { useNextGraphAuth } from "./reactMethods"; |
||||||
|
|
||||||
|
|
||||||
|
export const Header: FunctionComponent = () => { |
||||||
|
|
||||||
|
const { session, login, logout } = useNextGraphAuth(); |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="full-layout"> |
||||||
|
|
||||||
|
{session.sessionId ? ( |
||||||
|
// If the session is logged in
|
||||||
|
<div className="p-1 text-white text-center fixed top-0 left-0 right-0" style={{zIndex:1000, height:'36px', backgroundColor:'rgb(73, 114, 165)'}}> |
||||||
|
You are logged in. |
||||||
|
{/* <span className="font-bold clickable" onClick={logout}> Log out</span> */} |
||||||
|
</div> |
||||||
|
) : ( |
||||||
|
// If the session is not logged in
|
||||||
|
<> |
||||||
|
<h1 className="text-2xl text-center mb-10">Welcome to the mini-LinkedIn demo</h1> |
||||||
|
<h1 className="text-lg text-center mb-10">An example use of Social Queries with NextGraph</h1> |
||||||
|
<div className="text-center text-xl p-1 text-white fixed top-0 left-0 right-0" style={{zIndex:1000, height:'36px', backgroundColor:'rgb(73, 114, 165)'}}> |
||||||
|
Please <span className="font-bold clickable" onClick={login}> Log in</span> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="text-center max-w-6xl lg:px-8 mx-auto px-4 text-blue-800"> |
||||||
|
|
||||||
|
<svg onClick={login} |
||||||
|
onKeyUp={login} className="cursor-pointer mt-10 h-16 w-16 mx-auto" data-slot="icon" fill="none" strokeWidth="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"> |
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M8.25 9V5.25A2.25 2.25 0 0 1 10.5 3h6a2.25 2.25 0 0 1 2.25 2.25v13.5A2.25 2.25 0 0 1 16.5 21h-6a2.25 2.25 0 0 1-2.25-2.25V15M12 9l3 3m0 0-3 3m3-3H2.25"></path> |
||||||
|
</svg> |
||||||
|
|
||||||
|
<button |
||||||
|
onClick={login} |
||||||
|
onKeyUp={login} |
||||||
|
className="select-none ml-0 mt-2 mb-10 text-white bg-blue-800 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-500/50 rounded-lg text-base p-2 text-center inline-flex items-center dark:focus:ring-primary-700/55" |
||||||
|
> |
||||||
|
Please Log in |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
</> |
||||||
|
)} |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
@ -0,0 +1,9 @@ |
|||||||
|
import { FormEvent, FunctionComponent, useCallback, useState } from "react"; |
||||||
|
import { useLdo, dataset, useNextGraphAuth } from './reactMethods'; |
||||||
|
import { SocialContactShapeType } from "./.ldo/contact.shapeTypes.ts"; |
||||||
|
|
||||||
|
export const MakeContact: FunctionComponent = () => { |
||||||
|
return ( |
||||||
|
<></> |
||||||
|
); |
||||||
|
}; |
@ -0,0 +1,211 @@ |
|||||||
|
import React, { FunctionComponent, useState } from 'react'; |
||||||
|
import { LifebuoyIcon } from '@heroicons/react/24/outline' |
||||||
|
import { useLdo, dataset, useNextGraphAuth, useResource, useSubject} from './reactMethods'; |
||||||
|
import { SocialQueryShapeType } from "./.ldo/socialquery.shapeTypes.ts"; |
||||||
|
import { namedNode } from "@rdfjs/data-model"; |
||||||
|
import type { Quad } from "@rdfjs/types"; |
||||||
|
import type { DatasetChanges } from "@ldo/rdf-utils"; |
||||||
|
|
||||||
|
|
||||||
|
const query_string = `PREFIX vcard: <http://www.w3.org/2006/vcard/ns#>
|
||||||
|
PREFIX xskills: <did:ng:x:skills#> |
||||||
|
PREFIX ksp: <did:ng:k:skills:programming:> |
||||||
|
PREFIX ng: <did:ng:x:ng#> |
||||||
|
CONSTRUCT { [ |
||||||
|
vcard:hasEmail ?email; |
||||||
|
vcard:fn ?name; |
||||||
|
a vcard:Individual; |
||||||
|
ng:site ?public_profile; |
||||||
|
ng:protected ?protected_profile; |
||||||
|
xskills:hasRating [ |
||||||
|
a xskills:Rating ; |
||||||
|
xskills:rated ?level; |
||||||
|
xskills:skill ?skill |
||||||
|
] |
||||||
|
] |
||||||
|
} |
||||||
|
WHERE {
|
||||||
|
?contact a vcard:Individual. |
||||||
|
?contact vcard:fn ?name. |
||||||
|
?contact vcard:hasEmail ?email. |
||||||
|
OPTIONAL { ?contact ng:site ?public_profile . ?contact ng:site_inbox ?public_inbox } |
||||||
|
OPTIONAL { ?contact ng:protected ?protected_profile . ?contact ng:protected_inbox ?prot_inbox } |
||||||
|
?contact xskills:hasRating [ |
||||||
|
a xskills:Rating ; |
||||||
|
xskills:rated ?level; |
||||||
|
xskills:skill ?skill |
||||||
|
]. |
||||||
|
?contact xskills:hasRating/xskills:skill ksp:rust. |
||||||
|
?contact xskills:hasRating/xskills:skill ksp:svelte. |
||||||
|
FILTER ( ?skill IN ( |
||||||
|
ksp:rust, ksp:svelte, ksp:rdf, ksp:tailwind, ksp:yjs, ksp:automerge |
||||||
|
) ) |
||||||
|
}`;
|
||||||
|
|
||||||
|
const ranking_query = `SELECT ?mail (SAMPLE(?n) as?name) (MAX(?rust_) as ?rust) (MAX(?svelte_) as ?svelte) (MAX(?tailwind_) as ?tailwind)
|
||||||
|
(MAX(?rdf_) as ?rdf) (MAX(?yjs_) as ?yjs) (MAX(?automerge_) as ?automerge) (SUM(?total_) as ?total)
|
||||||
|
WHERE {
|
||||||
|
{ SELECT ?mail (SAMPLE(?name) as ?n) ?skill (AVG(?value)+1 AS ?score)
|
||||||
|
WHERE { |
||||||
|
?rating <http://www.w3.org/2006/vcard/ns#hasEmail> ?mail. |
||||||
|
?rating <http://www.w3.org/2006/vcard/ns#fn> ?name. |
||||||
|
?rating <did:ng:x:skills#hasRating> ?hasrating. |
||||||
|
?hasrating <did:ng:x:skills#rated> ?value. |
||||||
|
?hasrating <did:ng:x:skills#skill> ?skill. |
||||||
|
} GROUP BY ?mail ?skill
|
||||||
|
} |
||||||
|
BIND (IF(sameTerm(?skill, <did:ng:k:skills:programming:rust>), ?score, 0) AS ?rust_) |
||||||
|
BIND (IF(sameTerm(?skill, <did:ng:k:skills:programming:svelte>), ?score, 0) AS ?svelte_) |
||||||
|
BIND (IF(sameTerm(?skill, <did:ng:k:skills:programming:tailwind>), ?score, 0) AS ?tailwind_) |
||||||
|
BIND (IF(sameTerm(?skill, <did:ng:k:skills:programming:rdf>), ?score, 0) AS ?rdf_) |
||||||
|
BIND (IF(sameTerm(?skill, <did:ng:k:skills:programming:yjs>), ?score, 0) AS ?yjs_) |
||||||
|
BIND (IF(sameTerm(?skill, <did:ng:k:skills:programming:automerge>), ?score, 0) AS ?automerge_) |
||||||
|
BIND (?tailwind_+?svelte_+?rust_+?rdf_+?yjs_+?automerge_ AS ?total_) |
||||||
|
} GROUP BY ?mail |
||||||
|
ORDER BY DESC(?total)`;
|
||||||
|
|
||||||
|
const Query: FunctionComponent = () => { |
||||||
|
|
||||||
|
const { createData, commitData, changeData } = useLdo(); |
||||||
|
const { session } = useNextGraphAuth(); |
||||||
|
|
||||||
|
const [resourceUri, setResourceUri] = useState(""); |
||||||
|
useResource(resourceUri, { subscribe: true }); |
||||||
|
const [nuri, setNuri] = useState(""); |
||||||
|
const [querying, setQuerying] = useState(false); |
||||||
|
const [results, setResults] = useState([]); |
||||||
|
let social_query = useSubject(SocialQueryShapeType, session.sessionId && nuri ? nuri : undefined); |
||||||
|
|
||||||
|
React.useEffect(() => { |
||||||
|
|
||||||
|
async function start() { |
||||||
|
let res = await session.ng.app_request_with_nuri_command(nuri, {Fetch:"CurrentHeads"}, session.sessionId); |
||||||
|
let request_nuri = res.V0?.Text; |
||||||
|
console.log(request_nuri); |
||||||
|
|
||||||
|
// finally start the social query
|
||||||
|
res = await session.ng.social_query_start( |
||||||
|
session.sessionId, |
||||||
|
"did:ng:a",
|
||||||
|
request_nuri, |
||||||
|
"did:ng:d:c",
|
||||||
|
0, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
if (social_query?.socialQuerySparql) { |
||||||
|
if (!social_query?.socialQueryForwarder) { |
||||||
|
//console.log(social_query?.socialQuerySparql);
|
||||||
|
start(); |
||||||
|
} else { |
||||||
|
console.log("ready to receive results") |
||||||
|
dataset.on( |
||||||
|
[null, null, null, namedNode(resourceUri)], |
||||||
|
(changes: DatasetChanges<Quad>) => { |
||||||
|
session.ng.sparql_query(session.sessionId, ranking_query, undefined, resourceUri).then((res) => { |
||||||
|
setResults(res.results?.bindings); |
||||||
|
}); |
||||||
|
}, |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
}, [resourceUri, nuri, social_query, session]) |
||||||
|
|
||||||
|
const openQuery = async () => { |
||||||
|
setQuerying(true); |
||||||
|
try { |
||||||
|
|
||||||
|
let resource = await dataset.createResource("nextgraph", { primaryClass: "social:query:skills:programming" }); |
||||||
|
if (!resource.isError) { |
||||||
|
console.log("Created resource:", resource.uri); |
||||||
|
setResourceUri(resource.uri); |
||||||
|
setNuri(resource.uri.substring(0,53)); |
||||||
|
const query = createData( |
||||||
|
SocialQueryShapeType, |
||||||
|
nuri, |
||||||
|
resource |
||||||
|
); |
||||||
|
query.type = { "@id": "SocialQuery" }; |
||||||
|
const result = await commitData(query); |
||||||
|
if (result.isError) { |
||||||
|
console.error(result.message); |
||||||
|
} |
||||||
|
|
||||||
|
// then add the did:ng:x:ng#social_query_sparql
|
||||||
|
//await session.ng.sparql_update(session.sessionId,`INSERT DATA { <${nuri}> <did:ng:x:ng#social_query_sparql> \"${query_string.replaceAll("\n"," ")}\".}`, resource.uri);
|
||||||
|
let editing_query = changeData(query, resource); |
||||||
|
editing_query.socialQuerySparql = query_string.replaceAll("\n"," "); |
||||||
|
// const changes = transactionChanges(editing_query);
|
||||||
|
// console.log(changes.added?.toString())
|
||||||
|
// console.log(changes.removed?.toString())
|
||||||
|
let res = await commitData(editing_query); |
||||||
|
if (res.isError) { |
||||||
|
console.error(result.message); |
||||||
|
} |
||||||
|
} |
||||||
|
else { |
||||||
|
console.error(resource);
|
||||||
|
} |
||||||
|
|
||||||
|
} catch (e) { |
||||||
|
console.error(e) |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
if (!session.sessionId) return <></>; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="centered"> |
||||||
|
<div className="flex flex-col justify-center gap-5 mt-10 mb-5"> |
||||||
|
{!querying && <p className="p-3"> |
||||||
|
<button |
||||||
|
onClick={openQuery} |
||||||
|
onKeyPress={openQuery}
|
||||||
|
className="button select-none ml-2 mt-2 mb-2 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-500/50 rounded-lg text-base p-2 text-center inline-flex items-center dark:focus:ring-primary-700/55" |
||||||
|
> |
||||||
|
<LifebuoyIcon tabIndex={-1} className="mr-2 focus:outline-none size-6" /> |
||||||
|
Start query |
||||||
|
</button> |
||||||
|
</p> |
||||||
|
} |
||||||
|
|
||||||
|
<div className="relative overflow-x-auto"> |
||||||
|
<table className="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400 table-auto"> |
||||||
|
<thead className="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400"> |
||||||
|
<tr> |
||||||
|
<th scope="col" className="px-6 py-3">Email</th> |
||||||
|
<th scope="col" className="px-6 py-3">Name</th> |
||||||
|
<th scope="col" className="px-6 py-3">Rust</th> |
||||||
|
<th scope="col" className="px-6 py-3">Svelte</th> |
||||||
|
<th scope="col" className="px-6 py-3">Tailwind</th> |
||||||
|
<th scope="col" className="px-6 py-3">Rdf</th> |
||||||
|
<th scope="col" className="px-6 py-3">Yjs</th> |
||||||
|
<th scope="col" className="px-6 py-3">Automerge</th> |
||||||
|
<th scope="col" className="px-6 py-3">Total</th> |
||||||
|
</tr> |
||||||
|
</thead> |
||||||
|
<tbody> |
||||||
|
{
|
||||||
|
results.map((res) =>
|
||||||
|
<tr key={res.mail.value} className="bg-white border-b dark:bg-gray-800 dark:border-gray-700 border-gray-200"> |
||||||
|
<td scope="row" className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">{res.mail.value}</td> |
||||||
|
<td scope="row" className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">{res.name.value}</td> |
||||||
|
<td scope="row" className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">{Math.round(res.rust.value * 10) / 10 }</td> |
||||||
|
<td scope="row" className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">{Math.round(res.svelte.value * 10) / 10 }</td> |
||||||
|
<td scope="row" className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">{Math.round(res.tailwind.value * 10) / 10 }</td> |
||||||
|
<td scope="row" className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">{Math.round(res.rdf.value * 10) / 10 }</td> |
||||||
|
<td scope="row" className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">{Math.round(res.yjs.value * 10) / 10 }</td> |
||||||
|
<td scope="row" className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">{Math.round(res.automerge.value * 10) / 10 }</td> |
||||||
|
<td scope="row" className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">{Math.round(res.total.value * 10) / 10 }</td> |
||||||
|
</tr> |
||||||
|
) |
||||||
|
} |
||||||
|
</tbody> |
||||||
|
</table> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
export default Query |
@ -0,0 +1,3 @@ |
|||||||
|
@tailwind base; |
||||||
|
@tailwind components; |
||||||
|
@tailwind utilities; |
@ -0,0 +1,9 @@ |
|||||||
|
import { createRoot } from 'react-dom/client' |
||||||
|
import './index.css' |
||||||
|
import App from './App.tsx' |
||||||
|
|
||||||
|
createRoot(document.getElementById('app')!).render( |
||||||
|
|
||||||
|
<App /> |
||||||
|
|
||||||
|
) |
@ -0,0 +1,18 @@ |
|||||||
|
import { nextGraphConnectedPlugin } from "@ldo/connected-nextgraph"; |
||||||
|
import { createLdoReactMethods } from "@ldo/react"; |
||||||
|
import { createBrowserNGReactMethods } from "nextgraph-react"; |
||||||
|
|
||||||
|
export const { |
||||||
|
dataset, |
||||||
|
useLdo, |
||||||
|
useMatchObject, |
||||||
|
useMatchSubject, |
||||||
|
useResource, |
||||||
|
useSubject, |
||||||
|
useSubscribeToResource, |
||||||
|
} = createLdoReactMethods([nextGraphConnectedPlugin]); |
||||||
|
|
||||||
|
const methods = createBrowserNGReactMethods(dataset); |
||||||
|
|
||||||
|
export const { BrowserNGLdoProvider, useNextGraphAuth } = methods; |
||||||
|
|
@ -0,0 +1,226 @@ |
|||||||
|
/* |
||||||
|
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers |
||||||
|
// All rights reserved. |
||||||
|
// Licensed under the Apache License, Version 2.0 |
||||||
|
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0> |
||||||
|
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>, |
||||||
|
// at your option. All files in the project carrying such |
||||||
|
// notice may not be copied, modified, or distributed except |
||||||
|
// according to those terms. |
||||||
|
*/ |
||||||
|
|
||||||
|
|
||||||
|
/** To format paths, like Settings > Wallet > Generate Wallet QR */ |
||||||
|
.path { |
||||||
|
font-family: monospace; |
||||||
|
background-color: rgba(73, 114, 165, 0.1); |
||||||
|
} |
||||||
|
/* .splash-loaded { |
||||||
|
display: none; |
||||||
|
} */ |
||||||
|
|
||||||
|
|
||||||
|
.toggle * { |
||||||
|
cursor: pointer; |
||||||
|
} |
||||||
|
|
||||||
|
.error-popover h3 { |
||||||
|
text-align: center; |
||||||
|
color: rgb(200 30 30); |
||||||
|
} |
||||||
|
.error-popover > div:first-child { |
||||||
|
background-color: rgb(200 30 30); |
||||||
|
} |
||||||
|
.error-popover > div:first-child > h3 { |
||||||
|
color: white; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.logo { |
||||||
|
padding: 1.5em; |
||||||
|
will-change: filter; |
||||||
|
transition: 0.75s; |
||||||
|
padding-bottom: 1em; |
||||||
|
} |
||||||
|
|
||||||
|
@keyframes pulse-logo-color { |
||||||
|
0%, |
||||||
|
100% { |
||||||
|
fill: rgb(73, 114, 165); |
||||||
|
stroke: rgb(73, 114, 165); |
||||||
|
} |
||||||
|
50% { |
||||||
|
/* Mid-transition color */ |
||||||
|
stroke: #bbb; |
||||||
|
fill: #bbb; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.logo-pulse path { |
||||||
|
animation: pulse-logo-color 2s infinite; |
||||||
|
animation-timing-function: cubic-bezier(0.65, 0.01, 0.59, 0.83); |
||||||
|
} |
||||||
|
|
||||||
|
.logo-gray path { |
||||||
|
fill: #bbb; |
||||||
|
stroke: #bbb; |
||||||
|
} |
||||||
|
|
||||||
|
.logo-blue path { |
||||||
|
fill: rgb(73, 114, 165); |
||||||
|
stroke: rgb(73, 114, 165); |
||||||
|
} |
||||||
|
|
||||||
|
.jse-absolute-popup-content { |
||||||
|
left: 0 !important; |
||||||
|
} |
||||||
|
|
||||||
|
.container3 { |
||||||
|
margin: 0; |
||||||
|
|
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
justify-content: center; |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
|
||||||
|
.container3 aside { |
||||||
|
width: 20rem !important; |
||||||
|
} |
||||||
|
|
||||||
|
div[role="alert"] div { |
||||||
|
display: block; |
||||||
|
} |
||||||
|
|
||||||
|
.spinner-overlay button { |
||||||
|
display: none; |
||||||
|
} |
||||||
|
|
||||||
|
.choice-button { |
||||||
|
min-width: 305px; |
||||||
|
} |
||||||
|
|
||||||
|
.clickable { |
||||||
|
cursor: pointer; |
||||||
|
} |
||||||
|
|
||||||
|
.row { |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
} |
||||||
|
|
||||||
|
.deactivated-menu > svg { |
||||||
|
color: rgb(156 163 175) !important; |
||||||
|
} |
||||||
|
|
||||||
|
:root { |
||||||
|
font-family: Inter, Avenir, Helvetica, Arial, sans-serif; |
||||||
|
font-size: 16px; |
||||||
|
line-height: 24px; |
||||||
|
font-weight: 400; |
||||||
|
|
||||||
|
color: #0f0f0f; |
||||||
|
background-color: white; |
||||||
|
|
||||||
|
font-synthesis: none; |
||||||
|
text-rendering: optimizeLegibility; |
||||||
|
-webkit-font-smoothing: antialiased; |
||||||
|
-moz-osx-font-smoothing: grayscale; |
||||||
|
-webkit-text-size-adjust: 100%; |
||||||
|
} |
||||||
|
|
||||||
|
body { |
||||||
|
margin: 0; |
||||||
|
display: flex; |
||||||
|
place-items: center; |
||||||
|
min-width: 305px; |
||||||
|
min-height: 100vh; |
||||||
|
} |
||||||
|
|
||||||
|
#app { |
||||||
|
width: 100%; |
||||||
|
} |
||||||
|
/* #app { |
||||||
|
/*max-width: 1280px; |
||||||
|
margin: 0 auto; |
||||||
|
padding: 0rem; |
||||||
|
text-align: center; |
||||||
|
} */ |
||||||
|
|
||||||
|
/* .container2 { |
||||||
|
padding-top: 10vh; |
||||||
|
} */ |
||||||
|
|
||||||
|
a { |
||||||
|
font-weight: 500; |
||||||
|
color: #646cff; |
||||||
|
text-decoration: inherit; |
||||||
|
} |
||||||
|
|
||||||
|
a:hover { |
||||||
|
color: #535bf2; |
||||||
|
} |
||||||
|
|
||||||
|
.toast { |
||||||
|
left: 50%; |
||||||
|
transform: translateX(-50%); |
||||||
|
z-index: 49; |
||||||
|
cursor: pointer; |
||||||
|
} |
||||||
|
|
||||||
|
input, |
||||||
|
button { |
||||||
|
border-radius: 8px; |
||||||
|
border: 1px solid transparent; |
||||||
|
padding: 0.6em 1.2em; |
||||||
|
font-size: 1em; |
||||||
|
font-weight: 500; |
||||||
|
font-family: inherit; |
||||||
|
color: #0f0f0f; |
||||||
|
background-color: #ffffff; |
||||||
|
transition: border-color 0.25s; |
||||||
|
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2); |
||||||
|
} |
||||||
|
|
||||||
|
button { |
||||||
|
cursor: pointer; |
||||||
|
} |
||||||
|
|
||||||
|
button:hover { |
||||||
|
border-color: #396cd8; |
||||||
|
} |
||||||
|
button:active { |
||||||
|
border-color: #396cd8; |
||||||
|
background-color: #e8e8e8; |
||||||
|
} |
||||||
|
|
||||||
|
/* input, |
||||||
|
button { |
||||||
|
outline: none; |
||||||
|
} */ |
||||||
|
|
||||||
|
button:focus, |
||||||
|
button:focus-visible { |
||||||
|
outline: 4px auto -webkit-focus-ring-color; |
||||||
|
} |
||||||
|
|
||||||
|
/* @media (prefers-color-scheme: dark) { |
||||||
|
:root { |
||||||
|
color: #f6f6f6; |
||||||
|
background-color: #2f2f2f; |
||||||
|
} |
||||||
|
|
||||||
|
a:hover { |
||||||
|
color: #24c8db; |
||||||
|
} |
||||||
|
|
||||||
|
input, |
||||||
|
button { |
||||||
|
color: #ffffff; |
||||||
|
background-color: #0f0f0f98; |
||||||
|
} |
||||||
|
button:active { |
||||||
|
background-color: #0f0f0f69; |
||||||
|
} |
||||||
|
} */ |
@ -0,0 +1 @@ |
|||||||
|
/// <reference types="vite/client" />
|
@ -0,0 +1,12 @@ |
|||||||
|
/** @type {import('tailwindcss').Config} */ |
||||||
|
export default { |
||||||
|
content: [ |
||||||
|
"./index.html", |
||||||
|
"./src/**/*.{js,jsx,ts,tsx}" |
||||||
|
], |
||||||
|
theme: { |
||||||
|
extend: {}, |
||||||
|
}, |
||||||
|
plugins: [], |
||||||
|
} |
||||||
|
|
@ -0,0 +1,26 @@ |
|||||||
|
{ |
||||||
|
"compilerOptions": { |
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", |
||||||
|
"target": "ES2020", |
||||||
|
"useDefineForClassFields": true, |
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"], |
||||||
|
"module": "ESNext", |
||||||
|
"skipLibCheck": true, |
||||||
|
|
||||||
|
/* Bundler mode */ |
||||||
|
"moduleResolution": "bundler", |
||||||
|
"allowImportingTsExtensions": true, |
||||||
|
"isolatedModules": true, |
||||||
|
"moduleDetection": "force", |
||||||
|
"noEmit": true, |
||||||
|
"jsx": "react-jsx", |
||||||
|
|
||||||
|
/* Linting */ |
||||||
|
"strict": true, |
||||||
|
"noUnusedLocals": true, |
||||||
|
"noUnusedParameters": true, |
||||||
|
"noFallthroughCasesInSwitch": true, |
||||||
|
"noUncheckedSideEffectImports": true |
||||||
|
}, |
||||||
|
"include": ["src"] |
||||||
|
} |
@ -0,0 +1,7 @@ |
|||||||
|
{ |
||||||
|
"files": [], |
||||||
|
"references": [ |
||||||
|
{ "path": "./tsconfig.app.json" }, |
||||||
|
{ "path": "./tsconfig.node.json" } |
||||||
|
] |
||||||
|
} |
@ -0,0 +1,24 @@ |
|||||||
|
{ |
||||||
|
"compilerOptions": { |
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", |
||||||
|
"target": "ES2022", |
||||||
|
"lib": ["ES2023"], |
||||||
|
"module": "ESNext", |
||||||
|
"skipLibCheck": true, |
||||||
|
|
||||||
|
/* Bundler mode */ |
||||||
|
"moduleResolution": "bundler", |
||||||
|
"allowImportingTsExtensions": true, |
||||||
|
"isolatedModules": true, |
||||||
|
"moduleDetection": "force", |
||||||
|
"noEmit": true, |
||||||
|
|
||||||
|
/* Linting */ |
||||||
|
"strict": true, |
||||||
|
"noUnusedLocals": true, |
||||||
|
"noUnusedParameters": true, |
||||||
|
"noFallthroughCasesInSwitch": true, |
||||||
|
"noUncheckedSideEffectImports": true |
||||||
|
}, |
||||||
|
"include": ["vite.config.ts"] |
||||||
|
} |
@ -0,0 +1,7 @@ |
|||||||
|
import { defineConfig } from 'vite' |
||||||
|
import react from '@vitejs/plugin-react' |
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({ |
||||||
|
plugins: [react()], |
||||||
|
}) |
Loading…
Reference in new issue