parent
39388bfe83
commit
108b1d677b
@ -1,55 +0,0 @@ |
|||||||
import React, { createContext, useContext } from "react"; |
|
||||||
import { |
|
||||||
useMemo, |
|
||||||
type FunctionComponent, |
|
||||||
type PropsWithChildren, |
|
||||||
useEffect, |
|
||||||
} from "react"; |
|
||||||
import { useSolidAuth } from "./SolidAuthContext"; |
|
||||||
import type { SolidLdoDataset } from "@ldo/solid"; |
|
||||||
import { createSolidLdoDataset } from "@ldo/solid"; |
|
||||||
import type { UseLdoMethods } from "./useLdoMethods"; |
|
||||||
import { createUseLdoMethods } from "./useLdoMethods"; |
|
||||||
|
|
||||||
export const SolidLdoReactContext = |
|
||||||
// This will be set in the provider
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
createContext<UseLdoMethods>(undefined); |
|
||||||
|
|
||||||
export function useLdo(): UseLdoMethods { |
|
||||||
return useContext(SolidLdoReactContext); |
|
||||||
} |
|
||||||
|
|
||||||
export interface SolidLdoProviderProps extends PropsWithChildren {} |
|
||||||
|
|
||||||
export const SolidLdoProvider: FunctionComponent<SolidLdoProviderProps> = ({ |
|
||||||
children, |
|
||||||
}) => { |
|
||||||
const { fetch } = useSolidAuth(); |
|
||||||
|
|
||||||
// Initialize storeDependencies before render
|
|
||||||
const solidLdoDataset: SolidLdoDataset = useMemo(() => { |
|
||||||
const ldoDataset = createSolidLdoDataset({ |
|
||||||
fetch, |
|
||||||
}); |
|
||||||
ldoDataset.setMaxListeners(1000); |
|
||||||
return ldoDataset; |
|
||||||
}, []); |
|
||||||
|
|
||||||
// Keep context in sync with props
|
|
||||||
useEffect(() => { |
|
||||||
solidLdoDataset.context.fetch = fetch; |
|
||||||
}, [fetch]); |
|
||||||
|
|
||||||
const value: UseLdoMethods = useMemo( |
|
||||||
() => createUseLdoMethods(solidLdoDataset), |
|
||||||
[solidLdoDataset], |
|
||||||
); |
|
||||||
|
|
||||||
return ( |
|
||||||
<SolidLdoReactContext.Provider value={value}> |
|
||||||
{children} |
|
||||||
</SolidLdoReactContext.Provider> |
|
||||||
); |
|
||||||
}; |
|
@ -0,0 +1,15 @@ |
|||||||
|
import { solidConnectedPlugin } from "@ldo/connected-solid"; |
||||||
|
import { createLdoReactMethods } from "@ldo/react"; |
||||||
|
import { createBrowserSolidLdoProvider } from "./BrowserSolidLdoProvider"; |
||||||
|
|
||||||
|
export const { |
||||||
|
dataset, |
||||||
|
useLdo, |
||||||
|
useMatchObject, |
||||||
|
useMatchSubject, |
||||||
|
useResource, |
||||||
|
useSubject, |
||||||
|
useSubscribeToResource, |
||||||
|
} = createLdoReactMethods([solidConnectedPlugin]); |
||||||
|
|
||||||
|
export const BrowserSolidLdoProvider = createBrowserSolidLdoProvider(dataset); |
@ -1,12 +1,7 @@ |
|||||||
export * from "./BrowserSolidLdoProvider"; |
export * from "./BrowserSolidLdoProvider"; |
||||||
export * from "./UnauthenticatedSolidLdoProvider"; |
export * from "./UnauthenticatedSolidLdoProvider"; |
||||||
export * from "./SolidAuthContext"; |
export * from "./SolidAuthContext"; |
||||||
|
export * from "./defaultIntance"; |
||||||
export { useLdo } from "./SolidLdoProvider"; |
|
||||||
|
|
||||||
// hooks
|
// hooks
|
||||||
export * from "./useResource"; |
|
||||||
export * from "./useSubject"; |
|
||||||
export * from "./useMatchSubject"; |
|
||||||
export * from "./useMatchObject"; |
|
||||||
export * from "./useRootContainer"; |
export * from "./useRootContainer"; |
||||||
|
@ -1,84 +0,0 @@ |
|||||||
import type { LdoBase, ShapeType } from "@ldo/ldo"; |
|
||||||
import type { SubjectNode } from "@ldo/rdf-utils"; |
|
||||||
import type { |
|
||||||
Resource, |
|
||||||
SolidLdoDataset, |
|
||||||
SolidLdoTransactionDataset, |
|
||||||
} from "@ldo/solid"; |
|
||||||
import { changeData, commitData } from "@ldo/solid"; |
|
||||||
|
|
||||||
export interface UseLdoMethods { |
|
||||||
dataset: SolidLdoDataset; |
|
||||||
getResource: SolidLdoDataset["getResource"]; |
|
||||||
getSubject<Type extends LdoBase>( |
|
||||||
shapeType: ShapeType<Type>, |
|
||||||
subject: string | SubjectNode, |
|
||||||
): Type; |
|
||||||
createData<Type extends LdoBase>( |
|
||||||
shapeType: ShapeType<Type>, |
|
||||||
subject: string | SubjectNode, |
|
||||||
resource: Resource, |
|
||||||
...additionalResources: Resource[] |
|
||||||
): Type; |
|
||||||
changeData<Type extends LdoBase>( |
|
||||||
input: Type, |
|
||||||
resource: Resource, |
|
||||||
...additionalResources: Resource[] |
|
||||||
): Type; |
|
||||||
commitData( |
|
||||||
input: LdoBase, |
|
||||||
): ReturnType<SolidLdoTransactionDataset["commitToPod"]>; |
|
||||||
} |
|
||||||
|
|
||||||
export function createUseLdoMethods(dataset: SolidLdoDataset): UseLdoMethods { |
|
||||||
return { |
|
||||||
dataset: dataset, |
|
||||||
/** |
|
||||||
* Gets a resource |
|
||||||
*/ |
|
||||||
getResource: dataset.getResource.bind(dataset), |
|
||||||
/** |
|
||||||
* Returns a Linked Data Object for a subject |
|
||||||
* @param shapeType The shape type for the data |
|
||||||
* @param subject Subject Node |
|
||||||
* @returns A Linked Data Object |
|
||||||
*/ |
|
||||||
getSubject<Type extends LdoBase>( |
|
||||||
shapeType: ShapeType<Type>, |
|
||||||
subject: string | SubjectNode, |
|
||||||
): Type { |
|
||||||
return dataset.usingType(shapeType).fromSubject(subject); |
|
||||||
}, |
|
||||||
/** |
|
||||||
* Begins tracking changes to eventually commit for a new subject |
|
||||||
* @param shapeType The shape type that defines the created data |
|
||||||
* @param subject The RDF subject for a Linked Data Object |
|
||||||
* @param resources Any number of resources to which this data should be written |
|
||||||
* @returns A Linked Data Object to modify and commit |
|
||||||
*/ |
|
||||||
createData<Type extends LdoBase>( |
|
||||||
shapeType: ShapeType<Type>, |
|
||||||
subject: string | SubjectNode, |
|
||||||
resource: Resource, |
|
||||||
...additionalResources: Resource[] |
|
||||||
): Type { |
|
||||||
return dataset.createData( |
|
||||||
shapeType, |
|
||||||
subject, |
|
||||||
resource, |
|
||||||
...additionalResources, |
|
||||||
); |
|
||||||
}, |
|
||||||
/** |
|
||||||
* Begins tracking changes to eventually commit |
|
||||||
* @param input A linked data object to track changes on |
|
||||||
* @param resources |
|
||||||
*/ |
|
||||||
changeData: changeData, |
|
||||||
/** |
|
||||||
* Commits the transaction to the global dataset, syncing all subscribing |
|
||||||
* components and Solid Pods |
|
||||||
*/ |
|
||||||
commitData: commitData, |
|
||||||
}; |
|
||||||
} |
|
@ -1,21 +0,0 @@ |
|||||||
import type { LdoBase, LdSet, ShapeType } from "@ldo/ldo"; |
|
||||||
import type { QuadMatch } from "@ldo/rdf-utils"; |
|
||||||
import type { LdoBuilder } from "@ldo/ldo"; |
|
||||||
import { useCallback } from "react"; |
|
||||||
import { useTrackingProxy } from "./util/useTrackingProxy"; |
|
||||||
|
|
||||||
export function useMatchObject<Type extends LdoBase>( |
|
||||||
shapeType: ShapeType<Type>, |
|
||||||
subject?: QuadMatch[0] | string, |
|
||||||
predicate?: QuadMatch[1] | string, |
|
||||||
graph?: QuadMatch[3] | string, |
|
||||||
): LdSet<Type> { |
|
||||||
const matchObject = useCallback( |
|
||||||
(builder: LdoBuilder<Type>) => { |
|
||||||
return builder.matchObject(subject, predicate, graph); |
|
||||||
}, |
|
||||||
[subject, predicate, graph], |
|
||||||
); |
|
||||||
|
|
||||||
return useTrackingProxy(shapeType, matchObject); |
|
||||||
} |
|
@ -1,21 +0,0 @@ |
|||||||
import type { LdoBase, LdSet, ShapeType } from "@ldo/ldo"; |
|
||||||
import type { QuadMatch } from "@ldo/rdf-utils"; |
|
||||||
import type { LdoBuilder } from "@ldo/ldo"; |
|
||||||
import { useCallback } from "react"; |
|
||||||
import { useTrackingProxy } from "./util/useTrackingProxy"; |
|
||||||
|
|
||||||
export function useMatchSubject<Type extends LdoBase>( |
|
||||||
shapeType: ShapeType<Type>, |
|
||||||
predicate?: QuadMatch[1] | string, |
|
||||||
object?: QuadMatch[2] | string, |
|
||||||
graph?: QuadMatch[3] | string, |
|
||||||
): LdSet<Type> { |
|
||||||
const matchSubject = useCallback( |
|
||||||
(builder: LdoBuilder<Type>) => { |
|
||||||
return builder.matchSubject(predicate, object, graph); |
|
||||||
}, |
|
||||||
[predicate, object, graph], |
|
||||||
); |
|
||||||
|
|
||||||
return useTrackingProxy(shapeType, matchSubject); |
|
||||||
} |
|
@ -1,114 +0,0 @@ |
|||||||
import { useMemo, useEffect, useRef, useState, useCallback } from "react"; |
|
||||||
import type { |
|
||||||
Container, |
|
||||||
ContainerUri, |
|
||||||
LeafUri, |
|
||||||
Resource, |
|
||||||
Leaf, |
|
||||||
} from "@ldo/solid"; |
|
||||||
import { useLdo } from "./SolidLdoProvider"; |
|
||||||
|
|
||||||
export interface UseResourceOptions { |
|
||||||
suppressInitialRead?: boolean; |
|
||||||
reloadOnMount?: boolean; |
|
||||||
subscribe?: boolean; |
|
||||||
} |
|
||||||
|
|
||||||
export function useResource( |
|
||||||
uri: ContainerUri, |
|
||||||
options?: UseResourceOptions, |
|
||||||
): Container; |
|
||||||
export function useResource(uri: LeafUri, options?: UseResourceOptions): Leaf; |
|
||||||
export function useResource( |
|
||||||
uri: string, |
|
||||||
options?: UseResourceOptions, |
|
||||||
): Leaf | Container; |
|
||||||
export function useResource( |
|
||||||
uri?: ContainerUri, |
|
||||||
options?: UseResourceOptions, |
|
||||||
): Container | undefined; |
|
||||||
export function useResource( |
|
||||||
uri?: LeafUri, |
|
||||||
options?: UseResourceOptions, |
|
||||||
): Leaf | undefined; |
|
||||||
export function useResource( |
|
||||||
uri?: string, |
|
||||||
options?: UseResourceOptions, |
|
||||||
): Leaf | Container | undefined; |
|
||||||
export function useResource( |
|
||||||
uri?: string, |
|
||||||
options?: UseResourceOptions, |
|
||||||
): Leaf | Container | undefined { |
|
||||||
const { getResource } = useLdo(); |
|
||||||
const subscriptionIdRef = useRef<string | undefined>(); |
|
||||||
|
|
||||||
// Get the resource
|
|
||||||
const resource = useMemo(() => { |
|
||||||
if (uri) { |
|
||||||
const resource = getResource(uri); |
|
||||||
// Run read operations if necissary
|
|
||||||
if (!options?.suppressInitialRead) { |
|
||||||
if (options?.reloadOnMount) { |
|
||||||
resource.read(); |
|
||||||
} else { |
|
||||||
resource.readIfUnfetched(); |
|
||||||
} |
|
||||||
} |
|
||||||
return resource; |
|
||||||
} |
|
||||||
return undefined; |
|
||||||
}, [getResource, uri]); |
|
||||||
const [resourceRepresentation, setResourceRepresentation] = |
|
||||||
useState(resource); |
|
||||||
const pastResource = useRef< |
|
||||||
{ resource?: Resource; callback: () => void } | undefined |
|
||||||
>(); |
|
||||||
|
|
||||||
useEffect(() => { |
|
||||||
if (options?.subscribe) { |
|
||||||
resource |
|
||||||
?.subscribeToNotifications() |
|
||||||
.then((subscriptionId) => (subscriptionIdRef.current = subscriptionId)); |
|
||||||
} else if (subscriptionIdRef.current) { |
|
||||||
resource?.unsubscribeFromNotifications(subscriptionIdRef.current); |
|
||||||
} |
|
||||||
return () => { |
|
||||||
if (subscriptionIdRef.current) |
|
||||||
resource?.unsubscribeFromNotifications(subscriptionIdRef.current); |
|
||||||
}; |
|
||||||
}, [resource, options?.subscribe]); |
|
||||||
|
|
||||||
// Callback function to force the react dom to reload.
|
|
||||||
const forceReload = useCallback( |
|
||||||
// Wrap the resource in a proxy so it's techically a different object
|
|
||||||
() => { |
|
||||||
if (resource) setResourceRepresentation(new Proxy(resource, {})); |
|
||||||
}, |
|
||||||
[resource], |
|
||||||
); |
|
||||||
|
|
||||||
useEffect(() => { |
|
||||||
// Remove listeners for the previous resource
|
|
||||||
if (pastResource.current?.resource) { |
|
||||||
pastResource.current.resource.off( |
|
||||||
"update", |
|
||||||
pastResource.current.callback, |
|
||||||
); |
|
||||||
} |
|
||||||
// Set a new past resource to the current resource
|
|
||||||
pastResource.current = { resource, callback: forceReload }; |
|
||||||
if (resource) { |
|
||||||
// Add listener
|
|
||||||
resource.on("update", forceReload); |
|
||||||
setResourceRepresentation(new Proxy(resource, {})); |
|
||||||
|
|
||||||
// Unsubscribe on unmount
|
|
||||||
return () => { |
|
||||||
resource.off("update", forceReload); |
|
||||||
}; |
|
||||||
} else { |
|
||||||
setResourceRepresentation(undefined); |
|
||||||
} |
|
||||||
}, [resource]); |
|
||||||
return resourceRepresentation; |
|
||||||
} |
|
@ -1,30 +0,0 @@ |
|||||||
import type { SubjectNode } from "@ldo/rdf-utils"; |
|
||||||
import type { ShapeType } from "@ldo/ldo"; |
|
||||||
import type { LdoBuilder } from "@ldo/ldo"; |
|
||||||
import type { LdoBase } from "@ldo/ldo"; |
|
||||||
import { useCallback } from "react"; |
|
||||||
|
|
||||||
import { useTrackingProxy } from "./util/useTrackingProxy"; |
|
||||||
|
|
||||||
export function useSubject<Type extends LdoBase>( |
|
||||||
shapeType: ShapeType<Type>, |
|
||||||
subject: string | SubjectNode, |
|
||||||
): Type; |
|
||||||
export function useSubject<Type extends LdoBase>( |
|
||||||
shapeType: ShapeType<Type>, |
|
||||||
subject?: string | SubjectNode, |
|
||||||
): Type | undefined; |
|
||||||
export function useSubject<Type extends LdoBase>( |
|
||||||
shapeType: ShapeType<Type>, |
|
||||||
subject?: string | SubjectNode, |
|
||||||
): Type | undefined { |
|
||||||
const fromSubject = useCallback( |
|
||||||
(builder: LdoBuilder<Type>) => { |
|
||||||
if (!subject) return; |
|
||||||
return builder.fromSubject(subject); |
|
||||||
}, |
|
||||||
[subject], |
|
||||||
); |
|
||||||
|
|
||||||
return useTrackingProxy(shapeType, fromSubject); |
|
||||||
} |
|
@ -1,52 +0,0 @@ |
|||||||
import { useLdo } from "./SolidLdoProvider"; |
|
||||||
import { useEffect, useRef } from "react"; |
|
||||||
|
|
||||||
export function useSubscribeToResource(...uris: string[]): void { |
|
||||||
const { dataset } = useLdo(); |
|
||||||
const currentlySubscribed = useRef<Record<string, string>>({}); |
|
||||||
useEffect(() => { |
|
||||||
const resources = uris.map((uri) => dataset.getResource(uri)); |
|
||||||
const previousSubscriptions = { ...currentlySubscribed.current }; |
|
||||||
Promise.all<void>( |
|
||||||
resources.map(async (resource) => { |
|
||||||
if (!previousSubscriptions[resource.uri]) { |
|
||||||
// Prevent multiple triggers from created subscriptions while waiting
|
|
||||||
// for connection
|
|
||||||
currentlySubscribed.current[resource.uri] = "AWAITING"; |
|
||||||
// Read and subscribe
|
|
||||||
await resource.readIfUnfetched(); |
|
||||||
currentlySubscribed.current[resource.uri] = |
|
||||||
await resource.subscribeToNotifications(); |
|
||||||
} else { |
|
||||||
delete previousSubscriptions[resource.uri]; |
|
||||||
} |
|
||||||
}), |
|
||||||
).then(async () => { |
|
||||||
// Unsubscribe from all remaining previous subscriptions
|
|
||||||
await Promise.all( |
|
||||||
Object.entries(previousSubscriptions).map( |
|
||||||
async ([resourceUri, subscriptionId]) => { |
|
||||||
// Unsubscribe
|
|
||||||
delete currentlySubscribed.current[resourceUri]; |
|
||||||
const resource = dataset.getResource(resourceUri); |
|
||||||
await resource.unsubscribeFromNotifications(subscriptionId); |
|
||||||
}, |
|
||||||
), |
|
||||||
); |
|
||||||
}); |
|
||||||
}, [uris]); |
|
||||||
|
|
||||||
// Cleanup Subscriptions
|
|
||||||
useEffect(() => { |
|
||||||
return () => { |
|
||||||
Promise.all( |
|
||||||
Object.entries(currentlySubscribed.current).map( |
|
||||||
async ([resourceUri, subscriptionId]) => { |
|
||||||
const resource = dataset.getResource(resourceUri); |
|
||||||
await resource.unsubscribeFromNotifications(subscriptionId); |
|
||||||
}, |
|
||||||
), |
|
||||||
); |
|
||||||
}; |
|
||||||
}, []); |
|
||||||
} |
|
@ -1,61 +0,0 @@ |
|||||||
import type { |
|
||||||
ProxyContextOptions, |
|
||||||
SubjectProxy, |
|
||||||
SetProxy, |
|
||||||
} from "@ldo/jsonld-dataset-proxy"; |
|
||||||
import { ProxyContext } from "@ldo/jsonld-dataset-proxy"; |
|
||||||
import type { QuadMatch } from "@ldo/rdf-utils"; |
|
||||||
import type { SubscribableDataset } from "@ldo/subscribable-dataset"; |
|
||||||
import type { BlankNode, NamedNode, Quad } from "@rdfjs/types"; |
|
||||||
import { createTrackingSubjectProxy } from "./TrackingSubjectProxy"; |
|
||||||
import { createTrackingSetProxy } from "./TrackingSetProxy"; |
|
||||||
|
|
||||||
/** |
|
||||||
* @internal |
|
||||||
* Options to be passed to the tracking proxy |
|
||||||
*/ |
|
||||||
export interface TrackingProxyContextOptions extends ProxyContextOptions { |
|
||||||
dataset: SubscribableDataset<Quad>; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* @internal |
|
||||||
* This proxy exists to ensure react components rerender at the right time. It |
|
||||||
* keeps track of every key accessed in a Linked Data Object and only when the |
|
||||||
* dataset is updated with that key does it rerender the react component. |
|
||||||
*/ |
|
||||||
export class TrackingProxyContext extends ProxyContext { |
|
||||||
private listener: () => void; |
|
||||||
private subscribableDataset: SubscribableDataset<Quad>; |
|
||||||
|
|
||||||
constructor(options: TrackingProxyContextOptions, listener: () => void) { |
|
||||||
super(options); |
|
||||||
this.subscribableDataset = options.dataset; |
|
||||||
this.listener = listener; |
|
||||||
} |
|
||||||
|
|
||||||
// Adds the listener to the subscribable dataset while ensuring deduping of the listener
|
|
||||||
public addListener(eventName: QuadMatch) { |
|
||||||
const listeners = this.subscribableDataset.listeners(eventName); |
|
||||||
if (!listeners.includes(this.listener)) { |
|
||||||
this.subscribableDataset.on(eventName, this.listener); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
protected createNewSubjectProxy(node: NamedNode | BlankNode): SubjectProxy { |
|
||||||
return createTrackingSubjectProxy(this, node); |
|
||||||
} |
|
||||||
|
|
||||||
protected createNewSetProxy( |
|
||||||
quadMatch: QuadMatch, |
|
||||||
isSubjectOriented?: boolean, |
|
||||||
isLangStringSet?: boolean, |
|
||||||
): SetProxy { |
|
||||||
return createTrackingSetProxy( |
|
||||||
this, |
|
||||||
quadMatch, |
|
||||||
isSubjectOriented, |
|
||||||
isLangStringSet, |
|
||||||
); |
|
||||||
} |
|
||||||
} |
|
@ -1,56 +0,0 @@ |
|||||||
import { createNewSetProxy, type SetProxy } from "@ldo/jsonld-dataset-proxy"; |
|
||||||
import type { TrackingProxyContext } from "./TrackingProxyContext"; |
|
||||||
import type { QuadMatch } from "@ldo/rdf-utils"; |
|
||||||
|
|
||||||
export function createTrackingSetProxy( |
|
||||||
proxyContext: TrackingProxyContext, |
|
||||||
quadMatch: QuadMatch, |
|
||||||
isSubjectOriented?: boolean, |
|
||||||
isLangStringSet?: boolean, |
|
||||||
): SetProxy { |
|
||||||
const baseSetProxy = createNewSetProxy( |
|
||||||
quadMatch, |
|
||||||
isSubjectOriented ?? false, |
|
||||||
proxyContext, |
|
||||||
isLangStringSet, |
|
||||||
); |
|
||||||
|
|
||||||
return new Proxy(baseSetProxy, { |
|
||||||
get: (target: SetProxy, key: string | symbol, receiver) => { |
|
||||||
if (trackingMethods.has(key)) { |
|
||||||
proxyContext.addListener(quadMatch); |
|
||||||
} else if (disallowedMethods.has(key)) { |
|
||||||
console.warn( |
|
||||||
"You've attempted to modify 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 Reflect.get(target, key, receiver); |
|
||||||
}, |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
const trackingMethods = new Set([ |
|
||||||
"has", |
|
||||||
"size", |
|
||||||
"entries", |
|
||||||
"keys", |
|
||||||
"values", |
|
||||||
Symbol.iterator, |
|
||||||
"every", |
|
||||||
"every", |
|
||||||
"some", |
|
||||||
"forEach", |
|
||||||
"map", |
|
||||||
"reduce", |
|
||||||
"toArray", |
|
||||||
"toJSON", |
|
||||||
"difference", |
|
||||||
"intersection", |
|
||||||
"isDisjointFrom", |
|
||||||
"isSubsetOf", |
|
||||||
"isSupersetOf", |
|
||||||
"symmetricDifference", |
|
||||||
"union", |
|
||||||
]); |
|
||||||
|
|
||||||
const disallowedMethods = new Set<string | symbol>(["add", "clear", "delete"]); |
|
@ -1,43 +0,0 @@ |
|||||||
import type { SubjectProxyTarget } from "@ldo/jsonld-dataset-proxy"; |
|
||||||
import { |
|
||||||
createSubjectHandler, |
|
||||||
type SubjectProxy, |
|
||||||
} from "@ldo/jsonld-dataset-proxy"; |
|
||||||
import type { BlankNode, NamedNode } from "@rdfjs/types"; |
|
||||||
import type { TrackingProxyContext } from "./TrackingProxyContext"; |
|
||||||
import { namedNode } from "@rdfjs/data-model"; |
|
||||||
|
|
||||||
export function createTrackingSubjectProxy( |
|
||||||
proxyContext: TrackingProxyContext, |
|
||||||
node: NamedNode | BlankNode, |
|
||||||
): SubjectProxy { |
|
||||||
const baseHandler = createSubjectHandler(proxyContext); |
|
||||||
const oldGetFunction = baseHandler.get; |
|
||||||
const newGetFunction: ProxyHandler<SubjectProxyTarget>["get"] = ( |
|
||||||
target: SubjectProxyTarget, |
|
||||||
key: string | symbol, |
|
||||||
receiver, |
|
||||||
) => { |
|
||||||
const subject = target["@id"]; |
|
||||||
const rdfTypes = proxyContext.getRdfType(subject); |
|
||||||
if (typeof key === "symbol") { |
|
||||||
// Do Nothing
|
|
||||||
} else if (key === "@id") { |
|
||||||
proxyContext.addListener([subject, null, null, null]); |
|
||||||
} else if (!proxyContext.contextUtil.isSet(key, rdfTypes)) { |
|
||||||
const predicate = namedNode( |
|
||||||
proxyContext.contextUtil.keyToIri(key, rdfTypes), |
|
||||||
); |
|
||||||
proxyContext.addListener([subject, predicate, null, null]); |
|
||||||
} |
|
||||||
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 new Proxy({ "@id": node }, baseHandler) as unknown as SubjectProxy; |
|
||||||
} |
|
@ -1,55 +0,0 @@ |
|||||||
import { |
|
||||||
ContextUtil, |
|
||||||
JsonldDatasetProxyBuilder, |
|
||||||
} from "@ldo/jsonld-dataset-proxy"; |
|
||||||
import { LdoBuilder } from "@ldo/ldo"; |
|
||||||
import type { LdoBase, ShapeType } from "@ldo/ldo"; |
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react"; |
|
||||||
import { TrackingProxyContext } from "./TrackingProxyContext"; |
|
||||||
import { defaultGraph } from "@rdfjs/data-model"; |
|
||||||
import { useLdo } from "../SolidLdoProvider"; |
|
||||||
|
|
||||||
export function useTrackingProxy<Type extends LdoBase, ReturnType>( |
|
||||||
shapeType: ShapeType<Type>, |
|
||||||
createLdo: (builder: LdoBuilder<Type>) => ReturnType, |
|
||||||
): ReturnType { |
|
||||||
const { dataset } = useLdo(); |
|
||||||
|
|
||||||
const [forceUpdateCounter, setForceUpdateCounter] = useState(0); |
|
||||||
const forceUpdate = useCallback( |
|
||||||
() => setForceUpdateCounter((val) => val + 1), |
|
||||||
[], |
|
||||||
); |
|
||||||
|
|
||||||
// The main linked data object
|
|
||||||
const linkedDataObject = useMemo(() => { |
|
||||||
// Remove all current subscriptions
|
|
||||||
dataset.removeListenerFromAllEvents(forceUpdate); |
|
||||||
|
|
||||||
// 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"], |
|
||||||
}, |
|
||||||
forceUpdate, |
|
||||||
); |
|
||||||
const builder = new LdoBuilder( |
|
||||||
new JsonldDatasetProxyBuilder(proxyContext), |
|
||||||
shapeType, |
|
||||||
); |
|
||||||
return createLdo(builder); |
|
||||||
}, [shapeType, dataset, forceUpdateCounter, forceUpdate, createLdo]); |
|
||||||
|
|
||||||
useEffect(() => { |
|
||||||
// Unregister force update listener upon unmount
|
|
||||||
return () => { |
|
||||||
dataset.removeListenerFromAllEvents(forceUpdate); |
|
||||||
}; |
|
||||||
}, [shapeType]); |
|
||||||
|
|
||||||
return linkedDataObject; |
|
||||||
} |
|
Loading…
Reference in new issue