parent
9c1165653b
commit
663e8d31f1
@ -0,0 +1,27 @@ |
||||
<!-- |
||||
// 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. |
||||
--> |
||||
|
||||
<!-- |
||||
Error page (?o=https://domain.tld was not provided or is invalid) |
||||
--> |
||||
|
||||
<script> |
||||
import { ExclamationTriangle} from "svelte-heros-v2"; |
||||
</script> |
||||
|
||||
<div class="text-center max-w-6xl lg:px-8 mx-auto px-4 text-green-800"> |
||||
<div class=" max-w-6xl lg:px-8 mx-auto px-4 text-red-800"> |
||||
<ExclamationTriangle class="animate-bounce mt-10 h-16 w-16 mx-auto" /> |
||||
<p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5"> |
||||
Invalid request |
||||
</p> |
||||
</div> |
||||
</div> |
@ -1,26 +0,0 @@ |
||||
// 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.
|
||||
|
||||
//import * as sdk from "@ng-org/wasm-tools-auth";
|
||||
import { fromWritablePort } from 'remote-web-streams'; |
||||
|
||||
self.onmessage = (event) => { |
||||
(async function() { |
||||
const { method, args, port } = event.data; |
||||
const writable = fromWritablePort(port); |
||||
const writer = writable.getWriter(); |
||||
//console.log("Message received by worker", method, args);
|
||||
|
||||
let ret = await Reflect.apply(sdk[method], null, args); |
||||
writer.write(ret); |
||||
writer.close(); |
||||
})(); |
||||
} |
||||
|
||||
//console.log("worker loaded");
|
@ -1,32 +0,0 @@ |
||||
var crypto = require('crypto') |
||||
, fs = require('fs') |
||||
const {gzip, } = require('node-gzip'); |
||||
|
||||
var algorithm = 'sha256' |
||||
, shasum = crypto.createHash(algorithm) |
||||
|
||||
const sha_file = './dist/index.sha256'; |
||||
const gzip_file = './dist/index.gzip'; |
||||
var filename = './dist/index.html' |
||||
, s = fs.ReadStream(filename) |
||||
|
||||
var bufs = []; |
||||
s.on('data', function(data) { |
||||
shasum.update(data) |
||||
bufs.push(data); |
||||
}) |
||||
|
||||
s.on('end', function() { |
||||
var hash = shasum.digest('hex') |
||||
console.log(hash + ' ' + filename) |
||||
|
||||
fs.writeFileSync(sha_file, hash, 'utf8'); |
||||
|
||||
var buf = Buffer.concat(bufs); |
||||
gzip(buf).then((compressed) => {fs.writeFileSync(gzip_file, compressed);}); |
||||
|
||||
fs.rm(filename,()=>{}); |
||||
|
||||
}) |
||||
|
||||
|
@ -1,180 +0,0 @@ |
||||
<!-- |
||||
// 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. |
||||
--> |
||||
|
||||
<script lang="ts"> |
||||
import { push, default as Router } from "svelte-spa-router"; |
||||
import { isLoading } from "svelte-i18n"; |
||||
|
||||
import { onMount, tick, onDestroy } from "svelte"; |
||||
import { ng, brokers_info } from "./store"; |
||||
import { |
||||
NotFound, |
||||
Test, |
||||
WalletCreate, |
||||
Invitation, |
||||
WalletLogin, |
||||
WalletInfo, |
||||
User, |
||||
UserRegistered, |
||||
Install, |
||||
ScanQRWeb, |
||||
AccountInfo, |
||||
WalletLoginUsername, |
||||
WalletLoginQr, |
||||
WalletLoginTextCode |
||||
} from "@ng-org/ui-common/routes"; |
||||
import { Bowser } from "../../../../sdk/js/lib-wasm/jsland/bowser.js"; |
||||
|
||||
import Home from "./routes/Home.svelte"; |
||||
|
||||
const routes = new Map(); |
||||
routes.set("/", Home); |
||||
// routes.set("/test", Test); |
||||
// routes.set("/wallet/login", WalletLogin); |
||||
// routes.set("/wallet/username", WalletLoginUsername); |
||||
// routes.set("/wallet/login-qr", WalletLoginQr); |
||||
// routes.set("/wallet/login-text-code", WalletLoginTextCode); |
||||
// routes.set("/wallet/create", WalletCreate); |
||||
// routes.set("/i/:invitation", Invitation); |
||||
// routes.set("/user", User); |
||||
// routes.set("/user/registered", UserRegistered); |
||||
// routes.set("/wallet", WalletInfo); |
||||
// routes.set("/user/accounts", AccountInfo); |
||||
// routes.set("/scanqr", ScanQRWeb); |
||||
// routes.set("/install", Install); |
||||
routes.set("*", NotFound); |
||||
|
||||
// window.refresh_wallets = async () => { |
||||
// let walls = await ng.get_wallets(); |
||||
// wallets.set(walls); |
||||
// }; |
||||
|
||||
let no_local_storage = false; |
||||
let is_safari = false; |
||||
|
||||
function load_bootstraps(bs: string | null) { |
||||
if (bs) { |
||||
let bootstrap_map = JSON.parse(bs); |
||||
brokers_info.set(bootstrap_map); |
||||
} |
||||
} |
||||
|
||||
onMount(async () => { |
||||
|
||||
window.document.getElementById("splash").className="noshow"; |
||||
window.document.getElementById("app").className=""; |
||||
|
||||
let info = Bowser.parse(window.navigator.userAgent); |
||||
//console.log(info); |
||||
is_safari = info.browser.name == "Safari"; |
||||
if (is_safari) return; |
||||
|
||||
window.addEventListener("storage", (event) => { |
||||
//console.log("localStorage event", event); |
||||
if (event.storageArea != localStorage) return; |
||||
if (event.key === "ng_bootstrap") { |
||||
load_bootstraps(event.newValue); |
||||
} |
||||
}); |
||||
|
||||
let ls; |
||||
try { |
||||
ls = localStorage; |
||||
|
||||
try { |
||||
let ret = await document.requestStorageAccess({ localStorage: true }); |
||||
ls = ret.localStorage; |
||||
console.log("REQUEST STORAGE ACCESS GRANTED by chrome"); |
||||
} |
||||
catch(e) { |
||||
console.warn("requestStorageAccess of chrome failed. falling back to previous api", e) |
||||
try { |
||||
await document.requestStorageAccess(); |
||||
localStorage; |
||||
console.log("REQUEST STORAGE ACCESS GRANTED"); |
||||
} catch (e) { |
||||
console.error("REQUEST STORAGE ACCESS DENIED",e); |
||||
no_local_storage = true; |
||||
} |
||||
} |
||||
|
||||
} catch (e) { |
||||
no_local_storage = true; |
||||
console.log("no access to localStorage",e) |
||||
} |
||||
|
||||
if (!no_local_storage) { |
||||
try { |
||||
load_bootstraps(ls.getItem("ng_bootstrap")); |
||||
} catch (e) { |
||||
console.log("load_bootstraps failed") |
||||
} |
||||
} |
||||
|
||||
}); |
||||
|
||||
</script> |
||||
{#if is_safari} |
||||
|
||||
<div class="text-center max-w-6xl lg:px-8 mx-auto px-4 text-red-800"> |
||||
<svg |
||||
class="animate-bounce mt-10 h-16 w-16 mx-auto" |
||||
fill="none" |
||||
stroke="currentColor" |
||||
stroke-width="1.5" |
||||
viewBox="0 0 24 24" |
||||
xmlns="http://www.w3.org/2000/svg" |
||||
aria-hidden="true" |
||||
> |
||||
<path |
||||
stroke-linecap="round" |
||||
stroke-linejoin="round" |
||||
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" |
||||
/> |
||||
</svg> |
||||
<p class="mb-5"> |
||||
We are sorry but Safari is not supported yet<br/>for WebApps authentication with your Wallet.<br/>Please use another browser. |
||||
</p> |
||||
</div> |
||||
|
||||
{:else if no_local_storage} |
||||
|
||||
<div class="text-center max-w-6xl lg:px-8 mx-auto px-4 text-red-800"> |
||||
<svg |
||||
class="animate-bounce mt-10 h-16 w-16 mx-auto" |
||||
fill="none" |
||||
stroke="currentColor" |
||||
stroke-width="1.5" |
||||
viewBox="0 0 24 24" |
||||
xmlns="http://www.w3.org/2000/svg" |
||||
aria-hidden="true" |
||||
> |
||||
<path |
||||
stroke-linecap="round" |
||||
stroke-linejoin="round" |
||||
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" |
||||
/> |
||||
</svg> |
||||
<p class="mb-5"> |
||||
Please give access to localStorage for the website<br/> |
||||
{location.origin} |
||||
</p> |
||||
</div> |
||||
|
||||
{:else} |
||||
|
||||
{#if $isLoading} |
||||
<p class="text-center">Loading translations...</p> |
||||
{:else} |
||||
<Router {routes} /> |
||||
{/if} |
||||
|
||||
{/if} |
Before Width: | Height: | Size: 1.1 KiB |
@ -1,160 +0,0 @@ |
||||
<!-- |
||||
// 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. |
||||
--> |
||||
<script lang="ts"> |
||||
import { onMount } from "svelte"; |
||||
import { t, locale } from "svelte-i18n"; |
||||
import { CenteredLayout } from "@ng-org/ui-common/lib"; |
||||
import { LogoSimple } from "@ng-org/ui-common/components"; |
||||
import { |
||||
Sidebar, |
||||
SidebarGroup, |
||||
SidebarItem, |
||||
SidebarWrapper, |
||||
} from "flowbite-svelte"; |
||||
import { |
||||
ComputerDesktop, |
||||
GlobeAlt, |
||||
ServerStack |
||||
} from "svelte-heros-v2"; |
||||
|
||||
import { web_origin, brokers_info, selected_broker } from '../store'; |
||||
import { fromWritablePort, RemoteReadableStream } from 'remote-web-streams'; |
||||
|
||||
let top; |
||||
let nonActiveClass = |
||||
"flex items-center p-2 text-base font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700"; |
||||
|
||||
const AUTH_HOME = "#/"; |
||||
|
||||
function select(broker: Object) { |
||||
|
||||
let url; |
||||
if (import.meta.env.NG_DEV && broker.localhost === 14400) { |
||||
// dev mode |
||||
url = "http://localhost:1421/appauth.html"; |
||||
} else if (broker.localhost) { |
||||
url = `http://localhost:${broker.localhost}/auth/`; |
||||
} else if (broker.private) { |
||||
//TODO |
||||
url = `http://unimplemented/auth/`; |
||||
} else if (broker.domain) { |
||||
url = `https://${broker.domain}/auth/`; |
||||
} else if (broker.ngbox) { |
||||
url = `https://nextgraph.app/auth/`; |
||||
} else return; |
||||
|
||||
selected_broker.set(broker); |
||||
let iframe = window.document.getElementById("nextgraph-app-auth-iframe"); |
||||
iframe?.classList.add('nextgraph-app-auth-iframe--active'); |
||||
window.document.getElementById("app").style["display"] = "none"; |
||||
|
||||
(<any>window).ng_iframe_origin = new URL(url).origin; |
||||
|
||||
iframe.addEventListener("load", function() { |
||||
|
||||
(<any>window).ng_broker_selected = this.contentWindow; |
||||
const ready_handler = async function(m) { |
||||
if (m.data.ready && m.origin === (<any>window).ng_iframe_origin) { |
||||
//console.log("got ready message",m); |
||||
//remove this listener |
||||
window.removeEventListener("message",ready_handler); |
||||
const { readable, writablePort } = new RemoteReadableStream(); |
||||
//console.log("sending init message to app-auth"); |
||||
(<any>window).ng_broker_selected.postMessage({ method: "init", manifest:window.ng_manifest, port: writablePort }, (<any>window).ng_iframe_origin, [writablePort]); |
||||
const reader = readable.getReader(); |
||||
for (var msg; msg = await reader.read(); ) { |
||||
if (msg.done) { |
||||
(<any>window).ng_status_callback.close(); |
||||
break; |
||||
} else { |
||||
//console.log("forwarding upstream",msg.value); |
||||
(<any>window).ng_status_callback.write(msg.value); |
||||
} |
||||
} |
||||
} |
||||
}; |
||||
window.addEventListener("message",ready_handler); |
||||
}); |
||||
|
||||
iframe.src = url+"?o="+location.search.substring(3)+AUTH_HOME; |
||||
} |
||||
|
||||
onMount(() => { |
||||
if (Object.keys($brokers_info).length == 1) { |
||||
select(Object.values($brokers_info)[0]); |
||||
} |
||||
}); |
||||
</script> |
||||
|
||||
{#if Object.keys($brokers_info).length > 1} |
||||
<CenteredLayout> |
||||
<div class="container3" bind:this={top}> |
||||
<div class="row"> |
||||
<LogoSimple/> |
||||
</div> |
||||
<div class="row mb-20"> |
||||
<Sidebar {nonActiveClass}> |
||||
<SidebarWrapper |
||||
divClass="bg-gray-60 overflow-y-auto py-4 px-3 rounded dark:bg-gray-800" |
||||
> |
||||
<SidebarGroup ulClass="space-y-2" role="menu"> |
||||
<li> |
||||
<h2 class="text-xl mb-6">{@html $t("auth.select_broker", {values: { origin:$web_origin }})}</h2> |
||||
</li> |
||||
{#each Object.entries($brokers_info) as broker} |
||||
<li |
||||
tabindex="0" |
||||
role="menuitem" |
||||
class="flex items-center p-2 text-base font-normal text-gray-900 clickable rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700" |
||||
on:keypress={()=>select(broker[1])} |
||||
on:click={()=>select(broker[1])} |
||||
> |
||||
{#if broker[1].localhost} |
||||
<ComputerDesktop tabindex="-1" |
||||
class="w-10 h-10 mr-4 text-black transition duration-75 focus:outline-none dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"/> |
||||
{:else if broker[1].domain} |
||||
<GlobeAlt tabindex="-1" |
||||
class="w-10 min-w-10 h-10 mr-4 text-black transition duration-75 focus:outline-none dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"/> |
||||
{:else if broker[1].private} |
||||
<ServerStack tabindex="-1" |
||||
class="w-10 h-10 mr-4 text-black transition duration-75 focus:outline-none dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"/> |
||||
{:else if broker[1].ngbox} |
||||
<svg |
||||
xmlns="http://www.w3.org/2000/svg" |
||||
version="1.1" |
||||
viewBox="0 0 225 225" |
||||
class="mr-4 block h-10 w-10" |
||||
stroke="currentColor" |
||||
stroke-width="12" |
||||
fill="none" |
||||
> |
||||
<path |
||||
d="M 88.332599,179.77884 C 72.858008,177.42608 59.581081,170.564 48.8817,159.38898 36.800075,146.77026 30.396139,130.74266 30.396139,113.12381 c 0,-8.81477 1.466462,-16.772273 4.503812,-24.439156 3.697755,-9.333883 8.658122,-16.726264 15.988284,-23.827148 4.07992,-3.952299 5.699054,-5.267377 9.730928,-7.903581 10.263753,-6.710853 20.852276,-10.247623 32.861256,-10.976317 17.083161,-1.036581 33.737521,4.410501 47.100151,15.404873 1.30009,1.069669 2.35446,2.035155 2.34305,2.145524 -0.0114,0.110369 -3.32807,3.135042 -7.37038,6.721489 -4.04229,3.586437 -8.6667,7.731233 -10.27646,9.210635 -1.60975,1.479412 -3.05439,2.689839 -3.21032,2.689839 -0.15591,0 -1.2075,-0.642795 -2.33686,-1.428431 -6.49544,-4.518567 -13.79659,-6.747116 -22.104843,-6.747116 -10.982241,0 -20.054641,3.741852 -27.727158,11.435891 -5.517107,5.532575 -9.233107,12.555305 -10.782595,20.377588 -0.596045,3.00901 -0.594915,11.67153 0.0017,14.67182 3.195984,16.0665 15.801761,28.55358 31.607491,31.30987 3.592183,0.62643 10.334745,0.61437 13.792675,-0.0247 12.10383,-2.2368 22.30712,-9.80603 27.83192,-20.64689 0.66747,-1.30971 1.08703,-2.48825 0.93235,-2.61898 -0.1547,-0.13073 -5.9299,-1.01605 -12.83381,-1.96739 -8.43575,-1.16241 -12.87296,-1.9096 -13.52955,-2.27826 -1.31171,-0.73647 -2.44642,-2.49122 -2.44642,-3.78325 0,-1.012 1.74837,-13.68832 2.1486,-15.57814 0.25598,-1.20873 2.0923,-3.01339 3.3151,-3.25795 0.53677,-0.10735 7.61424,0.73799 15.7688,1.88346 8.13723,1.14303 14.89071,1.97925 15.00772,1.85826 0.11702,-0.12098 0.96445,-5.648553 1.88315,-12.283473 0.95557,-6.900944 1.90122,-12.59548 2.20977,-13.306594 0.29667,-0.683692 0.95765,-1.595052 1.46889,-2.025218 1.77972,-1.497534 2.7114,-1.539742 10.52745,-0.476938 8.31229,1.130266 9.2373,1.347581 10.59333,2.488613 1.41776,1.192951 1.96085,2.424677 1.94866,4.419342 -0.006,0.950347 -0.79507,7.156475 -1.75393,13.791395 -0.95885,6.634933 -1.70069,12.111623 -1.64854,12.170443 0.0522,0.0588 6.18174,0.95872 13.62132,1.99978 9.57969,1.34053 13.80866,2.0595 14.49353,2.46406 1.3199,0.77969 2.13943,2.28402 2.1135,3.87957 -0.0399,2.45278 -2.08103,15.63263 -2.5664,16.57122 -0.57073,1.10369 -2.24485,2.197 -3.38232,2.20889 -0.44831,0.004 -6.79249,-0.82755 -14.09817,-1.84941 -7.3057,-1.02186 -13.34942,-1.79161 -13.43049,-1.71053 -0.0811,0.0811 -1.02469,6.33285 -2.09694,13.89286 -1.24218,8.75802 -2.1547,14.1778 -2.51495,14.93697 -0.62565,1.31846 -2.38302,2.64205 -3.91461,2.94836 -0.8254,0.16509 -9.4024,-0.80047 -11.73007,-1.32049 -0.47193,-0.10544 -1.63157,0.58011 -3.8898,2.29957 -9.71515,7.39729 -20.99725,11.99799 -33.08692,13.49241 -3.79574,0.46921 -13.565667,0.37348 -17.125664,-0.16779 z" |
||||
/> |
||||
<rect |
||||
ry="37.596001" |
||||
y="10.583322" |
||||
x="14.363095" |
||||
height="204.86308" |
||||
width="195.79167" |
||||
/> |
||||
</svg> |
||||
{/if} |
||||
<span class="text-left text-xl ml-3" style="overflow-wrap: anywhere;">{broker[0]}</span> |
||||
</li> |
||||
{/each} |
||||
</SidebarGroup> |
||||
</SidebarWrapper> |
||||
</Sidebar> |
||||
</div> |
||||
</div> |
||||
</CenteredLayout> |
||||
{/if} |
@ -1,99 +0,0 @@ |
||||
<!-- |
||||
// 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. |
||||
--> |
||||
|
||||
<!-- |
||||
Home page to display for logged in users. |
||||
Redirects to no-wallet or login page, if not logged in. |
||||
--> |
||||
|
||||
<script> |
||||
import Home from "../lib/Home.svelte"; |
||||
import { t, locale } from "svelte-i18n"; |
||||
import { NoWallet } from "@ng-org/ui-common/lib"; |
||||
import { push } from "svelte-spa-router"; |
||||
import { onMount, onDestroy } from "svelte"; |
||||
import { brokers_info, web_origin } from "../store"; |
||||
|
||||
</script> |
||||
|
||||
{#if Object.keys($brokers_info).length == 0} |
||||
<!-- <NoWallet /> --> |
||||
{#if import.meta.env.NG_DEV} |
||||
<div class="text-center max-w-6xl lg:px-8 mx-auto px-4 text-red-800"> |
||||
<svg |
||||
class="animate-bounce mt-10 h-16 w-16 mx-auto" |
||||
fill="none" |
||||
stroke="currentColor" |
||||
stroke-width="1.5" |
||||
viewBox="0 0 24 24" |
||||
xmlns="http://www.w3.org/2000/svg" |
||||
aria-hidden="true" |
||||
> |
||||
<path |
||||
stroke-linecap="round" |
||||
stroke-linejoin="round" |
||||
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" |
||||
/> |
||||
</svg> |
||||
<p class="mb-5"> |
||||
We could not find a wallet in your browser.<br/> |
||||
This is probably due to the recent changes made to the framework.<br/> |
||||
Please create a new wallet from this browser and try again, |
||||
</p> |
||||
</div> |
||||
{:else} |
||||
<div class="text-center max-w-6xl lg:px-8 mx-auto px-4 text-blue-600"> |
||||
|
||||
<svg |
||||
class="y-10 h-16 w-16 mx-auto mb-3" |
||||
fill="none" |
||||
stroke="currentColor" |
||||
stroke-width="1.5" |
||||
viewBox="0 0 24 24" |
||||
xmlns="http://www.w3.org/2000/svg" |
||||
aria-hidden="true" |
||||
> |
||||
<path |
||||
stroke-linecap="round" |
||||
stroke-linejoin="round" |
||||
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" |
||||
/> |
||||
</svg> |
||||
We could not find a wallet in your browser.<br/> |
||||
For now, creating a new wallet while a Web App<br/> |
||||
is authenticating, is not implemented. Please<br/> |
||||
create or import your wallet in a new tab by<br/><a href="https://nextgraph.eu" target="_blank">clicking here</a> |
||||
</div> |
||||
{/if} |
||||
{:else if $web_origin} |
||||
<Home /> |
||||
{:else} |
||||
<div class="text-center max-w-6xl lg:px-8 mx-auto px-4 text-red-800"> |
||||
<svg |
||||
class="animate-bounce mt-10 h-16 w-16 mx-auto" |
||||
fill="none" |
||||
stroke="currentColor" |
||||
stroke-width="1.5" |
||||
viewBox="0 0 24 24" |
||||
xmlns="http://www.w3.org/2000/svg" |
||||
aria-hidden="true" |
||||
> |
||||
<path |
||||
stroke-linecap="round" |
||||
stroke-linejoin="round" |
||||
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" |
||||
/> |
||||
</svg> |
||||
<p class="mb-5"> |
||||
{@html $t("auth.unexpected_error")} |
||||
</p> |
||||
</div> |
||||
{/if} |
@ -1,53 +0,0 @@ |
||||
// 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.
|
||||
|
||||
import { |
||||
writable, |
||||
readable, |
||||
readonly, |
||||
derived, |
||||
get, |
||||
type Writable, |
||||
} from "svelte/store"; |
||||
|
||||
import { createAsyncProxy } from "async-proxy"; |
||||
import { RemoteReadableStream } from 'remote-web-streams'; |
||||
|
||||
export const selected_broker = writable<undefined | Object>( undefined ); |
||||
|
||||
export const brokers_info = writable( {} ); |
||||
|
||||
export const unlocked_wallet = writable(undefined); |
||||
|
||||
export const web_origin = writable(""); |
||||
|
||||
import worker_ from "./worker.js?worker&inline"; |
||||
const worker = new worker_(); |
||||
|
||||
async function rpc( method:string, args?: any) : Promise<any> { |
||||
const { readable, writablePort } = new RemoteReadableStream(); |
||||
worker.postMessage({ method, args, port: writablePort }, [writablePort]); |
||||
const reader = readable.getReader(); |
||||
let ret = await reader.read(); |
||||
await reader.read(); // the close.
|
||||
return ret.value; |
||||
} |
||||
|
||||
const handler = { |
||||
async apply(_target: object, path: PropertyKey[], _caller: any, args?: any) :Promise<any> { |
||||
|
||||
if (path[0] === "login") { |
||||
|
||||
} else { |
||||
return await rpc(<string>path[0], args); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
export const ng = createAsyncProxy({}, handler); |
@ -1,2 +1 @@ |
||||
/// <reference types="svelte" />
|
||||
/// <reference types="vite/client" />
|
||||
|
@ -1,26 +0,0 @@ |
||||
// 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.
|
||||
|
||||
//import * as sdk from "@ng-org/wasm-tools-auth";
|
||||
import { fromWritablePort } from 'remote-web-streams'; |
||||
|
||||
self.onmessage = (event) => { |
||||
(async function() { |
||||
const { method, args, port } = event.data; |
||||
const writable = fromWritablePort(port); |
||||
const writer = writable.getWriter(); |
||||
console.log("Message received by worker", method, args); |
||||
|
||||
let ret = await Reflect.apply(sdk[method], null, args); |
||||
writer.write(ret); |
||||
writer.close(); |
||||
})(); |
||||
} |
||||
|
||||
console.log("worker loaded"); |
@ -1,70 +1,14 @@ |
||||
import { defineConfig } from 'vite' |
||||
import { svelte, vitePreprocess } from '@sveltejs/vite-plugin-svelte' |
||||
import sveltePreprocess from "svelte-preprocess"; |
||||
import svelteSVG from "vite-plugin-svelte-svg"; |
||||
import wasm from "vite-plugin-wasm"; |
||||
import topLevelAwait from "vite-plugin-top-level-await"; |
||||
import { viteSingleFile } from "vite-plugin-singlefile" |
||||
|
||||
const jsToBottom = () => { |
||||
return { |
||||
name: "script-at-end-of-body", |
||||
transformIndexHtml(html) { |
||||
let scriptTag = html.match(/<script type[^>]*>(.*?)<\/script[^>]*>/)[0] |
||||
//console.log("\n SCRIPT TAG", scriptTag, "\n")
|
||||
html = html.replace(scriptTag, "") |
||||
html = html.replace("<!-- # INSERT SCRIPT HERE -->", scriptTag) |
||||
return html; |
||||
} |
||||
} |
||||
} |
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({ |
||||
envPrefix: ["VITE_", "NG_"], |
||||
server: { |
||||
port: 14402 |
||||
port: 14404 |
||||
}, |
||||
worker: { |
||||
format: 'es', |
||||
plugins : [ |
||||
topLevelAwait(), |
||||
wasm(), |
||||
viteSingleFile() |
||||
] |
||||
}, |
||||
plugins: [ |
||||
topLevelAwait(), |
||||
wasm(), |
||||
svelte({ |
||||
preprocess: [ |
||||
vitePreprocess(), |
||||
sveltePreprocess({ |
||||
typescript: false, |
||||
postcss: true, |
||||
}), |
||||
], |
||||
}), |
||||
svelteSVG({ |
||||
svgoConfig: { |
||||
plugins: [ |
||||
{ |
||||
name: 'preset-default', |
||||
params: { |
||||
overrides: { |
||||
// disable plugins
|
||||
removeViewBox: false, |
||||
}, |
||||
}, |
||||
}, |
||||
{ |
||||
name: 'prefixIds', |
||||
} |
||||
], |
||||
}, // See https://github.com/svg/svgo#configuration
|
||||
requireSuffix: true, // Set false to accept '.svg' without the '?component'
|
||||
}), |
||||
viteSingleFile(), |
||||
jsToBottom(), |
||||
viteSingleFile() |
||||
] |
||||
}) |
||||
|
@ -0,0 +1,13 @@ |
||||
const tailwindcss = require("tailwindcss"); |
||||
const autoprefixer = require("autoprefixer"); |
||||
|
||||
const config = { |
||||
plugins: [ |
||||
//Some plugins, like tailwindcss/nesting, need to run before Tailwind, |
||||
tailwindcss(), |
||||
//But others, like autoprefixer, need to run after, |
||||
autoprefixer, |
||||
], |
||||
}; |
||||
|
||||
module.exports = config; |
@ -0,0 +1,180 @@ |
||||
<!-- |
||||
// 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. |
||||
--> |
||||
|
||||
<script lang="ts"> |
||||
import { push, default as Router, querystring } from "svelte-spa-router"; |
||||
import { onMount, tick, onDestroy } from "svelte"; |
||||
import { |
||||
NotFound, |
||||
} from "@ng-org/ui-common/routes"; |
||||
import {error} from "./store"; |
||||
|
||||
import Home from "./routes/Home.svelte"; |
||||
import Error from "./routes/Error.svelte"; |
||||
import is_ip_private from 'private-ip'; |
||||
|
||||
function base64UrlDecode(str) { |
||||
const padding = '='.repeat((4 - str.length % 4) % 4); |
||||
const base64 = str.replace(/-/g, '+').replace(/_/g, '/') + padding; |
||||
return atob(base64); |
||||
} |
||||
|
||||
const routes = new Map(); |
||||
routes.set("/", Home); |
||||
routes.set("/error", Error); |
||||
routes.set("*", NotFound); |
||||
|
||||
// TODO: take this list from local API |
||||
const bsp_list = [ |
||||
"https://nextgraph.eu", |
||||
"https://nextgraph.one", |
||||
"https://stage1.nextgraph.eu", |
||||
]; |
||||
|
||||
let channel; |
||||
try { |
||||
channel = new BroadcastChannel("ng_bootstrap"); |
||||
channel.onmessage = (event) => { |
||||
if (event.origin !== location.origin) return; |
||||
if (!event.data.key) return; |
||||
(async () => { |
||||
try { |
||||
let bootstraps = JSON.parse(localStorage.getItem("ng_bootstrap") || "{}"); |
||||
if (event.data.value){ |
||||
//console.log("received added",event.data.key, event.data.value); |
||||
if(!bootstraps[event.data.key]) { |
||||
bootstraps[event.data.key] = event.data.value; |
||||
localStorage.setItem("ng_bootstrap",JSON.stringify(bootstraps)); |
||||
} |
||||
} else { |
||||
//console.log("received removed", event.data.key); |
||||
if ( bootstraps[event.data.key]) { |
||||
delete bootstraps[event.data.key]; |
||||
localStorage.setItem("ng_bootstrap",JSON.stringify(bootstraps)); |
||||
} |
||||
} |
||||
} catch (e) { |
||||
console.log("localStorage error in BroadcastChannel",e) |
||||
} |
||||
})(); |
||||
} |
||||
} |
||||
catch (e) { |
||||
console.error("error in BroadcastChannel",e) |
||||
} |
||||
|
||||
onMount(() => { |
||||
|
||||
const param = new URLSearchParams($querystring); |
||||
let method = param.get("m"); |
||||
let url; |
||||
let msgs; |
||||
try { |
||||
url = new URL(decodeURIComponent(param.get("ab"))); |
||||
msgs = JSON.parse(base64UrlDecode(param.get("b"))); |
||||
if (!method) |
||||
throw new Error("InvalidValue"); |
||||
} |
||||
catch (e) { |
||||
console.error(e); |
||||
error.set(e); |
||||
push("#/error"); |
||||
return; |
||||
} |
||||
let origin_url = url.origin; |
||||
let hostname = url.hostname; |
||||
|
||||
|
||||
let is_ng_box = origin_url === "https://nextgraph.app"; |
||||
let is_domain = false; |
||||
let is_lan = false; |
||||
let is_local = false; |
||||
if (!is_ng_box) { |
||||
is_local = origin_url.startsWith("http://localhost"); |
||||
if (!is_local) { |
||||
is_lan = !!is_ip_private(hostname); |
||||
if (!is_lan) |
||||
is_domain = bsp_list.includes(origin_url); |
||||
} |
||||
} |
||||
|
||||
function abort(error) { |
||||
console.error(error); |
||||
let u = url.toString(); |
||||
window.location.href = u + "&re=" + error; |
||||
throw new Error(error); |
||||
} |
||||
try { |
||||
let keys: Array<string> = []; |
||||
for (const data of msgs) { |
||||
|
||||
let key; |
||||
|
||||
//console.log("ng_bootstrap received msg",JSON.stringify(data), is_ng_box, is_domain,is_lan,is_local,new URL(origin_url).hostname, new URL(origin_url).hostname === data.domain, data.domain && is_domain && new URL(origin_url).hostname === data.domain ) |
||||
|
||||
if (data.ngbox && (is_ng_box || is_lan || is_local || is_domain)) { |
||||
key = "Self-hosted / NGbox"; |
||||
} else if (data.domain && is_domain && new URL(origin_url).hostname === data.domain ) { |
||||
key = data.domain; |
||||
//console.log("key for domain is", key) |
||||
} else if (data.localhost && (is_local || is_lan)) { |
||||
if (!data.peer_id) { |
||||
abort("missing peer_id of localhost"); |
||||
} |
||||
let port = Number(new URL(origin_url).port || '80'); |
||||
if (!import.meta.env.NG_DEV && !import.meta.env.DEV && is_local && port !== data.localhost) { |
||||
abort("mismatch of localhost port"); |
||||
} |
||||
key = `Local port ${data.localhost}`; |
||||
} else if (data.private && (is_lan || is_local )) { |
||||
if (!data.peer_id) { |
||||
abort("missing peer_id of LAN"); |
||||
} |
||||
key = `Network ${data.peer_id.substring(0,7)}`; |
||||
} else { |
||||
abort("mismatch between origin and msg"); |
||||
} |
||||
keys.push(key); |
||||
} |
||||
|
||||
try { |
||||
let bootstraps = JSON.parse(localStorage.getItem("ng_bootstrap") || "{}"); |
||||
for (const [i, key] of keys.entries()) { |
||||
//console.log(method, method === "add",bootstraps, !bootstraps[key]) |
||||
const value = msgs[i]; |
||||
if ( method === "add" && !bootstraps[key]) { |
||||
//console.log("adding..."+key) |
||||
bootstraps[key] = value; |
||||
if (channel) channel.postMessage({ key, value }); |
||||
//console.log("added",key,value); |
||||
} else if ( method === "remove" && bootstraps[key]) { |
||||
delete bootstraps[key]; |
||||
if (channel) channel.postMessage({ key }); |
||||
//console.log("removed",key); |
||||
} |
||||
} |
||||
localStorage.setItem("ng_bootstrap",JSON.stringify(bootstraps)); |
||||
} catch (e) { |
||||
abort("NoLocalStorage"); |
||||
} |
||||
|
||||
let u = url.toString(); |
||||
// url.searchParams.set('i', param.get("i")); |
||||
// url.searchParams.set('rs', param.get("rs")); |
||||
// url.searchParams.set('ab', "1"); |
||||
window.location.href = u + "&ab=1"; |
||||
}catch {} |
||||
}); |
||||
|
||||
</script> |
||||
|
||||
<Router {routes} /> |
||||
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
@ -0,0 +1,28 @@ |
||||
<!-- |
||||
// 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. |
||||
--> |
||||
|
||||
<!-- |
||||
Error page (?o=https://domain.tld was not provided or is invalid) |
||||
--> |
||||
|
||||
<script> |
||||
import { ExclamationTriangle} from "svelte-heros-v2"; |
||||
import {error} from "../store"; |
||||
</script> |
||||
|
||||
<div class="text-center max-w-6xl lg:px-8 mx-auto px-4 text-green-800"> |
||||
<div class=" max-w-6xl lg:px-8 mx-auto px-4 text-red-800"> |
||||
<ExclamationTriangle class="animate-bounce mt-10 h-16 w-16 mx-auto" /> |
||||
<p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5"> |
||||
{$error} |
||||
</p> |
||||
</div> |
||||
</div> |
@ -0,0 +1,20 @@ |
||||
<!-- |
||||
// 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. |
||||
--> |
||||
|
||||
<script> |
||||
import Logo from "../assets/nextgraph.svg?component"; |
||||
|
||||
</script> |
||||
|
||||
<a href="https://nextgraph.org"> |
||||
<Logo style="margin:auto;" class="logo block h-40" alt="NextGraph" /> |
||||
</a> |
||||
|
@ -0,0 +1,15 @@ |
||||
// 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.
|
||||
|
||||
import { |
||||
writable, |
||||
type Writable, |
||||
} from "svelte/store"; |
||||
|
||||
export const error = writable(""); |
@ -1,32 +0,0 @@ |
||||
var crypto = require('crypto') |
||||
, fs = require('fs') |
||||
const {gzip, } = require('node-gzip'); |
||||
|
||||
var algorithm = 'sha256' |
||||
, shasum = crypto.createHash(algorithm) |
||||
|
||||
const sha_file = './dist/index.sha256'; |
||||
const gzip_file = './dist/index.gzip'; |
||||
var filename = './dist/index.html' |
||||
, s = fs.ReadStream(filename) |
||||
|
||||
var bufs = []; |
||||
s.on('data', function(data) { |
||||
shasum.update(data) |
||||
bufs.push(data); |
||||
}) |
||||
|
||||
s.on('end', function() { |
||||
var hash = shasum.digest('hex') |
||||
console.log(hash + ' ' + filename) |
||||
|
||||
fs.writeFileSync(sha_file, hash, 'utf8'); |
||||
|
||||
var buf = Buffer.concat(bufs); |
||||
gzip(buf).then((compressed) => {fs.writeFileSync(gzip_file, compressed);}); |
||||
|
||||
fs.rm(filename,()=>{}); |
||||
|
||||
}) |
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 2.9 KiB |
@ -1,26 +0,0 @@ |
||||
// 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.
|
||||
|
||||
//import * as sdk from "@ng-org/wasm-tools-auth";
|
||||
import { fromWritablePort } from 'remote-web-streams'; |
||||
|
||||
self.onmessage = (event) => { |
||||
(async function() { |
||||
const { method, args, port } = event.data; |
||||
const writable = fromWritablePort(port); |
||||
const writer = writable.getWriter(); |
||||
console.log("Message received by worker", method, args); |
||||
|
||||
let ret = await Reflect.apply(sdk[method], null, args); |
||||
writer.write(ret); |
||||
writer.close(); |
||||
})(); |
||||
} |
||||
|
||||
console.log("worker loaded"); |
Before Width: | Height: | Size: 1.1 KiB |
File diff suppressed because it is too large
Load Diff
@ -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,57 @@ |
||||
# example-webapp-react |
||||
|
||||
Example of a Web app made with NextGraph, using React and LDO, and Vite |
||||
|
||||
## 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 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 @ng-org/web package in dev mode: |
||||
|
||||
``` |
||||
pnpm run -C ../../web 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: |
||||
|
||||
``` |
||||
pnpm install |
||||
pnpm 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+LDO example</title> |
||||
</head> |
||||
<body> |
||||
<div id="app"></div> |
||||
<script type="module" src="/src/main.tsx"></script> |
||||
</body> |
||||
</html> |
@ -0,0 +1,40 @@ |
||||
{ |
||||
"name": "@ng-org/example-react-ldo", |
||||
"private": true, |
||||
"version": "0.0.0", |
||||
"type": "module", |
||||
"scripts": { |
||||
"dev": "vite", |
||||
"build": "tsc --noEmit && vite build", |
||||
"lint": "eslint .", |
||||
"preview": "vite preview", |
||||
"build:ldo": "ldo build --input src/.shapes --output src/.ldo" |
||||
}, |
||||
"dependencies": { |
||||
"@ldo/connected-nextgraph": "^1.0.0-alpha.15", |
||||
"@ldo/ldo": "^1.0.0-alpha.15", |
||||
"@ldo/react": "^1.0.0-alpha.15", |
||||
"@ng-org/web": "workspace:*", |
||||
"react": "^19.0.0", |
||||
"react-dom": "^19.0.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,68 @@ |
||||
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", |
||||
}, |
||||
}, |
||||
}, |
||||
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", |
||||
}, |
||||
}, |
||||
}, |
||||
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", |
||||
}, |
||||
}, |
||||
}, |
||||
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", |
||||
}, |
||||
}; |
@ -0,0 +1,115 @@ |
||||
import { Schema } from "shexj"; |
||||
|
||||
/** |
||||
* ============================================================================= |
||||
* contactSchema: ShexJ Schema for contact |
||||
* ============================================================================= |
||||
*/ |
||||
export const contactSchema: Schema = { |
||||
type: "Schema", |
||||
shapes: [ |
||||
{ |
||||
id: "did:ng:n:g:x:social:contact#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", |
||||
}, |
||||
min: 0, |
||||
max: 1, |
||||
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.", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
], |
||||
}, |
||||
extra: ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"], |
||||
}, |
||||
}, |
||||
], |
||||
}; |
@ -0,0 +1,19 @@ |
||||
import { ShapeType } from "@ldo/ldo"; |
||||
import { contactSchema } from "./contact.schema"; |
||||
import { contactContext } from "./contact.context"; |
||||
import { SocialContact } from "./contact.typings"; |
||||
|
||||
/** |
||||
* ============================================================================= |
||||
* LDO ShapeTypes contact |
||||
* ============================================================================= |
||||
*/ |
||||
|
||||
/** |
||||
* SocialContact ShapeType |
||||
*/ |
||||
export const SocialContactShapeType: ShapeType<SocialContact> = { |
||||
schema: contactSchema, |
||||
shape: "did:ng:n:g:x:social:contact#SocialContact", |
||||
context: contactContext, |
||||
}; |
@ -0,0 +1,37 @@ |
||||
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; |
||||
} |
@ -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,26 @@ |
||||
|
||||
# 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 ngx: <did:ng:n:g:x:social:contact#> |
||||
|
||||
ngx: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." ; |
||||
} |
@ -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,56 @@ |
||||
|
||||
|
||||
.centered { |
||||
/*max-width: 1280px;*/ |
||||
margin: 0 auto; |
||||
padding: 0rem; |
||||
text-align: center; |
||||
width: fit-content; |
||||
} |
||||
|
||||
.contact { |
||||
width: 300px; |
||||
height: 100px; |
||||
background-color: #f6f6f6; |
||||
position: relative; |
||||
overflow-wrap: anywhere; |
||||
} |
||||
|
||||
.name { |
||||
width: 300px; |
||||
position: absolute; |
||||
left: 0; |
||||
top: 0; |
||||
padding: 5px; |
||||
height: 35px; |
||||
overflow: hidden; |
||||
background-color: #e0e0e0d0; |
||||
overflow: hidden; |
||||
text-overflow: ellipsis; |
||||
white-space: nowrap; |
||||
} |
||||
|
||||
.email-logo { |
||||
position: absolute; |
||||
left: 5px; |
||||
top: 35px; |
||||
} |
||||
|
||||
.email { |
||||
width: 270px; |
||||
position: absolute; |
||||
left: 30px; |
||||
top: 30px; |
||||
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 "../../../../../app/ui-common/src/styles.css"; |
||||
|
||||
const App: FunctionComponent = () => { |
||||
|
||||
return ( |
||||
<div className="App"> |
||||
<BrowserNGLdoProvider> |
||||
<Header /> |
||||
<Contacts />
|
||||
</BrowserNGLdoProvider> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
export default App |
@ -0,0 +1,29 @@ |
||||
import { FunctionComponent } from "react"; |
||||
import { useNextGraphAuth } from "./reactMethods"; |
||||
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(SocialContactShapeType, session.sessionId && nuri ? nuri.substring(0,53) : undefined); |
||||
|
||||
if (!session.sessionId || !nuri) return <></>; |
||||
|
||||
return <> |
||||
{contact.fn? (
|
||||
<div className="contact" title={nuri}> |
||||
<span className="name">
|
||||
{contact.fn} |
||||
</span> |
||||
<svg className="w-6 h-6 inline email-logo" 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="M16.5 12a4.5 4.5 0 1 1-9 0 4.5 4.5 0 0 1 9 0Zm0 0c0 1.657 1.007 3 2.25 3S21 13.657 21 12a9 9 0 1 0-2.636 6.364M16.5 12V8.25"></path> |
||||
</svg> |
||||
<span className="email text-left"> |
||||
email: {contact.hasEmail} |
||||
</span> |
||||
</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,44 @@ |
||||
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 your contact manager</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 {ng, init} from "@ng-org/web"; |
||||
|
||||
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 (should it be launched as a unique instance, or will your app create many docs in the system)
|
||||
, []); //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" } |
||||
] |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue