Skip to content

Commit

Permalink
#98 parse nested resources & resource status
Browse files Browse the repository at this point in the history
  • Loading branch information
joepio committed Oct 23, 2021
1 parent 4765b17 commit bc7c48f
Show file tree
Hide file tree
Showing 16 changed files with 128 additions and 89 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

This changelog covers all three packges, as they are (for now) updated as a whole

## v0.27.0

- Parse nested, named JSON-AD resources
- Refactor resource status

## v0.26.2

- Add [Typedoc documentation](https://joepio.github.io/atomic-data-browser/docs/modules.html) #100
Expand Down
14 changes: 10 additions & 4 deletions data-browser/src/components/PropVal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { truncateUrl, Resource } from '@tomic/lib';
import { truncateUrl, Resource, Datatype } from '@tomic/lib';
import { useProperty } from '@tomic/react';
import React from 'react';
import styled from 'styled-components';
Expand Down Expand Up @@ -47,13 +47,19 @@ function PropVal({
columns,
}: Props): JSX.Element {
const property = useProperty(propertyURL);
const truncated = truncateUrl(propertyURL, 10, true);

if (property == null) {
return null;
return (
<PropValRow columns={columns}>
<PropertyLabel title={propertyURL + ' could not be loaded'}>
<ErrorLook>{truncated}</ErrorLook>
</PropertyLabel>
<code>{JSON.stringify(resource.get(propertyURL))}</code>
</PropValRow>
);
}

const truncated = truncateUrl(propertyURL, 10, true);

return (
<PropValRow columns={columns}>
<AtomicLink subject={propertyURL}>
Expand Down
9 changes: 4 additions & 5 deletions data-browser/src/components/ResourceSideBar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { useString, useResource, useTitle } from '@tomic/react';
import { ResourceStatus, urls } from '@tomic/lib';
import { urls } from '@tomic/lib';
import { ErrorLook } from '../views/ResourceInline';
import { useHistory } from 'react-router-dom';
import { useCurrentSubject } from '../helpers/useCurrentSubject';
Expand Down Expand Up @@ -36,15 +36,14 @@ export function ResourceSideBar({ subject, handleClose }: Props): JSX.Element {
}
};

const status = resource.getStatus();
if (status == ResourceStatus.loading) {
if (resource.loading) {
return (
<span about={subject} title={`${subject} is loading..`}>
...
loading...
</span>
);
}
if (status == ResourceStatus.error) {
if (resource.error) {
return (
<SideBarItem
clean
Expand Down
6 changes: 3 additions & 3 deletions data-browser/src/components/SideBar.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import styled from 'styled-components';
import * as React from 'react';
import { useArray, useResource, useStore, useTitle } from '@tomic/react';
import { properties, ResourceStatus } from '@tomic/lib';
import { useArray, useResource, useTitle } from '@tomic/react';
import { properties } from '@tomic/lib';
import { useHover } from '../helpers/useHover';
import { useSettings } from '../helpers/AppSettings';
import { useWindowSize } from '../helpers/useWindowSize';
Expand Down Expand Up @@ -206,7 +206,7 @@ function SideBarDrive({ handleClickItem }: SideBarDriveProps): JSX.Element {
/>
);
})
) : drive.getStatus() == ResourceStatus.loading ? null : (
) : drive.loading ? null : (
<SideBarErr>
{drive.getError()?.message || 'Could not load this baseURL'}
</SideBarErr>
Expand Down
18 changes: 5 additions & 13 deletions data-browser/src/components/forms/ResourceForm.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
import React, { useEffect, useState } from 'react';
import { useHistory } from 'react-router-dom';
import {
Resource,
ResourceStatus,
classes,
properties,
urls,
} from '@tomic/lib';
import { Resource, classes, properties, urls } from '@tomic/lib';
import {
useArray,
useCanWrite,
Expand Down Expand Up @@ -51,8 +45,6 @@ export function ResourceForm({
}
const [klass] = useResource(classSubject);
const store = useStore();
const resourceStatus = resource.getStatus();
const classStatus = klass.getStatus();
const [requires] = useArray(klass, properties.requires);
const [recommends] = useArray(klass, properties.recommends);
const [klassIsa] = useString(klass, properties.isA);
Expand Down Expand Up @@ -117,13 +109,13 @@ export function ResourceForm({
// array changes, but that leads to a weird loop, so that's what the length is for
}, [resource, tempOtherProps, requires.length, recommends.length]);

if (resourceStatus == ResourceStatus.loading) {
if (!resource.new && resource.loading) {
return <>Loading resource...</>;
}
if (resourceStatus == ResourceStatus.error) {
if (resource.error) {
return <ErrMessage>{resource.getError().message}</ErrMessage>;
}
if (classStatus == ResourceStatus.loading) {
if (klass.loading) {
return <>Loading class...</>;
}
if (klassIsa && klassIsa !== classes.class) {
Expand Down Expand Up @@ -182,7 +174,7 @@ export function ResourceForm({

return (
<form about={resource.getSubject()}>
{classStatus == ResourceStatus.error && (
{klass.error && (
<ErrMessage>
Error in class. {klass.getError().message}. You can still edit the
resource, though.
Expand Down
8 changes: 3 additions & 5 deletions data-browser/src/routes/DataRoute.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, { useState } from 'react';
import { useResource, useStore } from '@tomic/react';
import { ResourceStatus } from '@tomic/lib';
import AllProps from '../components/AllProps';
import { ContainerNarrow } from '../components/Containers';
import AtomicLink from '../components/Link';
Expand All @@ -14,16 +13,15 @@ import { ErrMessage } from '../components/forms/InputStyles';
function Data(): JSX.Element {
const [subject] = useCurrentSubject();
const [resource] = useResource(subject);
const status = resource.getStatus();
const [textResponse, setTextResponse] = useState(null);
const [isCopied, setIsCopied] = useState(false);
const [err, setErr] = useState(null);
const store = useStore();

if (status == ResourceStatus.loading) {
return <ContainerNarrow>Loading...</ContainerNarrow>;
if (resource.loading) {
return <ContainerNarrow>Loading {subject}...</ContainerNarrow>;
}
if (status == ResourceStatus.error) {
if (resource.error) {
return <ContainerNarrow>{resource.getError().message}</ContainerNarrow>;
}

Expand Down
7 changes: 3 additions & 4 deletions data-browser/src/views/ResourceCard.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useState } from 'react';
import { useInView } from 'react-intersection-observer';
import { useString, useResource, useTitle } from '@tomic/react';
import { Resource, ResourceStatus, properties, urls } from '@tomic/lib';
import { Resource, properties, urls } from '@tomic/lib';
import AllProps from '../components/AllProps';
import AtomicLink from '../components/Link';
import { Card } from '../components/Card';
Expand Down Expand Up @@ -73,11 +73,10 @@ function ResourceCardInner(props: Props): JSX.Element {
const title = useTitle(resource);
const [klass] = useString(resource, properties.isA);

const status = resource.getStatus();
if (status == ResourceStatus.loading) {
if (resource.loading) {
return <p>Loading...</p>;
}
if (status == ResourceStatus.error) {
if (resource.error) {
return (
<ErrorLook>
<AtomicLink subject={subject}>
Expand Down
7 changes: 3 additions & 4 deletions data-browser/src/views/ResourceInline.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import styled from 'styled-components';
import { useString, useResource, useTitle } from '@tomic/react';
import { ResourceStatus, urls } from '@tomic/lib';
import { urls } from '@tomic/lib';
import AtomicLink from '../components/Link';

type Props = {
Expand All @@ -15,15 +15,14 @@ function ResourceInline({ subject, untabbable }: Props): JSX.Element {
const title = useTitle(resource);
const [description] = useString(resource, urls.properties.description);

const status = resource.getStatus();
if (status == ResourceStatus.loading) {
if (resource.loading) {
return (
<span about={subject} title={`${subject} is loading..`}>
...
</span>
);
}
if (status == ResourceStatus.error) {
if (resource.error) {
return (
<AtomicLink subject={subject} untabbable={untabbable}>
<ErrorLook about={subject} title={resource.getError().message}>
Expand Down
7 changes: 3 additions & 4 deletions data-browser/src/views/ResourceLine.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { useString, useResource, useTitle } from '@tomic/react';
import { ResourceStatus, urls } from '@tomic/lib';
import { urls } from '@tomic/lib';
import ResourceInline, { ErrorLook } from './ResourceInline';

type Props = {
Expand All @@ -14,11 +14,10 @@ function ResourceLine({ subject, clickable }: Props): JSX.Element {
const title = useTitle(resource);
let [description] = useString(resource, urls.properties.description);

const status = resource.getStatus();
if (status == ResourceStatus.loading) {
if (resource.loading) {
return <span about={subject}>Loading...</span>;
}
if (status == ResourceStatus.error) {
if (resource.error) {
return (
<ErrorLook about={subject}>
Error: {resource.getError().message}
Expand Down
5 changes: 2 additions & 3 deletions data-browser/src/views/ResourcePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,10 @@ function ResourcePage({ subject }: Props): JSX.Element {
const [klass] = useString(resource, properties.isA);
const store = useStore();

const status = resource.getStatus();
if (status == ResourceStatus.loading) {
if (resource.loading) {
return <ContainerNarrow>Loading...</ContainerNarrow>;
}
if (status == ResourceStatus.error) {
if (resource.error) {
return (
<ContainerNarrow>
<h1>⚠️ {title}</h1>
Expand Down
12 changes: 10 additions & 2 deletions lib/src/client.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Store } from '.';
import { Commit, serializeDeterministically } from './commit';
import { parseJsonADResource } from './parse';
import { Resource } from './resource';
Expand All @@ -9,6 +10,11 @@ import { Resource } from './resource';
*/
export async function fetchResource(
subject: string,
/**
* Pass the Store if you want to directly add the resource (and its possible
* nested child Resources) to the Store.
*/
store?: Store,
/**
* Base URL of an atomic server. Uses the `/path` endpoint to indirectly fetch
* through that server.
Expand Down Expand Up @@ -37,14 +43,15 @@ export async function fetchResource(
const body = await response.text();
if (response.status == 200) {
const json = JSON.parse(body);
parseJsonADResource(json, resource);
parseJsonADResource(json, resource, store);
} else {
const error = new Error(`${response.status} error: ${body}`);
resource.setError(error);
}
} catch (e) {
resource.setError(e);
}
resource.loading = false;
return resource;
}

Expand Down Expand Up @@ -99,5 +106,6 @@ export function isValidURL(subject: string): boolean {
*/
// TODO: Not sure about this. Was done because `new Commit()` failed with `unknown-subject`.
export function removeQueryParamsFromURL(subject: string): string {
return subject.split('?')[0];
// return subject.split('?')[0];
return subject;
}
2 changes: 1 addition & 1 deletion lib/src/datatypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ export const validate = (value: JSONValue, datatype: Datatype): void => {
}
};

function isArray(val: JSONValue): val is [] {
export function isArray(val: JSONValue): val is [] {
return Object.prototype.toString.call(val) === '[object Array]';
}

Expand Down
64 changes: 60 additions & 4 deletions lib/src/parse.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { Resource, ResourceStatus, unknownSubject } from './resource';
import { isArray, JSONValue, Store } from '.';
import { Resource, unknownSubject } from './resource';
import { JSONObject } from './value';

/** Parses an JSON-AD object containing a resoure, adds it to the input Resource */
export function parseJsonADResource(
jsonObject: JSONObject,
resource: Resource,
/** Pass a Store if you want to add the parsed resources to it */
store?: Store,
): void {
try {
for (const key in jsonObject) {
Expand All @@ -24,24 +27,77 @@ export function parseJsonADResource(
resource.setSubject(subject);
continue;
}
const value = jsonObject[key];
try {
resource.setUnsafe(key, jsonObject[key]);
// Resource values can be either strings (URLs) or full Resources, which in turn can be either Anonymous (no @id) or Named (with an @id)
if (isArray(value)) {
const newarr = value.map(val =>
parseJsonAdResourceValue(store, val, resource, key),
);
resource.setUnsafe(key, newarr);
} else if (typeof value === 'string') {
resource.setUnsafe(key, value);
} else if (typeof value === 'number') {
resource.setUnsafe(key, value);
} else if (typeof value === 'boolean') {
resource.setUnsafe(key, value);
} else {
const subject = parseJsonAdResourceValue(store, value, resource, key);
resource.setUnsafe(key, subject);
}
} catch (e) {
throw new Error(
`Failed creating value for key ${key} in resource ${resource.getSubject()}. ${e.message
`Failed creating value ${value} for key ${key} in resource ${resource.getSubject()}. ${e.message
}`,
);
}
}
resource.setStatus(ResourceStatus.ready);
resource.loading == false;
store && store.addResource(resource);
} catch (e) {
e.message = 'Failed parsing JSON ' + e.message;
resource.setError(e);
resource.loading == false;
store && store.addResource(resource);
throw e;
}
return;
}

type StringOrNestedResource = string | JSONObject;

/**
* Parses a JSON-AD Value. If it's a string, it takes its URL. If it's an
* Object, it will parse it as a Resource. It will add the string property to
* the Resource.
*/
function parseJsonAdResourceValue(
store: Store,
value: JSONValue,
resource: Resource,
key: string,
): StringOrNestedResource {
if (typeof value === 'string') {
return value;
}
if (value.constructor === {}.constructor) {
if (Object.keys(value).includes('@id')) {
// It's a named resource that needs to be put in the store
const nestedSubject = value['@id'];
const nestedResource = new Resource(nestedSubject);
parseJsonADResource(value as JSONObject, nestedResource, store);
if (store) {
store.addResource(nestedResource);
}
return nestedSubject;
} else {
// It's an anonymous nested Resource
return value as JSONObject;
}
}
throw new Error(`Value ${value} in ${key} not a string or a nested Resource`);
}

/** Parsees a JSON-AD array string, returns array of Resources */
export function parseJsonADArray(jsonArray: any[]): Resource[] {
const resources: Resource[] = [];
Expand Down

0 comments on commit bc7c48f

Please sign in to comment.