skills with LDO

master
Niko PLP 16 hours ago
parent a9ddf9ad32
commit 07ec377202
  1. 24
      ng-sdk-js/example-webapp-react-socialquery/.gitignore
  2. 59
      ng-sdk-js/example-webapp-react-socialquery/README.md
  3. 28
      ng-sdk-js/example-webapp-react-socialquery/eslint.config.js
  4. 12
      ng-sdk-js/example-webapp-react-socialquery/index.html
  5. 6170
      ng-sdk-js/example-webapp-react-socialquery/package-lock.json
  6. 41
      ng-sdk-js/example-webapp-react-socialquery/package.json
  7. 6
      ng-sdk-js/example-webapp-react-socialquery/postcss.config.js
  8. 119
      ng-sdk-js/example-webapp-react-socialquery/src/.ldo/contact.context.ts
  9. 166
      ng-sdk-js/example-webapp-react-socialquery/src/.ldo/contact.schema.ts
  10. 28
      ng-sdk-js/example-webapp-react-socialquery/src/.ldo/contact.shapeTypes.ts
  11. 78
      ng-sdk-js/example-webapp-react-socialquery/src/.ldo/contact.typings.ts
  12. 82
      ng-sdk-js/example-webapp-react-socialquery/src/.ldo/container.context.ts
  13. 124
      ng-sdk-js/example-webapp-react-socialquery/src/.ldo/container.schema.ts
  14. 19
      ng-sdk-js/example-webapp-react-socialquery/src/.ldo/container.shapeTypes.ts
  15. 44
      ng-sdk-js/example-webapp-react-socialquery/src/.ldo/container.typings.ts
  16. 35
      ng-sdk-js/example-webapp-react-socialquery/src/.shapes/contact.shex
  17. 24
      ng-sdk-js/example-webapp-react-socialquery/src/.shapes/container.shex
  18. 46
      ng-sdk-js/example-webapp-react-socialquery/src/App.css
  19. 22
      ng-sdk-js/example-webapp-react-socialquery/src/App.tsx
  20. 118
      ng-sdk-js/example-webapp-react-socialquery/src/Contact.tsx
  21. 38
      ng-sdk-js/example-webapp-react-socialquery/src/Contacts.tsx
  22. 45
      ng-sdk-js/example-webapp-react-socialquery/src/Header.tsx
  23. 60
      ng-sdk-js/example-webapp-react-socialquery/src/MakeContact.tsx
  24. 20
      ng-sdk-js/example-webapp-react-socialquery/src/NextGraphAuthContext.ts
  25. 112
      ng-sdk-js/example-webapp-react-socialquery/src/createBrowserNGReactMethods.tsx
  26. 3
      ng-sdk-js/example-webapp-react-socialquery/src/index.css
  27. 10
      ng-sdk-js/example-webapp-react-socialquery/src/main.tsx
  28. 18
      ng-sdk-js/example-webapp-react-socialquery/src/reactMethods.ts
  29. 1
      ng-sdk-js/example-webapp-react-socialquery/src/vite-env.d.ts
  30. 12
      ng-sdk-js/example-webapp-react-socialquery/tailwind.config.js
  31. 26
      ng-sdk-js/example-webapp-react-socialquery/tsconfig.app.json
  32. 7
      ng-sdk-js/example-webapp-react-socialquery/tsconfig.json
  33. 24
      ng-sdk-js/example-webapp-react-socialquery/tsconfig.node.json
  34. 7
      ng-sdk-js/example-webapp-react-socialquery/vite.config.ts
  35. 8
      ng-sdk-js/example-webapp-react/package.json
  36. 2
      ng-sdk-js/example-webapp-react/src/.ldo/contact.schema.ts
  37. 8
      ng-sdk-js/example-webapp-react/src/.ldo/contact.shapeTypes.ts
  38. 4
      ng-sdk-js/example-webapp-react/src/.ldo/contact.typings.ts
  39. 2
      ng-sdk-js/example-webapp-react/src/.shapes/contact.shex
  40. 4
      ng-sdk-js/example-webapp-react/src/Contact.tsx
  41. 4
      ng-sdk-js/example-webapp-react/src/MakeContact.tsx

@ -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,59 @@
# example-webapp-react-socialquery
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.
## For developing locally
you need to have a running local ngd server. See those [instructions first](https://git.nextgraph.org/NextGraph/nextgraph-rs/src/branch/master/DEV.md#first-run).
If you are running a local devenv for the frontend of nextGraph on http://localhost:1421 , then (and only then) you need to compile the nextgraphweb package in dev mode:
```
pnpm run -C ../../helpers/nextgraphweb builddev
```
Due to the way `npm link` works, you will have to run this command again, after each time you use `npm install`.
Otherwise, if you are using http://localhost:14400 in your browser, just skip the line above, and continue with those:
```
npm install
npm link ../../helpers/nextgraphweb
npm run dev
```
Open this URL in browser : [http://localhost:5173](http://localhost:5173)
See the example code in [src/main.tsx](./src/App.tsx)
## 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,41 @@
{
"name": "example-webapp-react-socialquery",
"private": true,
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc --noEmit && vite build",
"lint": "eslint .",
"preview": "vite preview",
"build:ldo": "../../../ldo/packages/cli/dist/index.js build --input src/.shapes --output src/.ldo"
},
"dependencies": {
"@heroicons/react": "^2.2.0",
"@ldo/connected-nextgraph": "^1.0.0-alpha.11",
"@ldo/ldo": "^1.0.0-alpha.11",
"@ldo/react": "^1.0.0-alpha.11",
"nextgraphweb": "^0.1.1-alpha.4",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@eslint/js": "^9.22.0",
"@ldo/cli": "^1.0.0-alpha.11",
"@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,119 @@
import { LdoJsonldContext } from "@ldo/ldo";
/**
* =============================================================================
* contactContext: JSONLD Context for contact
* =============================================================================
*/
export const contactContext: LdoJsonldContext = {
type: {
"@id": "@type",
},
Individual: {
"@id": "http://www.w3.org/2006/vcard/ns#Individual",
"@context": {
type: {
"@id": "@type",
},
fn: {
"@id": "http://www.w3.org/2006/vcard/ns#fn",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
hasEmail: {
"@id": "http://www.w3.org/2006/vcard/ns#hasEmail",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
hasRating: {
"@id": "did:ng:x:skills#hasRating",
"@type": "@id",
"@isCollection": true,
},
},
},
Person: {
"@id": "http://schema.org/Person",
"@context": {
type: {
"@id": "@type",
},
fn: {
"@id": "http://www.w3.org/2006/vcard/ns#fn",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
hasEmail: {
"@id": "http://www.w3.org/2006/vcard/ns#hasEmail",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
hasRating: {
"@id": "did:ng:x:skills#hasRating",
"@type": "@id",
"@isCollection": true,
},
},
},
Person2: {
"@id": "http://xmlns.com/foaf/0.1/Person",
"@context": {
type: {
"@id": "@type",
},
fn: {
"@id": "http://www.w3.org/2006/vcard/ns#fn",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
hasEmail: {
"@id": "http://www.w3.org/2006/vcard/ns#hasEmail",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
hasRating: {
"@id": "did:ng:x:skills#hasRating",
"@type": "@id",
"@isCollection": true,
},
},
},
fn: {
"@id": "http://www.w3.org/2006/vcard/ns#fn",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
hasEmail: {
"@id": "http://www.w3.org/2006/vcard/ns#hasEmail",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
hasRating: {
"@id": "did:ng:x:skills#hasRating",
"@type": "@id",
"@isCollection": true,
},
Rating: {
"@id": "did:ng:x:skills#Rating",
"@context": {
type: {
"@id": "@type",
},
rated: {
"@id": "did:ng:x:skills#rated",
"@type": "http://www.w3.org/2001/XMLSchema#integer",
},
skill: {
"@id": "did:ng:x:skills#skill",
},
},
},
rated: {
"@id": "did:ng:x:skills#rated",
"@type": "http://www.w3.org/2001/XMLSchema#integer",
},
skill: {
"@id": "did:ng:x:skills#skill",
},
"ng:k:skills:programming:svelte": "did:ng:k:skills:programming:svelte",
"ng:k:skills:programming:nextjs": "did:ng:k:skills:programming:nextjs",
"ng:k:skills:programming:react": "did:ng:k:skills:programming:react",
"ng:k:skills:programming:vuejs": "did:ng:k:skills:programming:vuejs",
"ng:k:skills:programming:tailwind": "did:ng:k:skills:programming:tailwind",
"ng:k:skills:programming:rdf": "did:ng:k:skills:programming:rdf",
"ng:k:skills:programming:rust": "did:ng:k:skills:programming:rust",
"ng:k:skills:programming:yjs": "did:ng:k:skills:programming:yjs",
"ng:k:skills:programming:automerge": "did:ng:k:skills:programming:automerge",
};

@ -0,0 +1,166 @@
import { Schema } from "shexj";
/**
* =============================================================================
* contactSchema: ShexJ Schema for contact
* =============================================================================
*/
export const contactSchema: Schema = {
type: "Schema",
shapes: [
{
id: "did:ng:x:class#SocialContact",
type: "ShapeDecl",
shapeExpr: {
type: "Shape",
expression: {
type: "EachOf",
expressions: [
{
type: "TripleConstraint",
predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
valueExpr: {
type: "NodeConstraint",
values: ["http://www.w3.org/2006/vcard/ns#Individual"],
},
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "Defines the node as an Individual (from vcard)",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
valueExpr: {
type: "NodeConstraint",
values: ["http://schema.org/Person"],
},
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "Defines the node as a Person (from Schema.org)",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
valueExpr: {
type: "NodeConstraint",
values: ["http://xmlns.com/foaf/0.1/Person"],
},
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "Defines the node as a Person (from foaf)",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/2006/vcard/ns#fn",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#string",
},
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"The formatted name of a person. Example: John Smith",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/2006/vcard/ns#hasEmail",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#string",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "The person's email.",
},
},
],
},
{
type: "TripleConstraint",
predicate: "did:ng:x:skills#hasRating",
valueExpr: "did:ng:x:class#HasRating",
min: 0,
max: -1,
},
],
},
extra: ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"],
},
},
{
id: "did:ng:x:class#HasRating",
type: "ShapeDecl",
shapeExpr: {
type: "Shape",
expression: {
type: "EachOf",
expressions: [
{
type: "TripleConstraint",
predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
valueExpr: {
type: "NodeConstraint",
values: ["did:ng:x:skills#Rating"],
},
},
{
type: "TripleConstraint",
predicate: "did:ng:x:skills#rated",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#integer",
},
},
{
type: "TripleConstraint",
predicate: "did:ng:x:skills#skill",
valueExpr: {
type: "NodeConstraint",
values: [
"did:ng:k:skills:programming:svelte",
"did:ng:k:skills:programming:nextjs",
"did:ng:k:skills:programming:react",
"did:ng:k:skills:programming:vuejs",
"did:ng:k:skills:programming:tailwind",
"did:ng:k:skills:programming:rdf",
"did:ng:k:skills:programming:rust",
"did:ng:k:skills:programming:yjs",
"did:ng:k:skills:programming:automerge",
],
},
},
],
},
},
},
],
};

@ -0,0 +1,28 @@
import { ShapeType } from "@ldo/ldo";
import { contactSchema } from "./contact.schema";
import { contactContext } from "./contact.context";
import { SocialContact, HasRating } from "./contact.typings";
/**
* =============================================================================
* LDO ShapeTypes contact
* =============================================================================
*/
/**
* SocialContact ShapeType
*/
export const SocialContactShapeType: ShapeType<SocialContact> = {
schema: contactSchema,
shape: "did:ng:x:class#SocialContact",
context: contactContext,
};
/**
* HasRating ShapeType
*/
export const HasRatingShapeType: ShapeType<HasRating> = {
schema: contactSchema,
shape: "did:ng:x:class#HasRating",
context: contactContext,
};

@ -0,0 +1,78 @@
import { LdoJsonldContext, LdSet } from "@ldo/ldo";
/**
* =============================================================================
* Typescript Typings for contact
* =============================================================================
*/
/**
* SocialContact Type
*/
export interface SocialContact {
"@id"?: string;
"@context"?: LdoJsonldContext;
/**
* Defines the node as an Individual (from vcard) | Defines the node as a Person (from Schema.org) | Defines the node as a Person (from foaf)
*/
type: LdSet<
| {
"@id": "Individual";
}
| {
"@id": "Person";
}
| {
"@id": "Person2";
}
>;
/**
* The formatted name of a person. Example: John Smith
*/
fn: string;
/**
* The person's email.
*/
hasEmail?: string;
hasRating?: LdSet<HasRating>;
}
/**
* HasRating Type
*/
export interface HasRating {
"@id"?: string;
"@context"?: LdoJsonldContext;
type: {
"@id": "Rating";
};
rated: number;
skill:
| {
"@id": "ng:k:skills:programming:svelte";
}
| {
"@id": "ng:k:skills:programming:nextjs";
}
| {
"@id": "ng:k:skills:programming:react";
}
| {
"@id": "ng:k:skills:programming:vuejs";
}
| {
"@id": "ng:k:skills:programming:tailwind";
}
| {
"@id": "ng:k:skills:programming:rdf";
}
| {
"@id": "ng:k:skills:programming:rust";
}
| {
"@id": "ng:k:skills:programming:yjs";
}
| {
"@id": "ng:k:skills:programming:automerge";
};
}

@ -0,0 +1,82 @@
import { LdoJsonldContext } from "@ldo/ldo";
/**
* =============================================================================
* containerContext: JSONLD Context for container
* =============================================================================
*/
export const containerContext: LdoJsonldContext = {
type: {
"@id": "@type",
"@isCollection": true,
},
Container: {
"@id": "http://www.w3.org/ns/ldp#Container",
"@context": {
type: {
"@id": "@type",
"@isCollection": true,
},
modified: {
"@id": "http://purl.org/dc/terms/modified",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
contains: {
"@id": "http://www.w3.org/ns/ldp#contains",
"@type": "@id",
"@isCollection": true,
},
mtime: {
"@id": "http://www.w3.org/ns/posix/stat#mtime",
"@type": "http://www.w3.org/2001/XMLSchema#decimal",
},
size: {
"@id": "http://www.w3.org/ns/posix/stat#size",
"@type": "http://www.w3.org/2001/XMLSchema#integer",
},
},
},
Resource: {
"@id": "http://www.w3.org/ns/ldp#Resource",
"@context": {
type: {
"@id": "@type",
"@isCollection": true,
},
modified: {
"@id": "http://purl.org/dc/terms/modified",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
contains: {
"@id": "http://www.w3.org/ns/ldp#contains",
"@type": "@id",
"@isCollection": true,
},
mtime: {
"@id": "http://www.w3.org/ns/posix/stat#mtime",
"@type": "http://www.w3.org/2001/XMLSchema#decimal",
},
size: {
"@id": "http://www.w3.org/ns/posix/stat#size",
"@type": "http://www.w3.org/2001/XMLSchema#integer",
},
},
},
modified: {
"@id": "http://purl.org/dc/terms/modified",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
contains: {
"@id": "http://www.w3.org/ns/ldp#contains",
"@type": "@id",
"@isCollection": true,
},
mtime: {
"@id": "http://www.w3.org/ns/posix/stat#mtime",
"@type": "http://www.w3.org/2001/XMLSchema#decimal",
},
size: {
"@id": "http://www.w3.org/ns/posix/stat#size",
"@type": "http://www.w3.org/2001/XMLSchema#integer",
},
};

@ -0,0 +1,124 @@
import { Schema } from "shexj";
/**
* =============================================================================
* containerSchema: ShexJ Schema for container
* =============================================================================
*/
export const containerSchema: Schema = {
type: "Schema",
shapes: [
{
id: "http://www.w3.org/ns/lddps#Container",
type: "ShapeDecl",
shapeExpr: {
type: "Shape",
expression: {
id: "http://www.w3.org/ns/lddps#ContainerShape",
type: "EachOf",
expressions: [
{
type: "TripleConstraint",
predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
valueExpr: {
type: "NodeConstraint",
values: [
"http://www.w3.org/ns/ldp#Container",
"http://www.w3.org/ns/ldp#Resource",
],
},
min: 0,
max: -1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "A container",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://purl.org/dc/terms/modified",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#string",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "Date modified",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/ldp#contains",
valueExpr: {
type: "NodeConstraint",
nodeKind: "iri",
},
min: 0,
max: -1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "Defines a Resource",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/posix/stat#mtime",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#decimal",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "?",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/posix/stat#size",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#integer",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "size of this container",
},
},
],
},
],
},
extra: ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"],
},
},
],
};

@ -0,0 +1,19 @@
import { ShapeType } from "@ldo/ldo";
import { containerSchema } from "./container.schema";
import { containerContext } from "./container.context";
import { Container } from "./container.typings";
/**
* =============================================================================
* LDO ShapeTypes container
* =============================================================================
*/
/**
* Container ShapeType
*/
export const ContainerShapeType: ShapeType<Container> = {
schema: containerSchema,
shape: "http://www.w3.org/ns/lddps#Container",
context: containerContext,
};

@ -0,0 +1,44 @@
import { LdoJsonldContext, LdSet } from "@ldo/ldo";
/**
* =============================================================================
* Typescript Typings for container
* =============================================================================
*/
/**
* Container Type
*/
export interface Container {
"@id"?: string;
"@context"?: LdoJsonldContext;
/**
* A container
*/
type?: LdSet<
| {
"@id": "Container";
}
| {
"@id": "Resource";
}
>;
/**
* Date modified
*/
modified?: string;
/**
* Defines a Resource
*/
contains?: LdSet<{
"@id": string;
}>;
/**
* ?
*/
mtime?: number;
/**
* size of this container
*/
size?: number;
}

@ -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,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;
}
#save {
background-color:rgb(73, 114, 165);
color:white;
cursor:pointer;
}

@ -0,0 +1,22 @@
import React, { FunctionComponent } from 'react';
import { Header } from './Header';
import { Contacts } from './Contacts';
import { BrowserNGLdoProvider } from './reactMethods';
import './App.css'
import "../../../common/src/styles.css";
const App: FunctionComponent = () => {
return (
<div className="App">
<BrowserNGLdoProvider>
<Header />
<Contacts />
</BrowserNGLdoProvider>
</div>
);
}
export default App

@ -0,0 +1,118 @@
import { default as React, FunctionComponent } from "react";
import { useNextGraphAuth } from "./reactMethods";
import { SocialContactShapeType } from "./.ldo/contact.shapeTypes.ts";
import { useSubscribeToResource, useResource, useSubject } from "./reactMethods.ts";
import { StarIcon } from '@heroicons/react/24/solid'
import { StarIcon as StarIconOutline, NoSymbolIcon } from '@heroicons/react/24/outline'
export const Contact: FunctionComponent = ({nuri}) => {
const { session } = useNextGraphAuth();
useResource(session.sessionId && nuri ? nuri : undefined, { subscribe: true });
let contact = useSubject(SocialContactShapeType, session.sessionId && nuri ? nuri.substring(0,53) : undefined);
const ksp = "did:ng:k:skills:programming:";
const ksp_mapping = [
"svelte",
"nextjs",
"react",
"vuejs",
"tailwind",
"rdf",
"rust",
"yjs",
"automerge",
];
const ksp_name = [
"Svelte",
"NextJS",
"React",
"VueJS",
"Tailwind",
"RDF/SPARQL",
"Rust",
"Yjs",
"Automerge",
]
const [skills, setSkills] = React.useState([
0,
0,
5,
3,
1,
2,
4,
0,
0,
])
React.useEffect(() => {
console.log(contact.hasRating?.entries())
let nextSkills = skills.map((s) => {
return 0;
});
contact.hasRating?.map((r) => {
nextSkills[ksp_mapping.indexOf(r.skill["@id"].substring(28))] = r.rated +1;
});
setSkills(nextSkills);
}, [contact])
if (!session.sessionId || !nuri) return <></>;
function rate(skill: number, rating: number) {
console.log("rate", skill, rating);
const nextSkills = skills.map((s, i) => {
if (i === skill) {
if (s == rating) {
s = s - 1;
} else {
s = rating;
}
return s;
} else {
return s;
}
});
setSkills(nextSkills);
}
return <>
{contact.fn? (
<div className="contact text-left flex flex-col" title={nuri}>
<div className="name text-center">
{contact.fn}
</div>
<div className="p-2">
<span className="email">
{contact.hasEmail}
{JSON.stringify(contact.hasRating?.entries())}
</span>
</div>
{
skills.map(
(skill,s) =>
<div key={s} className="px-2 flex flex-row cursor-pointer text-yellow-500">
{/* <NoSymbolIcon className="size-6"/> */}
{
[...Array(5)].map(
(e,i) => {
if (i + 1 <= skill) {
return <StarIcon key={s * 10 + i} onClick={()=>rate(s,i+1)} className="size-6"/>
} else {
return <StarIconOutline key={s * 10 + i} onClick={()=>rate(s,i+1)} className=" size-6"/>
}
}
)
}
<span className="text-black ml-2">{ksp_name[s]}</span>
</div>
)
}
</div>
) : <></>}
</>;
};

@ -0,0 +1,38 @@
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";
export const Contacts: FunctionComponent = () => {
const { session } = useNextGraphAuth();
let container_overlay: string;
useResource(session.sessionId ? "did:ng:"+session.privateStoreId : undefined, { subscribe: true });
let myContainer = useSubject(ContainerShapeType, session.sessionId ? "did:ng:"+(session.privateStoreId.substring(0,46)) : undefined);
if (session.sessionId) {
container_overlay = session.privateStoreId.substring(46) as string;
}
if (!session.sessionId) return <></>;
return <>
<div className="centered">
<div className="flex flex-wrap justify-center gap-5 mt-10 mb-10">
<MakeContact/>
</div>
<div className="flex flex-wrap justify-center gap-5 mb-10">
{
myContainer.contains?.map(
(contained) =>
<Contact key={contained["@id"]} nuri={contained["@id"]+container_overlay}/>
)
}
</div>
</div>
</>;
};

@ -0,0 +1,45 @@
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 className="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,60 @@
import { FormEvent, FunctionComponent, useCallback, useState } from "react";
import { BrowserNGLdoProvider, useLdo, dataset } from './reactMethods';
import { SocialContactShapeType } from "./.ldo/contact.shapeTypes.ts";
import { LdSet } from "@ldo/ldo";
export const MakeContact: FunctionComponent = () => {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const { createData, commitData } = useLdo();
const onSubmit = useCallback(
async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const new_name = name.trim();
const new_email = email.trim();
if (new_name.trim().length > 2 && new_email.trim().length > 6 && new_email.indexOf("@") >= 0) {
setName("");
setEmail("");
const resource = await dataset.createResource("nextgraph");
if (!resource.isError) {
//console.log("Created resource:", resource.uri);
const contact = createData(
SocialContactShapeType,
resource.uri.substring(0,53),
resource
);
contact.type = { "@id": "Individual" };
contact.fn = new_name;
contact.hasEmail = new_email;
const result = await commitData(contact);
if (result.isError) {
console.error(result.message);
}
}
}
},
[name, email]
);
return (
<form onSubmit={onSubmit}>
<input
type="text"
placeholder="Enter name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<input
type="text"
placeholder="Enter email address"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input type="submit" id="save" value="Save" />
</form>
);
};

@ -0,0 +1,20 @@
import { createContext, useContext } from "react";
/**
* Functions for authenticating with NextGraph
*/
export interface NGWalletAuthFunctions {
login: () => Promise<void>;
logout: () => Promise<void>;
session: unknown;
ranInitialAuthCheck: boolean;
}
// There is no initial value for this context. It will be given in the provider
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
export const NextGraphAuthContext = createContext<NGWalletAuthFunctions>(undefined);
export function useNextGraphAuth(): NGWalletAuthFunctions {
return useContext(NextGraphAuthContext);
}

@ -0,0 +1,112 @@
import React, { useCallback, useEffect, useMemo, useState } from "react";
import type { FunctionComponent, PropsWithChildren } from "react";
import { NextGraphAuthContext, useNextGraphAuth } from "./NextGraphAuthContext";
import {default as ng, init} from "nextgraphweb";
import type { ConnectedLdoDataset, ConnectedPlugin } from "@ldo/connected";
import type { NextGraphConnectedPlugin, NextGraphConnectedContext } from "@ldo/connected-nextgraph";
/**
* Creates special react methods specific to the NextGraph Auth
* @param dataset the connectedLdoDataset with a nextGraphConnectedPlugin
* @returns { BrowserNGLdoProvider, useNextGraphAuth }
*/
export function createBrowserNGReactMethods(
dataset: ConnectedLdoDataset<(NextGraphConnectedPlugin | ConnectedPlugin)[]>,
) {
const BrowserNGLdoProvider: FunctionComponent<PropsWithChildren> = ({
children,
}) => {
const [session, setSession] = useState<NextGraphConnectedContext>(
{
ng: undefined,
}
);
const [ranInitialAuthCheck, setRanInitialAuthCheck] = useState(false);
const runInitialAuthCheck = useCallback(async () => {
//console.log("runInitialAuthCheck called", ranInitialAuthCheck)
if (ranInitialAuthCheck) return;
//console.log("init called");
setRanInitialAuthCheck(true);
// TODO: export the types for the session object coming from NG.
await init( (event: { status: string; session: { session_id: unknown; protected_store_id: unknown; private_store_id: unknown; public_store_id: unknown; }; }) => {
//console.log("called back in react", event)
// callback
// once you receive event.status == "loggedin"
// you can use the full API
if (event.status == "loggedin") {
setSession({
ng,
sessionId: event.session.session_id as string, //FIXME: sessionId should be a Number.
protectedStoreId: event.session.protected_store_id as string,
privateStoreId: event.session.private_store_id as string,
publicStoreId: event.session.public_store_id as string
}); // TODO: add event.session.user too
dataset.setContext("nextgraph", {
ng,
sessionId: event.session.session_id as string
});
}
else if (event.status == "cancelled" || event.status == "error" || event.status == "loggedout") {
setSession({ ng: undefined });
dataset.setContext("nextgraph", {
ng: undefined,
});
}
}
, true // singleton: boolean (will your app create many docs in the system, or should it be launched as a unique instance)
, []); //list of AccessRequests (for now, leave this empty)
}, []);
const login = useCallback(
async () => {
await ng.login();
},
[],
);
const logout = useCallback(async () => {
await ng.logout();
}, []);
useEffect(() => {
runInitialAuthCheck();
}, []);
const nextGraphAuthFunctions = useMemo(
() => ({
runInitialAuthCheck,
login,
logout,
session,
ranInitialAuthCheck,
}),
[
login,
logout,
ranInitialAuthCheck,
runInitialAuthCheck,
session,
],
);
return (
<NextGraphAuthContext.Provider value={nextGraphAuthFunctions}>
{children}
</NextGraphAuthContext.Provider>
);
};
return {
BrowserNGLdoProvider,
useNextGraphAuth: useNextGraphAuth
};
};

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

@ -0,0 +1,10 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'
createRoot(document.getElementById('app')!).render(
// <StrictMode>
<App />
// </StrictMode>,
)

@ -0,0 +1,18 @@
import { nextGraphConnectedPlugin } from "@ldo/connected-nextgraph";
import { createLdoReactMethods } from "@ldo/react";
import { createBrowserNGReactMethods } from "./createBrowserNGReactMethods";
export const {
dataset,
useLdo,
useMatchObject,
useMatchSubject,
useResource,
useSubject,
useSubscribeToResource,
} = createLdoReactMethods([nextGraphConnectedPlugin]);
const methods = createBrowserNGReactMethods(dataset);
export const { BrowserNGLdoProvider, useNextGraphAuth } = methods;

@ -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()],
})

@ -11,16 +11,16 @@
"build:ldo": "ldo build --input src/.shapes --output src/.ldo"
},
"dependencies": {
"@ldo/connected-nextgraph": "^1.0.0-alpha.8",
"@ldo/ldo": "^1.0.0-alpha.3",
"@ldo/react": "^1.0.0-alpha.3",
"@ldo/connected-nextgraph": "^1.0.0-alpha.11",
"@ldo/ldo": "^1.0.0-alpha.11",
"@ldo/react": "^1.0.0-alpha.11",
"nextgraphweb": "^0.1.1-alpha.4",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@eslint/js": "^9.22.0",
"@ldo/cli": "^1.0.0-alpha.3",
"@ldo/cli": "^1.0.0-alpha.11",
"@types/jsonld": "^1.5.15",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",

@ -9,7 +9,7 @@ export const contactSchema: Schema = {
type: "Schema",
shapes: [
{
id: "did:ng:n:g:x:social:contact#NGSocialContact",
id: "did:ng:n:g:x:social:contact#SocialContact",
type: "ShapeDecl",
shapeExpr: {
type: "Shape",

@ -1,7 +1,7 @@
import { ShapeType } from "@ldo/ldo";
import { contactSchema } from "./contact.schema";
import { contactContext } from "./contact.context";
import { NGSocialContact } from "./contact.typings";
import { SocialContact } from "./contact.typings";
/**
* =============================================================================
@ -10,10 +10,10 @@ import { NGSocialContact } from "./contact.typings";
*/
/**
* NGSocialContact ShapeType
* SocialContact ShapeType
*/
export const NGSocialContactShapeType: ShapeType<NGSocialContact> = {
export const SocialContactShapeType: ShapeType<SocialContact> = {
schema: contactSchema,
shape: "did:ng:n:g:x:social:contact#NGSocialContact",
shape: "did:ng:n:g:x:social:contact#SocialContact",
context: contactContext,
};

@ -7,9 +7,9 @@ import { LdoJsonldContext, LdSet } from "@ldo/ldo";
*/
/**
* NGSocialContact Type
* SocialContact Type
*/
export interface NGSocialContact {
export interface SocialContact {
"@id"?: string;
"@context"?: LdoJsonldContext;
/**

@ -12,7 +12,7 @@ PREFIX schem: <http://schema.org/>
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX ngx: <did:ng:n:g:x:social:contact#>
ngx:NGSocialContact EXTRA a {
ngx:SocialContact EXTRA a {
a [vcard:Individual ]
// rdfs:comment "Defines the node as an Individual (from vcard)" ;
a [ schem:Person ]

@ -1,13 +1,13 @@
import { FunctionComponent } from "react";
import { useNextGraphAuth } from "./reactMethods";
import { NGSocialContactShapeType } from "./.ldo/contact.shapeTypes.ts";
import { SocialContactShapeType } from "./.ldo/contact.shapeTypes.ts";
import { useSubscribeToResource, useResource, useSubject } from "./reactMethods.ts";
export const Contact: FunctionComponent = ({nuri}) => {
const { session } = useNextGraphAuth();
useResource(session.sessionId && nuri ? nuri : undefined, { subscribe: true });
let contact = useSubject(NGSocialContactShapeType, session.sessionId && nuri ? nuri.substring(0,53) : undefined);
let contact = useSubject(SocialContactShapeType, session.sessionId && nuri ? nuri.substring(0,53) : undefined);
if (!session.sessionId || !nuri) return <></>;

@ -1,6 +1,6 @@
import { FormEvent, FunctionComponent, useCallback, useState } from "react";
import { BrowserNGLdoProvider, useLdo, dataset } from './reactMethods';
import { NGSocialContactShapeType } from "./.ldo/contact.shapeTypes.ts";
import { SocialContactShapeType } from "./.ldo/contact.shapeTypes.ts";
import { LdSet } from "@ldo/ldo";
export const MakeContact: FunctionComponent = () => {
@ -22,7 +22,7 @@ export const MakeContact: FunctionComponent = () => {
//console.log("Created resource:", resource.uri);
const contact = createData(
NGSocialContactShapeType,
SocialContactShapeType,
resource.uri.substring(0,53),
resource
);

Loading…
Cancel
Save