commit
b2dacdc0fd
@ -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,38 @@ |
||||
# demo-berlin-final |
||||
|
||||
Example of a Web app made with NextGraph, using React and LDO, and Vite. |
||||
|
||||
It demonstrate the feature of Social Queries. |
||||
|
||||
This is the final working version. |
||||
|
||||
## 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,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,44 @@ |
||||
import { LdoJsonldContext } from "@ldo/ldo"; |
||||
|
||||
/** |
||||
* ============================================================================= |
||||
* socialqueryContext: JSONLD Context for socialquery |
||||
* ============================================================================= |
||||
*/ |
||||
export const socialqueryContext: LdoJsonldContext = { |
||||
type: { |
||||
"@id": "@type", |
||||
}, |
||||
SocialQuery: { |
||||
"@id": "did:ng:x:class#SocialQuery", |
||||
"@context": { |
||||
type: { |
||||
"@id": "@type", |
||||
}, |
||||
socialQuerySparql: { |
||||
"@id": "did:ng:x:ng#social_query_sparql", |
||||
"@type": "http://www.w3.org/2001/XMLSchema#string", |
||||
}, |
||||
socialQueryForwarder: { |
||||
"@id": "did:ng:x:ng#social_query_forwarder", |
||||
"@type": "@id", |
||||
}, |
||||
socialQueryEnded: { |
||||
"@id": "did:ng:x:ng#social_query_ended", |
||||
"@type": "http://www.w3.org/2001/XMLSchema#dateTime", |
||||
}, |
||||
}, |
||||
}, |
||||
socialQuerySparql: { |
||||
"@id": "did:ng:x:ng#social_query_sparql", |
||||
"@type": "http://www.w3.org/2001/XMLSchema#string", |
||||
}, |
||||
socialQueryForwarder: { |
||||
"@id": "did:ng:x:ng#social_query_forwarder", |
||||
"@type": "@id", |
||||
}, |
||||
socialQueryEnded: { |
||||
"@id": "did:ng:x:ng#social_query_ended", |
||||
"@type": "http://www.w3.org/2001/XMLSchema#dateTime", |
||||
}, |
||||
}; |
@ -0,0 +1,63 @@ |
||||
import { Schema } from "shexj"; |
||||
|
||||
/** |
||||
* ============================================================================= |
||||
* socialquerySchema: ShexJ Schema for socialquery |
||||
* ============================================================================= |
||||
*/ |
||||
export const socialquerySchema: Schema = { |
||||
type: "Schema", |
||||
shapes: [ |
||||
{ |
||||
id: "did:ng:x:shape#SocialQuery", |
||||
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:class#SocialQuery"], |
||||
}, |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "did:ng:x:ng#social_query_sparql", |
||||
valueExpr: { |
||||
type: "NodeConstraint", |
||||
datatype: "http://www.w3.org/2001/XMLSchema#string", |
||||
}, |
||||
min: 0, |
||||
max: 1, |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "did:ng:x:ng#social_query_forwarder", |
||||
valueExpr: { |
||||
type: "NodeConstraint", |
||||
nodeKind: "iri", |
||||
}, |
||||
min: 0, |
||||
max: 1, |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "did:ng:x:ng#social_query_ended", |
||||
valueExpr: { |
||||
type: "NodeConstraint", |
||||
datatype: "http://www.w3.org/2001/XMLSchema#dateTime", |
||||
}, |
||||
min: 0, |
||||
max: 1, |
||||
}, |
||||
], |
||||
}, |
||||
extra: ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"], |
||||
}, |
||||
}, |
||||
], |
||||
}; |
@ -0,0 +1,19 @@ |
||||
import { ShapeType } from "@ldo/ldo"; |
||||
import { socialquerySchema } from "./socialquery.schema"; |
||||
import { socialqueryContext } from "./socialquery.context"; |
||||
import { SocialQuery } from "./socialquery.typings"; |
||||
|
||||
/** |
||||
* ============================================================================= |
||||
* LDO ShapeTypes socialquery |
||||
* ============================================================================= |
||||
*/ |
||||
|
||||
/** |
||||
* SocialQuery ShapeType |
||||
*/ |
||||
export const SocialQueryShapeType: ShapeType<SocialQuery> = { |
||||
schema: socialquerySchema, |
||||
shape: "did:ng:x:shape#SocialQuery", |
||||
context: socialqueryContext, |
||||
}; |
@ -0,0 +1,23 @@ |
||||
import { LdoJsonldContext, LdSet } from "@ldo/ldo"; |
||||
|
||||
/** |
||||
* ============================================================================= |
||||
* Typescript Typings for socialquery |
||||
* ============================================================================= |
||||
*/ |
||||
|
||||
/** |
||||
* SocialQuery Type |
||||
*/ |
||||
export interface SocialQuery { |
||||
"@id"?: string; |
||||
"@context"?: LdoJsonldContext; |
||||
type: { |
||||
"@id": "SocialQuery"; |
||||
}; |
||||
socialQuerySparql?: string; |
||||
socialQueryForwarder?: { |
||||
"@id": string; |
||||
}; |
||||
socialQueryEnded?: string; |
||||
} |
@ -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,218 @@ |
||||
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}) => { |
||||
const { session } = useNextGraphAuth(); |
||||
const { createData, commitData, changeData, getResource } = useLdo(); |
||||
useResource(session.sessionId && nuri ? nuri : undefined, { subscribe: true }); |
||||
let contact = useSubject(SocialContactShapeType, session.sessionId && nuri ? nuri.substring(0,53) : undefined); |
||||
//console.log(nuri)
|
||||
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, 0, 0, 0, 0, 0, 0, 0 ]); |
||||
|
||||
React.useEffect(() => { |
||||
let nextSkills = skills.map((s) => { |
||||
return 0; |
||||
}); |
||||
contact.hasRating?.map((r) => { |
||||
nextSkills[ksp_mapping.indexOf(r.skill["@id"].substring(24))] = r.rated +1; |
||||
}); |
||||
setSkills(nextSkills); |
||||
}, [contact]) |
||||
|
||||
if (!session.sessionId || !nuri) return <></>; |
||||
|
||||
async function rate(skill: number, rating: number) { |
||||
const nextSkills = [...skills]; |
||||
const old = skills[skill]; |
||||
|
||||
if (old == 0) { |
||||
if (rating == 0) return; //shouldn't happen
|
||||
|
||||
// we create a new rating
|
||||
nextSkills[skill] = rating; |
||||
let re = getResource(nuri); |
||||
let editing_contact = changeData(contact, re); |
||||
editing_contact.hasRating.add({ |
||||
rated: rating-1, |
||||
skill: { |
||||
"@id": ksp + ksp_mapping[skill] |
||||
} |
||||
}) |
||||
let res = await commitData(editing_contact); |
||||
if (res.isError) { |
||||
console.error(res.message); |
||||
} |
||||
|
||||
// try {
|
||||
// await session.ng.sparql_update(session.sessionId, `PREFIX xskills: <did:ng:x:skills#>
|
||||
// PREFIX ksp: <did:ng:k:skills:programming:>
|
||||
// PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
|
||||
// INSERT {
|
||||
// <> xskills:hasRating [
|
||||
// a xskills:Rating ;
|
||||
// xskills:rated "${rating-1}"^^xsd:integer ;
|
||||
// xskills:skill ksp:${ksp_mapping[skill]}
|
||||
// ].
|
||||
// } WHERE {}`, nuri);
|
||||
// } catch (e) {
|
||||
// console.error(e)
|
||||
// }
|
||||
} else { |
||||
if (old == rating) { |
||||
nextSkills[skill] = rating - 1; |
||||
} else { |
||||
nextSkills[skill] = rating; |
||||
} |
||||
if (nextSkills[skill] == 0) { |
||||
// we remove the rating
|
||||
|
||||
let re = getResource(nuri); |
||||
for (const r of contact.hasRating.values()) { |
||||
if (r.skill["@id"].substring(24) === ksp_mapping[skill]) { |
||||
let editing_contact = changeData(contact, re); |
||||
editing_contact.hasRating?.delete(r); |
||||
// let changes = transactionChanges(editing_contact);
|
||||
// console.log(changes.added?.toString())
|
||||
// console.log(changes.removed?.toString())
|
||||
let res = await commitData(editing_contact); |
||||
if (res.isError) { |
||||
console.error(res.message); |
||||
} |
||||
let editing_rating = changeData(r, re); |
||||
delete editing_rating["@id"]; |
||||
// changes = transactionChanges(editing_rating);
|
||||
// console.log(changes.added?.toString())
|
||||
// console.log(changes.removed?.toString())
|
||||
res = await commitData(editing_rating); |
||||
if (res.isError) { |
||||
console.error(res.message); |
||||
} |
||||
break; |
||||
} |
||||
}; |
||||
|
||||
// try { const s = `PREFIX xskills: <did:ng:x:skills#>
|
||||
// PREFIX ksp: <did:ng:k:skills:programming:>
|
||||
// PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
|
||||
// DELETE {
|
||||
// <> xskills:hasRating ?rating.
|
||||
// ?rating a xskills:Rating .
|
||||
// ?rating xskills:skill ksp:${ksp_mapping[skill]} .
|
||||
// ?rating xskills:rated "${old-1}"^^xsd:integer.
|
||||
// } WHERE { <> xskills:hasRating ?rating .
|
||||
// ?rating a xskills:Rating .
|
||||
// ?rating xskills:skill ksp:${ksp_mapping[skill]}
|
||||
// }`;
|
||||
// await session.ng.sparql_update(session.sessionId, s, nuri);
|
||||
// } catch (e) {
|
||||
// console.error(e)
|
||||
// }
|
||||
} else { |
||||
// we update the rating
|
||||
try { |
||||
|
||||
let re = getResource(nuri); |
||||
for (const r of contact.hasRating.values()) { |
||||
if (r.skill["@id"].substring(24) === ksp_mapping[skill]) { |
||||
let rating = changeData(r, re); |
||||
rating.rated = nextSkills[skill]-1; |
||||
// const changes = transactionChanges(rating);
|
||||
// console.log(changes.added?.toString())
|
||||
// console.log(changes.removed?.toString())
|
||||
const res = await commitData(rating); |
||||
if (res.isError) { |
||||
console.error(res.message); |
||||
} |
||||
break; |
||||
} |
||||
}; |
||||
|
||||
// await session.ng.sparql_update(session.sessionId, `PREFIX xskills: <did:ng:x:skills#>
|
||||
// PREFIX ksp: <did:ng:k:skills:programming:>
|
||||
// PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
|
||||
// DELETE {
|
||||
// ?rating xskills:rated "${old-1}"^^xsd:integer.
|
||||
// } INSERT {
|
||||
// ?rating xskills:rated "${nextSkills[skill]-1}"^^xsd:integer.
|
||||
// }
|
||||
// WHERE { <> xskills:hasRating ?rating .
|
||||
// ?rating a xskills:Rating .
|
||||
// ?rating xskills:skill ksp:${ksp_mapping[skill]}
|
||||
// }`, nuri);
|
||||
} catch (e) { |
||||
console.error(e) |
||||
} |
||||
} |
||||
} |
||||
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} |
||||
</span> |
||||
</div> |
||||
{ |
||||
skills.map( |
||||
(skill,s) =>
|
||||
<div key={s} className="px-2 flex flex-row cursor-pointer text-yellow-500"> |
||||
{ |
||||
[...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,42 @@ |
||||
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(); |
||||
|
||||
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-5"> |
||||
<MakeContact/> |
||||
</div> |
||||
<div className="flex flex-wrap justify-center gap-5 mt-10 mb-10"> |
||||
<Link to="/query"><button className="button"><LifebuoyIcon className="size-7 inline"/> Query</button> </Link> |
||||
</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,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,61 @@ |
||||
import { FormEvent, FunctionComponent, useCallback, useState } from "react"; |
||||
import { useLdo, dataset, useNextGraphAuth } from './reactMethods'; |
||||
import { SocialContactShapeType } from "./.ldo/contact.shapeTypes.ts"; |
||||
|
||||
export const MakeContact: FunctionComponent = () => { |
||||
const [name, setName] = useState(""); |
||||
const [email, setEmail] = useState(""); |
||||
|
||||
const { createData, commitData } = useLdo(); |
||||
const { session } = useNextGraphAuth(); |
||||
|
||||
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", { primaryClass: "social:contact" }); |
||||
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); |
||||
} |
||||
await session.ng.update_header(session.sessionId, resource.uri.substring(0,53), new_name); |
||||
} |
||||
} |
||||
}, |
||||
[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" className="button" value="Add" /> |
||||
</form> |
||||
); |
||||
}; |
@ -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