initial commit

main
Niko PLP 2 weeks ago
commit 17a7ea706c
  1. 24
      .gitignore
  2. 36
      README.md
  3. 28
      eslint.config.js
  4. 12
      index.html
  5. 6229
      package-lock.json
  6. 45
      package.json
  7. 6
      postcss.config.js
  8. 35
      src/.shapes/contact.shex
  9. 24
      src/.shapes/container.shex
  10. 18
      src/.shapes/socialquery.shex
  11. 46
      src/App.css
  12. 29
      src/App.tsx
  13. 19
      src/Contact.tsx
  14. 17
      src/Contacts.tsx
  15. 46
      src/Header.tsx
  16. 9
      src/MakeContact.tsx
  17. 211
      src/Query.tsx
  18. 3
      src/index.css
  19. 9
      src/main.tsx
  20. 18
      src/reactMethods.ts
  21. 226
      src/styles.css
  22. 1
      src/vite-env.d.ts
  23. 12
      tailwind.config.js
  24. 26
      tsconfig.app.json
  25. 7
      tsconfig.json
  26. 24
      tsconfig.node.json
  27. 7
      vite.config.ts

24
.gitignore vendored

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

@ -0,0 +1,36 @@
# demo-berlin
Example of a Web app made with NextGraph, using React and LDO, and Vite.
It demonstrate the feature of Social Queries.
## NextGraph
> NextGraph brings about the convergence of P2P and Semantic Web technologies, towards a decentralized, secure and privacy-preserving cloud, based on CRDTs.
>
> This open source ecosystem provides solutions for end-users (a platform) and software developers (a framework), wishing to use or create **decentralized** apps featuring: **live collaboration** on rich-text documents, peer to peer communication with **end-to-end encryption**, offline-first, **local-first**, portable and interoperable data, total ownership of data and software, security and privacy. Centered on repositories containing **semantic data** (RDF), **rich text**, and structured data formats like **JSON**, synced between peers belonging to permissioned groups of users, it offers strong eventual consistency, thanks to the use of **CRDTs**. Documents can be linked together, signed, shared securely, queried using the **SPARQL** language and organized into sites and containers.
>
> More info here [https://nextgraph.org](https://nextgraph.org)
## For developing against a public Broker
```
npm install
npm run dev
```
You will have to use a Wallet that was created on one of our public Broker Service Providers ([nextgraph.eu](https://nextgraph.eu) by example) before you can actually login. We didn't implement yet the option to create a Wallet while you are using or developing a 3rd party app.
## License
Licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE2](LICENSE-APACHE2) or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.
`SPDX-License-Identifier: Apache-2.0 OR MIT`
---
NextGraph received funding through the [NGI Assure Fund](https://nlnet.nl/assure) and the [NGI Zero Commons Fund](https://nlnet.nl/commonsfund/), both funds established by [NLnet](https://nlnet.nl/) Foundation with financial support from the European Commission's [Next Generation Internet](https://ngi.eu/) programme, under the aegis of DG Communications Networks, Content and Technology under grant agreements No 957073 and No 101092990, respectively.

@ -0,0 +1,28 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
export default tseslint.config(
{ ignores: ['dist'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
)

@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>NextGraph WebApp React SocialQuery example</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

6229
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,45 @@
{
"name": "example-webapp-react-socialquery",
"private": true,
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite --host",
"build": "tsc --noEmit && vite build",
"lint": "eslint .",
"preview": "vite preview",
"build:ldo": "ldo build --input src/.shapes --output src/.ldo"
},
"dependencies": {
"@heroicons/react": "^2.2.0",
"@ldo/connected-nextgraph": "^1.0.0-alpha.15",
"@ldo/ldo": "^1.0.0-alpha.14",
"@ldo/react": "^1.0.0-alpha.15",
"@rdfjs/data-model": "^1.2.0",
"@rdfjs/types": "^1.0.1",
"nextgraphweb": "^0.1.1-alpha.4",
"nextgraph-react": "^0.1.1-alpha.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-router": "^7.6.0"
},
"devDependencies": {
"@eslint/js": "^9.22.0",
"@ldo/cli": "^1.0.0-alpha.15",
"@types/jsonld": "^1.5.15",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"@types/shexj": "^2.1.7",
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.21",
"eslint": "^9.22.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.19",
"globals": "^16.0.0",
"postcss": "^8.5.3",
"tailwindcss": "^3.4.17",
"typescript": "~5.7.2",
"typescript-eslint": "^8.26.1",
"vite": "^6.3.1"
}
}

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

@ -0,0 +1,35 @@
# Platform ontologies:
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX owl: <http://www.w3.org/2002/07/owl#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX dc: <http://purl.org/dc/terms/>
# Domain ontology for Contacts in vcard-like form
PREFIX vcard: <http://www.w3.org/2006/vcard/ns#>
PREFIX schem: <http://schema.org/>
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX ngc: <did:ng:x:class#>
PREFIX xskills: <did:ng:x:skills#>
PREFIX ksp: <did:ng:k:skills:programming:>
ngc:SocialContact EXTRA a {
a [ vcard:Individual ]
// rdfs:comment "Defines the node as an Individual (from vcard)" ;
a [ schem:Person ]
// rdfs:comment "Defines the node as a Person (from Schema.org)" ;
a [ foaf:Person ]
// rdfs:comment "Defines the node as a Person (from foaf)" ;
vcard:fn xsd:string
// rdfs:comment "The formatted name of a person. Example: John Smith" ;
vcard:hasEmail xsd:string ?
// rdfs:comment "The person's email." ;
xskills:hasRating @ngc:HasRating *;
}
ngc:HasRating {
a [ xskills:Rating ];
xskills:rated xsd:integer;
xskills:skill [ ksp:svelte ksp:nextjs ksp:react ksp:vuejs ksp:tailwind ksp:rdf ksp:rust ksp:yjs ksp:automerge ]
}

@ -0,0 +1,24 @@
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX ldp: <http://www.w3.org/ns/ldp#>
PREFIX ldps: <http://www.w3.org/ns/lddps#>
PREFIX dct: <http://purl.org/dc/terms/>
PREFIX stat: <http://www.w3.org/ns/posix/stat#>
PREFIX tur: <http://www.w3.org/ns/iana/media-types/text/turtle#>
PREFIX pim: <http://www.w3.org/ns/pim/space#>
ldps:Container EXTRA a {
$ldps:ContainerShape (
a [ ldp:Container ldp:Resource ]*
// rdfs:comment "A container";
dct:modified xsd:string?
// rdfs:comment "Date modified";
ldp:contains IRI *
// rdfs:comment "Defines a Resource";
stat:mtime xsd:decimal?
// rdfs:comment "?";
stat:size xsd:integer?
// rdfs:comment "size of this container";
)
}

@ -0,0 +1,18 @@
# Platform ontologies:
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX owl: <http://www.w3.org/2002/07/owl#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX dc: <http://purl.org/dc/terms/>
PREFIX ngs: <did:ng:x:shape#>
PREFIX ngc: <did:ng:x:class#>
PREFIX ng: <did:ng:x:ng#>
ngs:SocialQuery EXTRA a {
a [ ngc:SocialQuery ];
ng:social_query_sparql xsd:string ?;
ng:social_query_forwarder IRI ?;
ng:social_query_ended xsd:dateTime ?;
}

@ -0,0 +1,46 @@
.centered {
/*max-width: 1280px;*/
margin: 0 auto;
padding: 0rem;
text-align: center;
width: fit-content;
}
.contact {
width: 300px;
height: 300px;
background-color: #f6f6f6;
position: relative;
overflow-wrap: anywhere;
}
.name {
padding: 5px;
height: 35px;
overflow: hidden;
background-color: #e0e0e0d0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.email-logo {
}
.email {
padding: 5px;
overflow-wrap: anywhere;
}
input {
margin: 5px;
}
.button {
background-color:rgb(73, 114, 165);
color:white;
cursor:pointer;
}

@ -0,0 +1,29 @@
import React, { FunctionComponent } from 'react';
import { Header } from './Header';
import { Contacts } from './Contacts';
import Query from './Query';
import { BrowserNGLdoProvider } from './reactMethods';
import { BrowserRouter, Routes, Route } from "react-router";
import './App.css'
import "./styles.css";
const App: FunctionComponent = () => {
return (
<div className="App">
<BrowserNGLdoProvider>
<Header />
<BrowserRouter>
<Routes>
<Route path="/" element={<Contacts />} />
<Route path="/query" element={<Query />} />
</Routes>
</BrowserRouter>
</BrowserNGLdoProvider>
</div>
);
}
export default App

@ -0,0 +1,19 @@
import { default as React, FunctionComponent } from "react";
import { SocialContactShapeType } from "./.ldo/contact.shapeTypes.ts";
import { useSubscribeToResource, useResource, useSubject, useLdo, useNextGraphAuth } from "./reactMethods.ts";
import { StarIcon } from '@heroicons/react/24/solid'
import { StarIcon as StarIconOutline } from '@heroicons/react/24/outline'
import {
startTransaction,
transactionChanges,
toSparqlUpdate,
commitTransaction,
} from "@ldo/ldo";
export const Contact: FunctionComponent = ({nuri}) => {
return <>
</>;
};

@ -0,0 +1,17 @@
import { FunctionComponent } from "react";
import { useNextGraphAuth } from "./reactMethods";
import { ContainerShapeType } from "./.ldo/container.shapeTypes.ts";
import { useSubscribeToResource, useResource, useSubject } from "./reactMethods.ts";
import { Contact } from "./Contact";
import { MakeContact } from "./MakeContact";
import { Link } from "react-router";
import { LifebuoyIcon } from '@heroicons/react/24/outline'
export const Contacts: FunctionComponent = () => {
const { session } = useNextGraphAuth();
return <>
</>;
};

@ -0,0 +1,46 @@
import { FunctionComponent } from "react";
import { useNextGraphAuth } from "./reactMethods";
export const Header: FunctionComponent = () => {
const { session, login, logout } = useNextGraphAuth();
return (
<div className="full-layout">
{session.sessionId ? (
// If the session is logged in
<div className="p-1 text-white text-center fixed top-0 left-0 right-0" style={{zIndex:1000, height:'36px', backgroundColor:'rgb(73, 114, 165)'}}>
You are logged in.
{/* <span className="font-bold clickable" onClick={logout}> Log out</span> */}
</div>
) : (
// If the session is not logged in
<>
<h1 className="text-2xl text-center mb-10">Welcome to the mini-LinkedIn demo</h1>
<h1 className="text-lg text-center mb-10">An example use of Social Queries with NextGraph</h1>
<div className="text-center text-xl p-1 text-white fixed top-0 left-0 right-0" style={{zIndex:1000, height:'36px', backgroundColor:'rgb(73, 114, 165)'}}>
Please <span className="font-bold clickable" onClick={login}> Log in</span>
</div>
<div className="text-center max-w-6xl lg:px-8 mx-auto px-4 text-blue-800">
<svg onClick={login}
onKeyUp={login} className="cursor-pointer mt-10 h-16 w-16 mx-auto" data-slot="icon" fill="none" strokeWidth="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" d="M8.25 9V5.25A2.25 2.25 0 0 1 10.5 3h6a2.25 2.25 0 0 1 2.25 2.25v13.5A2.25 2.25 0 0 1 16.5 21h-6a2.25 2.25 0 0 1-2.25-2.25V15M12 9l3 3m0 0-3 3m3-3H2.25"></path>
</svg>
<button
onClick={login}
onKeyUp={login}
className="select-none ml-0 mt-2 mb-10 text-white bg-blue-800 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-500/50 rounded-lg text-base p-2 text-center inline-flex items-center dark:focus:ring-primary-700/55"
>
Please Log in
</button>
</div>
</>
)}
</div>
);
};

@ -0,0 +1,9 @@
import { FormEvent, FunctionComponent, useCallback, useState } from "react";
import { useLdo, dataset, useNextGraphAuth } from './reactMethods';
import { SocialContactShapeType } from "./.ldo/contact.shapeTypes.ts";
export const MakeContact: FunctionComponent = () => {
return (
<></>
);
};

@ -0,0 +1,211 @@
import React, { FunctionComponent, useState } from 'react';
import { LifebuoyIcon } from '@heroicons/react/24/outline'
import { useLdo, dataset, useNextGraphAuth, useResource, useSubject} from './reactMethods';
import { SocialQueryShapeType } from "./.ldo/socialquery.shapeTypes.ts";
import { namedNode } from "@rdfjs/data-model";
import type { Quad } from "@rdfjs/types";
import type { DatasetChanges } from "@ldo/rdf-utils";
const query_string = `PREFIX vcard: <http://www.w3.org/2006/vcard/ns#>
PREFIX xskills: <did:ng:x:skills#>
PREFIX ksp: <did:ng:k:skills:programming:>
PREFIX ng: <did:ng:x:ng#>
CONSTRUCT { [
vcard:hasEmail ?email;
vcard:fn ?name;
a vcard:Individual;
ng:site ?public_profile;
ng:protected ?protected_profile;
xskills:hasRating [
a xskills:Rating ;
xskills:rated ?level;
xskills:skill ?skill
]
]
}
WHERE {
?contact a vcard:Individual.
?contact vcard:fn ?name.
?contact vcard:hasEmail ?email.
OPTIONAL { ?contact ng:site ?public_profile . ?contact ng:site_inbox ?public_inbox }
OPTIONAL { ?contact ng:protected ?protected_profile . ?contact ng:protected_inbox ?prot_inbox }
?contact xskills:hasRating [
a xskills:Rating ;
xskills:rated ?level;
xskills:skill ?skill
].
?contact xskills:hasRating/xskills:skill ksp:rust.
?contact xskills:hasRating/xskills:skill ksp:svelte.
FILTER ( ?skill IN (
ksp:rust, ksp:svelte, ksp:rdf, ksp:tailwind, ksp:yjs, ksp:automerge
) )
}`;
const ranking_query = `SELECT ?mail (SAMPLE(?n) as?name) (MAX(?rust_) as ?rust) (MAX(?svelte_) as ?svelte) (MAX(?tailwind_) as ?tailwind)
(MAX(?rdf_) as ?rdf) (MAX(?yjs_) as ?yjs) (MAX(?automerge_) as ?automerge) (SUM(?total_) as ?total)
WHERE {
{ SELECT ?mail (SAMPLE(?name) as ?n) ?skill (AVG(?value)+1 AS ?score)
WHERE {
?rating <http://www.w3.org/2006/vcard/ns#hasEmail> ?mail.
?rating <http://www.w3.org/2006/vcard/ns#fn> ?name.
?rating <did:ng:x:skills#hasRating> ?hasrating.
?hasrating <did:ng:x:skills#rated> ?value.
?hasrating <did:ng:x:skills#skill> ?skill.
} GROUP BY ?mail ?skill
}
BIND (IF(sameTerm(?skill, <did:ng:k:skills:programming:rust>), ?score, 0) AS ?rust_)
BIND (IF(sameTerm(?skill, <did:ng:k:skills:programming:svelte>), ?score, 0) AS ?svelte_)
BIND (IF(sameTerm(?skill, <did:ng:k:skills:programming:tailwind>), ?score, 0) AS ?tailwind_)
BIND (IF(sameTerm(?skill, <did:ng:k:skills:programming:rdf>), ?score, 0) AS ?rdf_)
BIND (IF(sameTerm(?skill, <did:ng:k:skills:programming:yjs>), ?score, 0) AS ?yjs_)
BIND (IF(sameTerm(?skill, <did:ng:k:skills:programming:automerge>), ?score, 0) AS ?automerge_)
BIND (?tailwind_+?svelte_+?rust_+?rdf_+?yjs_+?automerge_ AS ?total_)
} GROUP BY ?mail
ORDER BY DESC(?total)`;
const Query: FunctionComponent = () => {
const { createData, commitData, changeData } = useLdo();
const { session } = useNextGraphAuth();
const [resourceUri, setResourceUri] = useState("");
useResource(resourceUri, { subscribe: true });
const [nuri, setNuri] = useState("");
const [querying, setQuerying] = useState(false);
const [results, setResults] = useState([]);
let social_query = useSubject(SocialQueryShapeType, session.sessionId && nuri ? nuri : undefined);
React.useEffect(() => {
async function start() {
let res = await session.ng.app_request_with_nuri_command(nuri, {Fetch:"CurrentHeads"}, session.sessionId);
let request_nuri = res.V0?.Text;
console.log(request_nuri);
// finally start the social query
res = await session.ng.social_query_start(
session.sessionId,
"did:ng:a",
request_nuri,
"did:ng:d:c",
0,
);
}
if (social_query?.socialQuerySparql) {
if (!social_query?.socialQueryForwarder) {
//console.log(social_query?.socialQuerySparql);
start();
} else {
console.log("ready to receive results")
dataset.on(
[null, null, null, namedNode(resourceUri)],
(changes: DatasetChanges<Quad>) => {
session.ng.sparql_query(session.sessionId, ranking_query, undefined, resourceUri).then((res) => {
setResults(res.results?.bindings);
});
},
);
}
}
}, [resourceUri, nuri, social_query, session])
const openQuery = async () => {
setQuerying(true);
try {
let resource = await dataset.createResource("nextgraph", { primaryClass: "social:query:skills:programming" });
if (!resource.isError) {
console.log("Created resource:", resource.uri);
setResourceUri(resource.uri);
setNuri(resource.uri.substring(0,53));
const query = createData(
SocialQueryShapeType,
nuri,
resource
);
query.type = { "@id": "SocialQuery" };
const result = await commitData(query);
if (result.isError) {
console.error(result.message);
}
// then add the did:ng:x:ng#social_query_sparql
//await session.ng.sparql_update(session.sessionId,`INSERT DATA { <${nuri}> <did:ng:x:ng#social_query_sparql> \"${query_string.replaceAll("\n"," ")}\".}`, resource.uri);
let editing_query = changeData(query, resource);
editing_query.socialQuerySparql = query_string.replaceAll("\n"," ");
// const changes = transactionChanges(editing_query);
// console.log(changes.added?.toString())
// console.log(changes.removed?.toString())
let res = await commitData(editing_query);
if (res.isError) {
console.error(result.message);
}
}
else {
console.error(resource);
}
} catch (e) {
console.error(e)
}
};
if (!session.sessionId) return <></>;
return (
<div className="centered">
<div className="flex flex-col justify-center gap-5 mt-10 mb-5">
{!querying && <p className="p-3">
<button
onClick={openQuery}
onKeyPress={openQuery}
className="button select-none ml-2 mt-2 mb-2 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-500/50 rounded-lg text-base p-2 text-center inline-flex items-center dark:focus:ring-primary-700/55"
>
<LifebuoyIcon tabIndex={-1} className="mr-2 focus:outline-none size-6" />
Start query
</button>
</p>
}
<div className="relative overflow-x-auto">
<table className="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400 table-auto">
<thead className="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
<tr>
<th scope="col" className="px-6 py-3">Email</th>
<th scope="col" className="px-6 py-3">Name</th>
<th scope="col" className="px-6 py-3">Rust</th>
<th scope="col" className="px-6 py-3">Svelte</th>
<th scope="col" className="px-6 py-3">Tailwind</th>
<th scope="col" className="px-6 py-3">Rdf</th>
<th scope="col" className="px-6 py-3">Yjs</th>
<th scope="col" className="px-6 py-3">Automerge</th>
<th scope="col" className="px-6 py-3">Total</th>
</tr>
</thead>
<tbody>
{
results.map((res) =>
<tr key={res.mail.value} className="bg-white border-b dark:bg-gray-800 dark:border-gray-700 border-gray-200">
<td scope="row" className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">{res.mail.value}</td>
<td scope="row" className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">{res.name.value}</td>
<td scope="row" className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">{Math.round(res.rust.value * 10) / 10 }</td>
<td scope="row" className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">{Math.round(res.svelte.value * 10) / 10 }</td>
<td scope="row" className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">{Math.round(res.tailwind.value * 10) / 10 }</td>
<td scope="row" className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">{Math.round(res.rdf.value * 10) / 10 }</td>
<td scope="row" className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">{Math.round(res.yjs.value * 10) / 10 }</td>
<td scope="row" className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">{Math.round(res.automerge.value * 10) / 10 }</td>
<td scope="row" className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">{Math.round(res.total.value * 10) / 10 }</td>
</tr>
)
}
</tbody>
</table>
</div>
</div>
</div>
);
}
export default Query

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

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

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

@ -0,0 +1,226 @@
/*
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
*/
/** To format paths, like Settings > Wallet > Generate Wallet QR */
.path {
font-family: monospace;
background-color: rgba(73, 114, 165, 0.1);
}
/* .splash-loaded {
display: none;
} */
.toggle * {
cursor: pointer;
}
.error-popover h3 {
text-align: center;
color: rgb(200 30 30);
}
.error-popover > div:first-child {
background-color: rgb(200 30 30);
}
.error-popover > div:first-child > h3 {
color: white;
}
.logo {
padding: 1.5em;
will-change: filter;
transition: 0.75s;
padding-bottom: 1em;
}
@keyframes pulse-logo-color {
0%,
100% {
fill: rgb(73, 114, 165);
stroke: rgb(73, 114, 165);
}
50% {
/* Mid-transition color */
stroke: #bbb;
fill: #bbb;
}
}
.logo-pulse path {
animation: pulse-logo-color 2s infinite;
animation-timing-function: cubic-bezier(0.65, 0.01, 0.59, 0.83);
}
.logo-gray path {
fill: #bbb;
stroke: #bbb;
}
.logo-blue path {
fill: rgb(73, 114, 165);
stroke: rgb(73, 114, 165);
}
.jse-absolute-popup-content {
left: 0 !important;
}
.container3 {
margin: 0;
display: flex;
flex-direction: column;
justify-content: center;
text-align: center;
}
.container3 aside {
width: 20rem !important;
}
div[role="alert"] div {
display: block;
}
.spinner-overlay button {
display: none;
}
.choice-button {
min-width: 305px;
}
.clickable {
cursor: pointer;
}
.row {
display: flex;
justify-content: center;
}
.deactivated-menu > svg {
color: rgb(156 163 175) !important;
}
:root {
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 400;
color: #0f0f0f;
background-color: white;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 305px;
min-height: 100vh;
}
#app {
width: 100%;
}
/* #app {
/*max-width: 1280px;
margin: 0 auto;
padding: 0rem;
text-align: center;
} */
/* .container2 {
padding-top: 10vh;
} */
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
.toast {
left: 50%;
transform: translateX(-50%);
z-index: 49;
cursor: pointer;
}
input,
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
color: #0f0f0f;
background-color: #ffffff;
transition: border-color 0.25s;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
}
button {
cursor: pointer;
}
button:hover {
border-color: #396cd8;
}
button:active {
border-color: #396cd8;
background-color: #e8e8e8;
}
/* input,
button {
outline: none;
} */
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
/* @media (prefers-color-scheme: dark) {
:root {
color: #f6f6f6;
background-color: #2f2f2f;
}
a:hover {
color: #24c8db;
}
input,
button {
color: #ffffff;
background-color: #0f0f0f98;
}
button:active {
background-color: #0f0f0f69;
}
} */

1
src/vite-env.d.ts vendored

@ -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…
Cancel
Save