parent
0dbe6a5e21
commit
a603cc02f4
@ -0,0 +1,37 @@ |
|||||||
|
import type { FunctionComponent, PropsWithChildren } from "react"; |
||||||
|
import React, { useCallback } from "react"; |
||||||
|
import { useSolidAuth } from "@ldo/solid-react"; |
||||||
|
|
||||||
|
const Layout: FunctionComponent<PropsWithChildren> = ({ children }) => { |
||||||
|
const { login, session, logout } = useSolidAuth(); |
||||||
|
|
||||||
|
const loginCb = useCallback(async () => { |
||||||
|
const issuer = prompt( |
||||||
|
"Enter your Pod Provider", |
||||||
|
"https://solidcommunity.net", |
||||||
|
); |
||||||
|
if (issuer) { |
||||||
|
await login(issuer); |
||||||
|
} |
||||||
|
}, []); |
||||||
|
|
||||||
|
return ( |
||||||
|
<div> |
||||||
|
<h1>LDO Solid React Test</h1> |
||||||
|
{session.isLoggedIn ? ( |
||||||
|
<div> |
||||||
|
<p> |
||||||
|
Logged in as {session.webId}{" "} |
||||||
|
<button onClick={logout}>Log Out</button> |
||||||
|
</p> |
||||||
|
<hr /> |
||||||
|
{children} |
||||||
|
</div> |
||||||
|
) : ( |
||||||
|
<button onClick={loginCb}>Log In</button> |
||||||
|
)} |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default Layout; |
@ -1,19 +1,13 @@ |
|||||||
import type { FunctionComponent } from "react"; |
import type { FunctionComponent } from "react"; |
||||||
import React from "react"; |
import React from "react"; |
||||||
import Profile from "./Profile"; |
import { Layout } from "./Layout"; |
||||||
import { SolidAuthProvider, LdoProvider } from "@ldo/solid-react"; |
import { BrowserSolidLdoProvider } from "@ldo/solid-react"; |
||||||
import { fetch } from "solid-authn-react-native"; |
|
||||||
import Layout from "./Layout"; |
|
||||||
|
|
||||||
const ProfileApp: FunctionComponent = () => { |
const ProfileApp: FunctionComponent = () => { |
||||||
return ( |
return ( |
||||||
<SolidAuthProvider> |
<BrowserSolidLdoProvider> |
||||||
<LdoProvider fetch={fetch}> |
<Layout /> |
||||||
<Layout> |
</BrowserSolidLdoProvider> |
||||||
<Profile /> |
|
||||||
</Layout> |
|
||||||
</LdoProvider> |
|
||||||
</SolidAuthProvider> |
|
||||||
); |
); |
||||||
}; |
}; |
||||||
export default ProfileApp; |
export default ProfileApp; |
||||||
|
@ -0,0 +1,10 @@ |
|||||||
|
import React from "react"; |
||||||
|
import type { FunctionComponent } from "react"; |
||||||
|
|
||||||
|
export const Dashboard: FunctionComponent = () => { |
||||||
|
return ( |
||||||
|
<div> |
||||||
|
<p>Dashboard</p> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
@ -0,0 +1,12 @@ |
|||||||
|
import React from "react"; |
||||||
|
import type { FunctionComponent } from "react"; |
||||||
|
import { useParams } from "react-router-dom"; |
||||||
|
|
||||||
|
export const Media: FunctionComponent = () => { |
||||||
|
const { uri } = useParams(); |
||||||
|
return ( |
||||||
|
<div> |
||||||
|
<p>Media: {uri}</p> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
@ -1,16 +0,0 @@ |
|||||||
{ |
|
||||||
"sourceType": "unambiguous", |
|
||||||
"presets": [ |
|
||||||
[ |
|
||||||
"@babel/preset-env", |
|
||||||
{ |
|
||||||
"targets": { |
|
||||||
"chrome": 100 |
|
||||||
} |
|
||||||
} |
|
||||||
], |
|
||||||
"@babel/preset-typescript", |
|
||||||
"@babel/preset-react" |
|
||||||
], |
|
||||||
"plugins": [] |
|
||||||
} |
|
@ -0,0 +1,96 @@ |
|||||||
|
import React, { useCallback, useEffect, useMemo, useState } from "react"; |
||||||
|
import type { FunctionComponent, PropsWithChildren } from "react"; |
||||||
|
import type { LoginOptions, SessionInfo } from "./SolidAuthContext"; |
||||||
|
import { SolidAuthContext } from "./SolidAuthContext"; |
||||||
|
import { |
||||||
|
getDefaultSession, |
||||||
|
handleIncomingRedirect, |
||||||
|
login as libraryLogin, |
||||||
|
logout as libraryLogout, |
||||||
|
fetch as libraryFetch, |
||||||
|
} from "@inrupt/solid-client-authn-browser"; |
||||||
|
|
||||||
|
const PRE_REDIRECT_URI = "PRE_REDIRECT_URI"; |
||||||
|
|
||||||
|
export const BrowserSolidLdoProvider: FunctionComponent<PropsWithChildren> = ({ |
||||||
|
children, |
||||||
|
}) => { |
||||||
|
const [session, setSession] = useState<SessionInfo>(getDefaultSession().info); |
||||||
|
const [ranInitialAuthCheck, setRanInitialAuthCheck] = useState(false); |
||||||
|
|
||||||
|
const runInitialAuthCheck = useCallback(async () => { |
||||||
|
if (!window.localStorage.getItem(PRE_REDIRECT_URI)) { |
||||||
|
window.localStorage.setItem(PRE_REDIRECT_URI, window.location.href); |
||||||
|
} |
||||||
|
|
||||||
|
await handleIncomingRedirect({ |
||||||
|
restorePreviousSession: true, |
||||||
|
}); |
||||||
|
setSession({ ...getDefaultSession().info }); |
||||||
|
|
||||||
|
window.history.replaceState( |
||||||
|
{}, |
||||||
|
"", |
||||||
|
window.localStorage.getItem(PRE_REDIRECT_URI), |
||||||
|
); |
||||||
|
window.localStorage.removeItem(PRE_REDIRECT_URI); |
||||||
|
|
||||||
|
setRanInitialAuthCheck(true); |
||||||
|
}, []); |
||||||
|
|
||||||
|
const login = useCallback(async (issuer: string, options?: LoginOptions) => { |
||||||
|
console.log("Before full options"); |
||||||
|
const fullOptions = { |
||||||
|
redirectUrl: window.location.href, |
||||||
|
clientName: "Solid App", |
||||||
|
oidcIssuer: issuer, |
||||||
|
...options, |
||||||
|
}; |
||||||
|
console.log("After full options"); |
||||||
|
window.localStorage.setItem(PRE_REDIRECT_URI, fullOptions.redirectUrl); |
||||||
|
console.log("Set Item"); |
||||||
|
console.log(fullOptions); |
||||||
|
await libraryLogin(fullOptions); |
||||||
|
console.log("After login"); |
||||||
|
setSession({ ...getDefaultSession().info }); |
||||||
|
}, []); |
||||||
|
|
||||||
|
const logout = useCallback(async () => { |
||||||
|
await libraryLogout(); |
||||||
|
setSession({ ...getDefaultSession().info }); |
||||||
|
}, []); |
||||||
|
|
||||||
|
const signUp = useCallback( |
||||||
|
async (issuer: string, options?: LoginOptions) => { |
||||||
|
// The typings on @inrupt/solid-client-authn-core have not yet been updated
|
||||||
|
// TODO: remove this ts-ignore when they are updated.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
return login(issuer, { ...options, prompt: "create" }); |
||||||
|
}, |
||||||
|
[login], |
||||||
|
); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
runInitialAuthCheck(); |
||||||
|
}, []); |
||||||
|
|
||||||
|
const solidAuthFunctions = useMemo( |
||||||
|
() => ({ |
||||||
|
runInitialAuthCheck, |
||||||
|
login, |
||||||
|
logout, |
||||||
|
signUp, |
||||||
|
session, |
||||||
|
ranInitialAuthCheck, |
||||||
|
fetch: libraryFetch, |
||||||
|
}), |
||||||
|
[login, logout, ranInitialAuthCheck, runInitialAuthCheck, session, signUp], |
||||||
|
); |
||||||
|
|
||||||
|
return ( |
||||||
|
<SolidAuthContext.Provider value={solidAuthFunctions}> |
||||||
|
{children} |
||||||
|
</SolidAuthContext.Provider> |
||||||
|
); |
||||||
|
}; |
@ -1,19 +0,0 @@ |
|||||||
import { createContext, useContext } from "react"; |
|
||||||
import type { BinaryResourceStoreDependencies } from "./document/resource/binaryResource/BinaryResourceStore"; |
|
||||||
import type { DataResourceStoreDependencies } from "./document/resource/dataResource/DataResourceStore"; |
|
||||||
import type { AccessRulesStoreDependencies } from "./document/accessRules/AccessRulesStore"; |
|
||||||
import type { ContainerResourceStoreDependencies } from "./document/resource/dataResource/containerResource/ContainerResourceStore"; |
|
||||||
|
|
||||||
export interface LdoContextData |
|
||||||
extends BinaryResourceStoreDependencies, |
|
||||||
DataResourceStoreDependencies, |
|
||||||
AccessRulesStoreDependencies, |
|
||||||
ContainerResourceStoreDependencies {} |
|
||||||
|
|
||||||
// No default parameter is required as it will be set in the provider
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
const LdoContext = createContext<LdoContextData>({}); |
|
||||||
|
|
||||||
export const LdoContextProvider = LdoContext.Provider; |
|
||||||
export const useLdoContext = () => useContext(LdoContext); |
|
@ -1,59 +0,0 @@ |
|||||||
import type { FunctionComponent, PropsWithChildren } from "react"; |
|
||||||
import React, { useEffect, useMemo } from "react"; |
|
||||||
import crossFetch from "cross-fetch"; |
|
||||||
import { createLdoDataset } from "@ldo/ldo"; |
|
||||||
import type { LdoContextData } from "./LdoContext"; |
|
||||||
import { LdoContextProvider } from "./LdoContext"; |
|
||||||
import { UpdateManager } from "./ldoHooks/helpers/UpdateManager"; |
|
||||||
import type { Dataset } from "@rdfjs/types"; |
|
||||||
|
|
||||||
export interface LdoProviderProps extends PropsWithChildren { |
|
||||||
fetch?: typeof fetch; |
|
||||||
dataset?: Dataset; |
|
||||||
onDocumentError?: LdoContextData["onDocumentError"]; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Main Ldo Provider |
|
||||||
*/ |
|
||||||
export const LdoProvider: FunctionComponent< |
|
||||||
PropsWithChildren<LdoProviderProps> |
|
||||||
> = ({ dataset, fetch, onDocumentError, children }) => { |
|
||||||
const finalFetch = useMemo(() => fetch || crossFetch, [fetch]); |
|
||||||
const ldoDataset = useMemo(() => createLdoDataset(dataset), [dataset]); |
|
||||||
|
|
||||||
// Initialize storeDependencies before render
|
|
||||||
const storeDependencies = useMemo(() => { |
|
||||||
// Ingnoring this because we're setting up circular dependencies
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
const dependencies: LdoContextData = { |
|
||||||
onDocumentError, |
|
||||||
fetch: finalFetch, |
|
||||||
dataset: ldoDataset, |
|
||||||
updateManager: new UpdateManager(), |
|
||||||
}; |
|
||||||
const binaryResourceStore = new BinaryResourceStore(dependencies); |
|
||||||
const dataResourceStore = new DataResourceStore(dependencies); |
|
||||||
const containerResourceStore = new ContainerResourceStore(dependencies); |
|
||||||
const accessRulesStore = new AccessRulesStore(dependencies); |
|
||||||
dependencies.binaryResourceStore = binaryResourceStore; |
|
||||||
dependencies.dataResourceStore = dataResourceStore; |
|
||||||
dependencies.containerResourceStore = containerResourceStore; |
|
||||||
dependencies.accessRulesStore = accessRulesStore; |
|
||||||
return dependencies; |
|
||||||
}, []); |
|
||||||
|
|
||||||
// Update the resource manager in case fetch or ldo dataset changes
|
|
||||||
useEffect(() => { |
|
||||||
storeDependencies.fetch = finalFetch; |
|
||||||
storeDependencies.dataset = ldoDataset; |
|
||||||
storeDependencies.onDocumentError = onDocumentError; |
|
||||||
}, [finalFetch, ldoDataset, onDocumentError]); |
|
||||||
|
|
||||||
return ( |
|
||||||
<LdoContextProvider value={storeDependencies}> |
|
||||||
{children} |
|
||||||
</LdoContextProvider> |
|
||||||
); |
|
||||||
}; |
|
@ -0,0 +1,26 @@ |
|||||||
|
import type { |
||||||
|
ISessionInfo, |
||||||
|
ILoginInputOptions, |
||||||
|
} from "@inrupt/solid-client-authn-core"; |
||||||
|
import { createContext, useContext } from "react"; |
||||||
|
|
||||||
|
export type SessionInfo = ISessionInfo; |
||||||
|
export type LoginOptions = ILoginInputOptions; |
||||||
|
|
||||||
|
export interface SolidAuthFunctions { |
||||||
|
login: (issuer: string, loginOptions?: LoginOptions) => Promise<void>; |
||||||
|
logout: () => Promise<void>; |
||||||
|
signUp: (issuer: string, loginOptions?: LoginOptions) => Promise<void>; |
||||||
|
fetch: typeof fetch; |
||||||
|
session: SessionInfo; |
||||||
|
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 SolidAuthContext = createContext<SolidAuthFunctions>(undefined); |
||||||
|
|
||||||
|
export function useSolidAuth() { |
||||||
|
return useContext(SolidAuthContext); |
||||||
|
} |
@ -1,98 +0,0 @@ |
|||||||
import { useCallback, useEffect, useMemo, useState } from "react"; |
|
||||||
import type { ISessionInfo } from "solid-authn-react-native"; |
|
||||||
import { |
|
||||||
handleIncomingRedirect, |
|
||||||
login as libraryLogin, |
|
||||||
getDefaultSession, |
|
||||||
logout as libraryLogout, |
|
||||||
fetch as libraryFetch, |
|
||||||
} from "solid-authn-react-native"; |
|
||||||
|
|
||||||
import { createGlobalHook } from "./util/createGlobalHook"; |
|
||||||
|
|
||||||
const PRE_REDIRECT_URI = "PRE_REDIRECT_URI"; |
|
||||||
|
|
||||||
interface AuthGlobalHookReturn { |
|
||||||
runInitialAuthCheck: () => Promise<void>; |
|
||||||
login: (issuer: string) => Promise<void>; |
|
||||||
logout: () => Promise<void>; |
|
||||||
signUp: (issuer: string) => Promise<void>; |
|
||||||
fetch: typeof fetch; |
|
||||||
session: ISessionInfo; |
|
||||||
ranInitialAuthCheck: boolean; |
|
||||||
} |
|
||||||
|
|
||||||
function useAuthGlobalHookFunc(): AuthGlobalHookReturn { |
|
||||||
const [session, setSession] = useState<ISessionInfo>( |
|
||||||
getDefaultSession().info, |
|
||||||
); |
|
||||||
const [ranInitialAuthCheck, setRanInitialAuthCheck] = useState(false); |
|
||||||
|
|
||||||
const runInitialAuthCheck = useCallback(async () => { |
|
||||||
// TODO: Change this to dependency injection so it works in React Native
|
|
||||||
if (!window.localStorage.getItem(PRE_REDIRECT_URI)) { |
|
||||||
window.localStorage.setItem(PRE_REDIRECT_URI, window.location.href); |
|
||||||
} |
|
||||||
|
|
||||||
await handleIncomingRedirect({ |
|
||||||
restorePreviousSession: true, |
|
||||||
}); |
|
||||||
setSession({ ...getDefaultSession().info }); |
|
||||||
|
|
||||||
window.history.replaceState( |
|
||||||
{}, |
|
||||||
"", |
|
||||||
window.localStorage.getItem(PRE_REDIRECT_URI), |
|
||||||
); |
|
||||||
window.localStorage.removeItem(PRE_REDIRECT_URI); |
|
||||||
|
|
||||||
setRanInitialAuthCheck(true); |
|
||||||
}, []); |
|
||||||
|
|
||||||
const login = useCallback( |
|
||||||
async (issuer: string, clientName = "Solid App") => { |
|
||||||
window.localStorage.setItem(PRE_REDIRECT_URI, window.location.href); |
|
||||||
await libraryLogin({ |
|
||||||
oidcIssuer: issuer, |
|
||||||
// TODO: this ties this to in-browser use
|
|
||||||
redirectUrl: window.location.href, |
|
||||||
clientName, |
|
||||||
}); |
|
||||||
setSession({ ...getDefaultSession().info }); |
|
||||||
}, |
|
||||||
[], |
|
||||||
); |
|
||||||
|
|
||||||
const logout = useCallback(async () => { |
|
||||||
await libraryLogout(); |
|
||||||
setSession({ ...getDefaultSession().info }); |
|
||||||
}, []); |
|
||||||
|
|
||||||
const signUp = useCallback(async (issuer: string) => { |
|
||||||
/* Do nothing for now */ |
|
||||||
console.log(`Signup Pressed with issuer ${issuer}`); |
|
||||||
}, []); |
|
||||||
|
|
||||||
useEffect(() => { |
|
||||||
runInitialAuthCheck(); |
|
||||||
}, []); |
|
||||||
|
|
||||||
return useMemo( |
|
||||||
() => ({ |
|
||||||
runInitialAuthCheck, |
|
||||||
login, |
|
||||||
logout, |
|
||||||
signUp, |
|
||||||
session, |
|
||||||
ranInitialAuthCheck, |
|
||||||
fetch: libraryFetch, |
|
||||||
}), |
|
||||||
[login, logout, ranInitialAuthCheck, runInitialAuthCheck, session, signUp], |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
const authGlobalHook = createGlobalHook(useAuthGlobalHookFunc); |
|
||||||
|
|
||||||
export const SolidAuthContext = authGlobalHook.Context; |
|
||||||
export const SolidAuthProvider = authGlobalHook.Provider; |
|
||||||
export const useSolidAuth = authGlobalHook.useGlobal; |
|
@ -0,0 +1,62 @@ |
|||||||
|
import React, { createContext } from "react"; |
||||||
|
import { |
||||||
|
useMemo, |
||||||
|
type FunctionComponent, |
||||||
|
type PropsWithChildren, |
||||||
|
useRef, |
||||||
|
useEffect, |
||||||
|
} from "react"; |
||||||
|
import { useSolidAuth } from "./SolidAuthContext"; |
||||||
|
import type { SolidLdoDataset, OnDocumentErrorCallback } from "@ldo/solid"; |
||||||
|
import { createSolidLdoDataset } from "@ldo/solid"; |
||||||
|
|
||||||
|
export const SolidLdoDatasetReactContext = |
||||||
|
// This will be set in the provider
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
createContext<SolidLdoDataset>(undefined); |
||||||
|
|
||||||
|
export interface SolidLdoProviderProps extends PropsWithChildren { |
||||||
|
onDocumentError?: OnDocumentErrorCallback; |
||||||
|
} |
||||||
|
|
||||||
|
export const SolidLdoProvider: FunctionComponent<SolidLdoProviderProps> = ({ |
||||||
|
onDocumentError, |
||||||
|
children, |
||||||
|
}) => { |
||||||
|
const { fetch } = useSolidAuth(); |
||||||
|
const curOnDocumentError = useRef(onDocumentError); |
||||||
|
|
||||||
|
// Initialize storeDependencies before render
|
||||||
|
const solidLdoDataset = useMemo(() => { |
||||||
|
const ldoDataset = createSolidLdoDataset({ |
||||||
|
fetch, |
||||||
|
}); |
||||||
|
if (curOnDocumentError.current) { |
||||||
|
ldoDataset.onDocumentError(curOnDocumentError.current); |
||||||
|
} |
||||||
|
return ldoDataset; |
||||||
|
}, []); |
||||||
|
|
||||||
|
// Keep context in sync with props
|
||||||
|
useEffect(() => { |
||||||
|
solidLdoDataset.context.fetch = fetch; |
||||||
|
}, [fetch]); |
||||||
|
useEffect(() => { |
||||||
|
if (curOnDocumentError.current) { |
||||||
|
solidLdoDataset.offDocumentError(curOnDocumentError.current); |
||||||
|
} |
||||||
|
if (onDocumentError) { |
||||||
|
solidLdoDataset.onDocumentError(onDocumentError); |
||||||
|
curOnDocumentError.current = onDocumentError; |
||||||
|
} else { |
||||||
|
curOnDocumentError.current = undefined; |
||||||
|
} |
||||||
|
}, [onDocumentError]); |
||||||
|
|
||||||
|
return ( |
||||||
|
<SolidLdoDatasetReactContext.Provider value={solidLdoDataset}> |
||||||
|
{children} |
||||||
|
</SolidLdoDatasetReactContext.Provider> |
||||||
|
); |
||||||
|
}; |
@ -1,18 +0,0 @@ |
|||||||
import { useMemo } from "react"; |
|
||||||
import { useLdoContext } from "../LdoContext"; |
|
||||||
import type { UseDocumentOptions } from "./useDocument"; |
|
||||||
import { useDocument } from "./useDocument"; |
|
||||||
import type { Resource } from "../document/resource/Resource"; |
|
||||||
|
|
||||||
export function useAccessRules( |
|
||||||
resource: string | Resource, |
|
||||||
options?: UseDocumentOptions, |
|
||||||
) { |
|
||||||
const { dataResourceStore, accessRulesStore } = useLdoContext(); |
|
||||||
const realResource = useMemo(() => { |
|
||||||
return typeof resource === "string" |
|
||||||
? dataResourceStore.get(resource) |
|
||||||
: resource; |
|
||||||
}, [resource]); |
|
||||||
return useDocument(realResource, accessRulesStore, options); |
|
||||||
} |
|
@ -1,8 +0,0 @@ |
|||||||
import { useLdoContext } from "../LdoContext"; |
|
||||||
import type { UseDocumentOptions } from "./useDocument"; |
|
||||||
import { useDocument } from "./useDocument"; |
|
||||||
|
|
||||||
export function useBinaryResource(uri: string, options?: UseDocumentOptions) { |
|
||||||
const { binaryResourceStore } = useLdoContext(); |
|
||||||
return useDocument(uri, binaryResourceStore, options); |
|
||||||
} |
|
@ -1,11 +0,0 @@ |
|||||||
import { useLdoContext } from "../LdoContext"; |
|
||||||
import type { UseDocumentOptions } from "./useDocument"; |
|
||||||
import { useDocument } from "./useDocument"; |
|
||||||
|
|
||||||
export function useContainerResource( |
|
||||||
uri: string, |
|
||||||
options?: UseDocumentOptions, |
|
||||||
) { |
|
||||||
const { containerResourceStore } = useLdoContext(); |
|
||||||
return useDocument(uri, containerResourceStore, options); |
|
||||||
} |
|
@ -1,8 +0,0 @@ |
|||||||
import { useLdoContext } from "../LdoContext"; |
|
||||||
import type { UseDocumentOptions } from "./useDocument"; |
|
||||||
import { useDocument } from "./useDocument"; |
|
||||||
|
|
||||||
export function useDataResource(uri: string, options?: UseDocumentOptions) { |
|
||||||
const { dataResourceStore } = useLdoContext(); |
|
||||||
return useDocument(uri, dataResourceStore, options); |
|
||||||
} |
|
@ -1,45 +0,0 @@ |
|||||||
import { useEffect, useMemo } from "react"; |
|
||||||
import type { |
|
||||||
DocumentStore, |
|
||||||
DocumentStoreDependencies, |
|
||||||
} from "../document/DocumentStore"; |
|
||||||
import type { FetchableDocument } from "../document/FetchableDocument"; |
|
||||||
import { useForceUpdate } from "../util/useForceReload"; |
|
||||||
|
|
||||||
export interface UseDocumentOptions { |
|
||||||
suppressLoadOnMount: boolean; |
|
||||||
} |
|
||||||
|
|
||||||
export function useDocument< |
|
||||||
DocumentType extends FetchableDocument, |
|
||||||
Initializer, |
|
||||||
>( |
|
||||||
initializer: Initializer, |
|
||||||
documentStore: DocumentStore< |
|
||||||
DocumentType, |
|
||||||
Initializer, |
|
||||||
DocumentStoreDependencies |
|
||||||
>, |
|
||||||
options?: UseDocumentOptions, |
|
||||||
) { |
|
||||||
const document = useMemo(() => { |
|
||||||
return documentStore.get(initializer); |
|
||||||
}, [initializer, documentStore]); |
|
||||||
|
|
||||||
const forceUpdate = useForceUpdate(); |
|
||||||
|
|
||||||
useEffect(() => { |
|
||||||
// Set up the listener for state update
|
|
||||||
function onStateUpdateCallback() { |
|
||||||
forceUpdate(); |
|
||||||
} |
|
||||||
document.onStateUpdate(onStateUpdateCallback); |
|
||||||
// Load the resource if load on mount is true
|
|
||||||
if (!options?.suppressLoadOnMount) { |
|
||||||
document.read(); |
|
||||||
} |
|
||||||
return () => document.offStateUpdate(onStateUpdateCallback); |
|
||||||
}, []); |
|
||||||
|
|
||||||
return document; |
|
||||||
} |
|
@ -1,14 +1,2 @@ |
|||||||
// documentHooks
|
export * from "./BrowserSolidLdoProvider"; |
||||||
export * from "./documentHooks/useAccessRules"; |
export * from "./SolidAuthContext"; |
||||||
export * from "./documentHooks/useBinaryResource"; |
|
||||||
export * from "./documentHooks/useContainerResource"; |
|
||||||
export * from "./documentHooks/useDataResource"; |
|
||||||
export * from "./documentHooks/useDocument"; |
|
||||||
|
|
||||||
// ldoHooks
|
|
||||||
export * from "./ldoHooks/useSubject"; |
|
||||||
|
|
||||||
// export
|
|
||||||
export * from "./useLdo"; |
|
||||||
export * from "./LdoProvider"; |
|
||||||
export * from "./SolidAuthProvider"; |
|
||||||
|
@ -1,36 +0,0 @@ |
|||||||
import type { ContextDefinition } from "jsonld"; |
|
||||||
|
|
||||||
/** |
|
||||||
* ============================================================================= |
|
||||||
* solidContext: JSONLD Context for solid |
|
||||||
* ============================================================================= |
|
||||||
*/ |
|
||||||
export const solidContext: ContextDefinition = { |
|
||||||
type: { |
|
||||||
"@id": "@type", |
|
||||||
"@container": "@set", |
|
||||||
}, |
|
||||||
Container: "http://www.w3.org/ns/ldp#Container", |
|
||||||
Resource: "http://www.w3.org/ns/ldp#Resource", |
|
||||||
modified: { |
|
||||||
"@id": "http://purl.org/dc/terms/modified", |
|
||||||
"@type": "http://www.w3.org/2001/XMLSchema#string", |
|
||||||
"@container": "@set", |
|
||||||
}, |
|
||||||
contains: { |
|
||||||
"@id": "http://www.w3.org/ns/ldp#contains", |
|
||||||
"@type": "@id", |
|
||||||
"@container": "@set", |
|
||||||
}, |
|
||||||
Resource2: "http://www.w3.org/ns/iana/media-types/text/turtle#Resource", |
|
||||||
mtime: { |
|
||||||
"@id": "http://www.w3.org/ns/posix/stat#mtime", |
|
||||||
"@type": "http://www.w3.org/2001/XMLSchema#decimal", |
|
||||||
"@container": "@set", |
|
||||||
}, |
|
||||||
size: { |
|
||||||
"@id": "http://www.w3.org/ns/posix/stat#size", |
|
||||||
"@type": "http://www.w3.org/2001/XMLSchema#integer", |
|
||||||
"@container": "@set", |
|
||||||
}, |
|
||||||
}; |
|
@ -1,214 +0,0 @@ |
|||||||
import type { Schema } from "shexj"; |
|
||||||
|
|
||||||
/** |
|
||||||
* ============================================================================= |
|
||||||
* solidSchema: ShexJ Schema for solid |
|
||||||
* ============================================================================= |
|
||||||
*/ |
|
||||||
export const solidSchema: 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 on a Solid server", |
|
||||||
}, |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
{ |
|
||||||
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: "http://www.w3.org/ns/lddps#Resource", |
|
||||||
min: 0, |
|
||||||
max: -1, |
|
||||||
annotations: [ |
|
||||||
{ |
|
||||||
type: "Annotation", |
|
||||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
|
||||||
object: { |
|
||||||
value: "Defines a Solid 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"], |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
id: "http://www.w3.org/ns/lddps#Resource", |
|
||||||
type: "ShapeDecl", |
|
||||||
shapeExpr: { |
|
||||||
type: "Shape", |
|
||||||
expression: { |
|
||||||
id: "http://www.w3.org/ns/lddps#ResourceShape", |
|
||||||
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#Resource", |
|
||||||
"http://www.w3.org/ns/iana/media-types/text/turtle#Resource", |
|
||||||
], |
|
||||||
}, |
|
||||||
min: 0, |
|
||||||
max: -1, |
|
||||||
annotations: [ |
|
||||||
{ |
|
||||||
type: "Annotation", |
|
||||||
predicate: "http://www.w3.org/2000/01/rdf-schema#comment", |
|
||||||
object: { |
|
||||||
value: "Any resource on a Solid server", |
|
||||||
}, |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
{ |
|
||||||
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/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"], |
|
||||||
}, |
|
||||||
}, |
|
||||||
], |
|
||||||
}; |
|
@ -1,28 +0,0 @@ |
|||||||
import type { ShapeType } from "@ldo/ldo"; |
|
||||||
import { solidSchema } from "./solid.schema"; |
|
||||||
import { solidContext } from "./solid.context"; |
|
||||||
import type { Container, Resource } from "./solid.typings"; |
|
||||||
|
|
||||||
/** |
|
||||||
* ============================================================================= |
|
||||||
* LDO ShapeTypes solid |
|
||||||
* ============================================================================= |
|
||||||
*/ |
|
||||||
|
|
||||||
/** |
|
||||||
* Container ShapeType |
|
||||||
*/ |
|
||||||
export const ContainerShapeType: ShapeType<Container> = { |
|
||||||
schema: solidSchema, |
|
||||||
shape: "http://www.w3.org/ns/lddps#Container", |
|
||||||
context: solidContext, |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Resource ShapeType |
|
||||||
*/ |
|
||||||
export const ResourceShapeType: ShapeType<Resource> = { |
|
||||||
schema: solidSchema, |
|
||||||
shape: "http://www.w3.org/ns/lddps#Resource", |
|
||||||
context: solidContext, |
|
||||||
}; |
|
@ -1,73 +0,0 @@ |
|||||||
import type { ContextDefinition } from "jsonld"; |
|
||||||
|
|
||||||
/** |
|
||||||
* ============================================================================= |
|
||||||
* Typescript Typings for solid |
|
||||||
* ============================================================================= |
|
||||||
*/ |
|
||||||
|
|
||||||
/** |
|
||||||
* Container Type |
|
||||||
*/ |
|
||||||
export interface Container { |
|
||||||
"@id"?: string; |
|
||||||
"@context"?: ContextDefinition; |
|
||||||
/** |
|
||||||
* A container on a Solid server |
|
||||||
*/ |
|
||||||
type?: ( |
|
||||||
| { |
|
||||||
"@id": "Container"; |
|
||||||
} |
|
||||||
| { |
|
||||||
"@id": "Resource"; |
|
||||||
} |
|
||||||
)[]; |
|
||||||
/** |
|
||||||
* Date modified |
|
||||||
*/ |
|
||||||
modified?: string; |
|
||||||
/** |
|
||||||
* Defines a Solid Resource |
|
||||||
*/ |
|
||||||
contains?: Resource[]; |
|
||||||
/** |
|
||||||
* ? |
|
||||||
*/ |
|
||||||
mtime?: number; |
|
||||||
/** |
|
||||||
* size of this container |
|
||||||
*/ |
|
||||||
size?: number; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Resource Type |
|
||||||
*/ |
|
||||||
export interface Resource { |
|
||||||
"@id"?: string; |
|
||||||
"@context"?: ContextDefinition; |
|
||||||
/** |
|
||||||
* Any resource on a Solid server |
|
||||||
*/ |
|
||||||
type?: ( |
|
||||||
| { |
|
||||||
"@id": "Resource"; |
|
||||||
} |
|
||||||
| { |
|
||||||
"@id": "Resource2"; |
|
||||||
} |
|
||||||
)[]; |
|
||||||
/** |
|
||||||
* Date modified |
|
||||||
*/ |
|
||||||
modified?: string; |
|
||||||
/** |
|
||||||
* ? |
|
||||||
*/ |
|
||||||
mtime?: number; |
|
||||||
/** |
|
||||||
* size of this container |
|
||||||
*/ |
|
||||||
size?: number; |
|
||||||
} |
|
@ -1,98 +0,0 @@ |
|||||||
import type { |
|
||||||
ArrayProxyTarget, |
|
||||||
SubjectProxyTarget, |
|
||||||
ProxyContextOptions, |
|
||||||
} from "@ldo/jsonld-dataset-proxy"; |
|
||||||
import { ProxyContext } from "@ldo/jsonld-dataset-proxy"; |
|
||||||
import type { UpdateManager } from "./UpdateManager"; |
|
||||||
import { namedNode } from "@rdfjs/data-model"; |
|
||||||
|
|
||||||
export class TrackingProxyContext extends ProxyContext { |
|
||||||
private updateManager: UpdateManager; |
|
||||||
private listener: () => void; |
|
||||||
|
|
||||||
constructor( |
|
||||||
options: ProxyContextOptions, |
|
||||||
updateManager: UpdateManager, |
|
||||||
listener: () => void, |
|
||||||
) { |
|
||||||
super(options); |
|
||||||
this.updateManager = updateManager; |
|
||||||
this.listener = listener; |
|
||||||
} |
|
||||||
|
|
||||||
protected createSubjectHandler(): ProxyHandler<SubjectProxyTarget> { |
|
||||||
const baseHandler = super.createSubjectHandler(); |
|
||||||
const oldGetFunction = baseHandler.get; |
|
||||||
const newGetFunction: ProxyHandler<SubjectProxyTarget>["get"] = ( |
|
||||||
target: SubjectProxyTarget, |
|
||||||
key: string | symbol, |
|
||||||
receiver, |
|
||||||
) => { |
|
||||||
const subject = target["@id"]; |
|
||||||
if (typeof key === "symbol") { |
|
||||||
// Do Nothing
|
|
||||||
} else if (key === "@id") { |
|
||||||
this.updateManager.registerListener( |
|
||||||
[subject, null, null, null], |
|
||||||
this.listener, |
|
||||||
); |
|
||||||
} else if (!this.contextUtil.isArray(key)) { |
|
||||||
const predicate = namedNode(this.contextUtil.keyToIri(key)); |
|
||||||
this.updateManager.registerListener( |
|
||||||
[subject, predicate, null, null], |
|
||||||
this.listener, |
|
||||||
); |
|
||||||
} |
|
||||||
return oldGetFunction && oldGetFunction(target, key, receiver); |
|
||||||
}; |
|
||||||
baseHandler.get = newGetFunction; |
|
||||||
baseHandler.set = () => { |
|
||||||
console.warn( |
|
||||||
"You've attempted to set a value on a Linked Data Object from the useSubject, useMatchingSubject, or useMatchingObject hooks. These linked data objects should only be used to render data, not modify it. To modify data, use the `changeData` function.", |
|
||||||
); |
|
||||||
return true; |
|
||||||
}; |
|
||||||
return baseHandler; |
|
||||||
} |
|
||||||
|
|
||||||
protected createArrayHandler(): ProxyHandler<ArrayProxyTarget> { |
|
||||||
const baseHandler = super.createArrayHandler(); |
|
||||||
const oldGetFunction = baseHandler.get; |
|
||||||
const newGetFunction: ProxyHandler<ArrayProxyTarget>["get"] = ( |
|
||||||
target: ArrayProxyTarget, |
|
||||||
key: string | symbol, |
|
||||||
receiver, |
|
||||||
) => { |
|
||||||
if (qualifiedArrayMethods.has(key)) { |
|
||||||
this.updateManager.registerListener( |
|
||||||
[target[0][0], target[0][1], target[0][2], null], |
|
||||||
this.listener, |
|
||||||
); |
|
||||||
} |
|
||||||
return oldGetFunction && oldGetFunction(target, key, receiver); |
|
||||||
}; |
|
||||||
baseHandler.get = newGetFunction; |
|
||||||
return baseHandler; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
const qualifiedArrayMethods = new Set([ |
|
||||||
"forEach", |
|
||||||
"map", |
|
||||||
"reduce", |
|
||||||
Symbol.iterator, |
|
||||||
"entries", |
|
||||||
"every", |
|
||||||
"filter", |
|
||||||
"find", |
|
||||||
"findIndex", |
|
||||||
"findLast", |
|
||||||
"findLastIndex", |
|
||||||
"includes, indexOf", |
|
||||||
"keys", |
|
||||||
"lastIndexOf", |
|
||||||
"reduceRight", |
|
||||||
"some", |
|
||||||
"values", |
|
||||||
]); |
|
@ -1,76 +0,0 @@ |
|||||||
import type { |
|
||||||
DatasetChanges, |
|
||||||
QuadMatch, |
|
||||||
SubjectNode, |
|
||||||
PredicateNode, |
|
||||||
ObjectNode, |
|
||||||
} from "@ldo/rdf-utils"; |
|
||||||
import { createDataset } from "@ldo/dataset"; |
|
||||||
import { quadMatchToString } from "@ldo/rdf-utils"; |
|
||||||
import type { Quad } from "@rdfjs/types"; |
|
||||||
|
|
||||||
export class UpdateManager { |
|
||||||
private quadMatchListenerMap: Record<string, Set<() => void>> = {}; |
|
||||||
private listenerHashMap: Map<() => void, Set<string>> = new Map(); |
|
||||||
|
|
||||||
registerListener(quadMatch: QuadMatch, callback: () => void): void { |
|
||||||
const hash = quadMatchToString(quadMatch); |
|
||||||
if (!this.quadMatchListenerMap[hash]) { |
|
||||||
this.quadMatchListenerMap[hash] = new Set(); |
|
||||||
} |
|
||||||
if (!this.listenerHashMap.has(callback)) { |
|
||||||
this.listenerHashMap.set(callback, new Set()); |
|
||||||
} |
|
||||||
this.quadMatchListenerMap[hash].add(callback); |
|
||||||
this.listenerHashMap.get(callback)?.add(hash); |
|
||||||
} |
|
||||||
|
|
||||||
removeListener(callback: () => void) { |
|
||||||
const hashSet = this.listenerHashMap.get(callback); |
|
||||||
if (hashSet) { |
|
||||||
hashSet.forEach((hash) => { |
|
||||||
this.quadMatchListenerMap[hash]?.delete(callback); |
|
||||||
}); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
notifyListenersOfChanges(changes: DatasetChanges<Quad>): void { |
|
||||||
const listenersToNotify = new Set<() => void>(); |
|
||||||
|
|
||||||
const allQuads = createDataset(); |
|
||||||
allQuads.addAll(changes.added || []); |
|
||||||
allQuads.addAll(changes.removed || []); |
|
||||||
|
|
||||||
// Iterate through all quads looking for any dataset match they effect
|
|
||||||
allQuads.forEach((tempQuad) => { |
|
||||||
// Cast the input because RDFJS types assume RDF 1.2 where a Subject can
|
|
||||||
// be a Quad
|
|
||||||
const quad = tempQuad as { |
|
||||||
subject: SubjectNode; |
|
||||||
predicate: PredicateNode; |
|
||||||
object: ObjectNode; |
|
||||||
}; |
|
||||||
const quadMatches: QuadMatch[] = [ |
|
||||||
[null, null, null, null], |
|
||||||
[quad.subject, null, null, null], |
|
||||||
[quad.subject, quad.predicate, null, null], |
|
||||||
[quad.subject, null, quad.object, null], |
|
||||||
[null, quad.predicate, null, null], |
|
||||||
[null, quad.predicate, quad.object, null], |
|
||||||
[null, null, quad.object, null], |
|
||||||
[quad.subject, quad.predicate, quad.object, null], |
|
||||||
]; |
|
||||||
|
|
||||||
quadMatches.forEach((quadMatch) => { |
|
||||||
const hash = quadMatchToString(quadMatch); |
|
||||||
this.quadMatchListenerMap[hash]?.forEach((callback) => { |
|
||||||
listenersToNotify.add(callback); |
|
||||||
}); |
|
||||||
delete this.quadMatchListenerMap[hash]; |
|
||||||
}); |
|
||||||
}); |
|
||||||
listenersToNotify.forEach((listener) => { |
|
||||||
listener(); |
|
||||||
}); |
|
||||||
} |
|
||||||
} |
|
@ -1,59 +0,0 @@ |
|||||||
import { defaultGraph } from "@rdfjs/data-model"; |
|
||||||
import type { SubjectNode } from "@ldo/rdf-utils"; |
|
||||||
import { |
|
||||||
ContextUtil, |
|
||||||
JsonldDatasetProxyBuilder, |
|
||||||
} from "@ldo/jsonld-dataset-proxy"; |
|
||||||
import type { ShapeType, LdoBase } from "@ldo/ldo"; |
|
||||||
import { LdoBuilder } from "@ldo/ldo"; |
|
||||||
import { useLdoContext } from "../LdoContext"; |
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react"; |
|
||||||
import { TrackingProxyContext } from "./helpers/TrackingProxyContext"; |
|
||||||
|
|
||||||
export function useSubject<Type extends LdoBase>( |
|
||||||
shapeType: ShapeType<Type>, |
|
||||||
subject: string | SubjectNode, |
|
||||||
): [Type, undefined] | [undefined, Error] { |
|
||||||
const { dataset, updateManager } = useLdoContext(); |
|
||||||
|
|
||||||
const [forceUpdateCounter, setForceUpdateCounter] = useState(0); |
|
||||||
const forceUpdate = useCallback( |
|
||||||
() => setForceUpdateCounter((val) => val + 1), |
|
||||||
[setForceUpdateCounter], |
|
||||||
); |
|
||||||
|
|
||||||
// The main linked data object
|
|
||||||
const linkedDataObject = useMemo(() => { |
|
||||||
// Rebuild the LdoBuilder from scratch to inject TrackingProxyContext
|
|
||||||
const contextUtil = new ContextUtil(shapeType.context); |
|
||||||
const proxyContext = new TrackingProxyContext( |
|
||||||
{ |
|
||||||
dataset, |
|
||||||
contextUtil, |
|
||||||
writeGraphs: [defaultGraph()], |
|
||||||
languageOrdering: ["none", "en", "other"], |
|
||||||
}, |
|
||||||
updateManager, |
|
||||||
forceUpdate, |
|
||||||
); |
|
||||||
const builder = new LdoBuilder( |
|
||||||
new JsonldDatasetProxyBuilder(proxyContext), |
|
||||||
shapeType, |
|
||||||
); |
|
||||||
return builder.fromSubject(subject); |
|
||||||
}, [ |
|
||||||
shapeType, |
|
||||||
subject, |
|
||||||
dataset, |
|
||||||
updateManager, |
|
||||||
forceUpdateCounter, |
|
||||||
forceUpdate, |
|
||||||
]); |
|
||||||
|
|
||||||
useEffect(() => { |
|
||||||
// Unregister force update listener upon unmount
|
|
||||||
return () => updateManager.removeListener(forceUpdate); |
|
||||||
}, [shapeType, subject]); |
|
||||||
|
|
||||||
return [linkedDataObject, undefined]; |
|
||||||
} |
|
@ -1,36 +0,0 @@ |
|||||||
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#> |
|
||||||
|
|
||||||
ldps:Container EXTRA a { |
|
||||||
$ldps:ContainerShape ( |
|
||||||
a [ ldp:Container ldp:Resource ]* |
|
||||||
// rdfs:comment "A container on a Solid server"; |
|
||||||
dct:modified xsd:string? |
|
||||||
// rdfs:comment "Date modified"; |
|
||||||
ldp:contains @ldps:Resource* |
|
||||||
// rdfs:comment "Defines a Solid Resource"; |
|
||||||
stat:mtime xsd:decimal? |
|
||||||
// rdfs:comment "?"; |
|
||||||
stat:size xsd:integer? |
|
||||||
// rdfs:comment "size of this container"; |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
ldps:Resource EXTRA a { |
|
||||||
$ldps:ResourceShape ( |
|
||||||
a [ ldp:Resource tur:Resource ]* |
|
||||||
// rdfs:comment "Any resource on a Solid server"; |
|
||||||
dct:modified xsd:string? |
|
||||||
// rdfs:comment "Date modified"; |
|
||||||
stat:mtime xsd:decimal? |
|
||||||
// rdfs:comment "?"; |
|
||||||
stat:size xsd:integer? |
|
||||||
// rdfs:comment "size of this container"; |
|
||||||
) |
|
||||||
} |
|
@ -1,110 +0,0 @@ |
|||||||
import { useCallback, useMemo } from "react"; |
|
||||||
import { useLdoContext } from "./LdoContext"; |
|
||||||
import type { LdoDataset, ShapeType, LdoBase } from "@ldo/ldo"; |
|
||||||
import { startTransaction, transactionChanges, write } from "@ldo/ldo"; |
|
||||||
import { splitChangesByGraph } from "./util/splitChangesByGraph"; |
|
||||||
import type { Resource } from "./document/resource/Resource"; |
|
||||||
import type { DataResource } from "./document/resource/dataResource/DataResource"; |
|
||||||
import type { BinaryResource } from "./document/resource/binaryResource/BinaryResource"; |
|
||||||
import type { ContainerResource } from "./document/resource/dataResource/containerResource/ContainerResource"; |
|
||||||
import type { AccessRules } from "./document/accessRules/AccessRules"; |
|
||||||
import type { DatasetChanges, SubjectNode } from "@ldo/rdf-utils"; |
|
||||||
import type { Quad } from "@rdfjs/types"; |
|
||||||
|
|
||||||
export interface UseLdoReturn { |
|
||||||
changeData<Type extends LdoBase>(input: Type, ...resources: Resource[]): Type; |
|
||||||
commitData(input: LdoBase): Promise<void>; |
|
||||||
createData<Type extends LdoBase>( |
|
||||||
shapeType: ShapeType<Type>, |
|
||||||
subject: string | SubjectNode, |
|
||||||
...resources: Resource[] |
|
||||||
): Type; |
|
||||||
dataset: LdoDataset; |
|
||||||
getDataResource: (uri: string) => DataResource; |
|
||||||
getBinaryResource: (uri: string) => BinaryResource; |
|
||||||
getContainerResource: (uri: string) => ContainerResource; |
|
||||||
getAccessRules: (resource: Resource) => AccessRules; |
|
||||||
} |
|
||||||
|
|
||||||
export function useLdo(): UseLdoReturn { |
|
||||||
const { |
|
||||||
dataResourceStore, |
|
||||||
containerResourceStore, |
|
||||||
binaryResourceStore, |
|
||||||
accessRulesStore, |
|
||||||
dataset, |
|
||||||
} = useLdoContext(); |
|
||||||
/** |
|
||||||
* Begins tracking changes to eventually commit |
|
||||||
*/ |
|
||||||
const changeData = useCallback( |
|
||||||
<Type extends LdoBase>(input: Type, ...resources: Resource[]) => { |
|
||||||
// Clone the input and set a graph
|
|
||||||
const [transactionLdo] = write(...resources.map((r) => r.uri)).usingCopy( |
|
||||||
input, |
|
||||||
); |
|
||||||
// Start a transaction with the input
|
|
||||||
startTransaction(transactionLdo); |
|
||||||
// Return
|
|
||||||
return transactionLdo; |
|
||||||
}, |
|
||||||
[dataset], |
|
||||||
); |
|
||||||
/** |
|
||||||
* Begins tracking changes to eventually commit for a new subject |
|
||||||
*/ |
|
||||||
const createData = useCallback( |
|
||||||
<Type extends LdoBase>( |
|
||||||
shapeType: ShapeType<Type>, |
|
||||||
subject: string | SubjectType, |
|
||||||
...resources: Resource[] |
|
||||||
) => { |
|
||||||
const linkedDataObject = dataset |
|
||||||
.usingType(shapeType) |
|
||||||
.write(...resources.map((r) => r.uri)) |
|
||||||
.fromSubject(subject); |
|
||||||
startTransaction(linkedDataObject); |
|
||||||
return linkedDataObject; |
|
||||||
}, |
|
||||||
[], |
|
||||||
); |
|
||||||
/** |
|
||||||
* Commits the transaction to the global dataset, syncing all subscribing |
|
||||||
* components and Solid Pods |
|
||||||
*/ |
|
||||||
const commitData = useCallback( |
|
||||||
async (input: LdoBase) => { |
|
||||||
const changes = transactionChanges(input); |
|
||||||
const changesByGraph = splitChangesByGraph( |
|
||||||
changes as DatasetChanges<Quad>, |
|
||||||
); |
|
||||||
// Make queries
|
|
||||||
await Promise.all( |
|
||||||
Array.from(changesByGraph.entries()).map( |
|
||||||
async ([graph, datasetChanges]) => { |
|
||||||
if (graph.termType === "DefaultGraph") { |
|
||||||
return; |
|
||||||
} |
|
||||||
const resource = dataResourceStore.get(graph.value); |
|
||||||
await resource.update(datasetChanges); |
|
||||||
}, |
|
||||||
), |
|
||||||
); |
|
||||||
}, |
|
||||||
[dataset, fetch], |
|
||||||
); |
|
||||||
// Returns the values
|
|
||||||
return useMemo( |
|
||||||
() => ({ |
|
||||||
dataset, |
|
||||||
changeData, |
|
||||||
createData, |
|
||||||
commitData, |
|
||||||
getDataResource: (uri) => dataResourceStore.get(uri), |
|
||||||
getBinaryResource: (uri) => binaryResourceStore.get(uri), |
|
||||||
getContainerResource: (uri) => containerResourceStore.get(uri), |
|
||||||
getAccessRules: (resource) => accessRulesStore.get(resource), |
|
||||||
}), |
|
||||||
[dataset, changeData, commitData], |
|
||||||
); |
|
||||||
} |
|
Loading…
Reference in new issue