From 35c7e30b29fdbe0d3f25471c33c67d5ab7e78914 Mon Sep 17 00:00:00 2001 From: Polle Pas Date: Fri, 11 Oct 2024 14:24:42 +0200 Subject: [PATCH] Console fixes + small fixes & improvements #977 --- .../src/components/ChildrenList.tsx | 37 ----------- .../src/components/Dropdown/index.tsx | 9 ++- .../data-browser/src/components/Loader.tsx | 1 + .../data-browser/src/components/PropVal.tsx | 12 +++- .../components/TableEditor/TableEditor.tsx | 11 ++-- .../src/components/WarningBlock.tsx | 4 +- .../src/components/forms/Field.tsx | 61 +++++++++++-------- .../components/forms/InputResourceArray.tsx | 1 + .../src/components/forms/InputStyles.tsx | 7 ++- .../src/components/forms/ResourceField.tsx | 5 +- .../ResourceSelector/ResourceSelector.tsx | 3 + .../components/forms/SearchBox/SearchBox.tsx | 3 + .../FormValidationContextProvider.tsx | 15 ++--- .../src/routes/NewResource/NewRoute.tsx | 3 +- .../src/routes/PruneTestsRoute.tsx | 2 +- browser/data-browser/src/views/DrivePage.tsx | 39 ++---------- .../OntologyPage/Class/AddPropertyButton.tsx | 1 + .../src/views/ResourcePageDefault.tsx | 10 ++- .../src/views/TablePage/ExpandedRowDialog.tsx | 3 +- .../TablePage/PropertyForm/PropertyForm.tsx | 2 +- browser/data-browser/vite.config.ts | 15 +++++ browser/e2e/tests/e2e.spec.ts | 2 +- browser/e2e/tests/filePicker.spec.ts | 2 +- browser/e2e/tests/search.spec.ts | 4 +- browser/e2e/tests/test-utils.ts | 5 +- lib/defaults/default_store.json | 10 +++ lib/src/resources.rs | 24 ++++++++ 27 files changed, 154 insertions(+), 137 deletions(-) delete mode 100644 browser/data-browser/src/components/ChildrenList.tsx diff --git a/browser/data-browser/src/components/ChildrenList.tsx b/browser/data-browser/src/components/ChildrenList.tsx deleted file mode 100644 index ecc1cfef5..000000000 --- a/browser/data-browser/src/components/ChildrenList.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { useChildren } from '@tomic/react'; -import { useState } from 'react'; - -import { FaCaretDown, FaCaretRight } from 'react-icons/fa'; -import { ResourceInline } from '../views/ResourceInline'; -import { Button } from './Button'; -import { Card, CardInsideFull, CardRow } from './Card'; - -export function Childrenlist({ resource }) { - const [show, setShow] = useState(false); - - return ( - <> - - {show && } - - ); -} - -function ChildrenList({ resource }) { - const children = useChildren(resource); - - return ( - - - {children.map(s => ( - - - - ))} - - - ); -} diff --git a/browser/data-browser/src/components/Dropdown/index.tsx b/browser/data-browser/src/components/Dropdown/index.tsx index dab7c210a..d7e81ec1a 100644 --- a/browser/data-browser/src/components/Dropdown/index.tsx +++ b/browser/data-browser/src/components/Dropdown/index.tsx @@ -8,6 +8,7 @@ import { PropsWithChildren, forwardRef, ReactNode, + useEffect, } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { styled } from 'styled-components'; @@ -350,9 +351,11 @@ export function MenuItem({ }: MenuItemPropsExtended): JSX.Element { const ref = useRef(null); - if (selected && document.activeElement !== ref.current) { - ref.current?.focus(); - } + useEffect(() => { + if (selected && document.activeElement !== ref.current) { + ref.current?.focus(); + } + }, [selected]); return ( p.theme.radius}; animation: ${loadingAnimation} 0.8s infinite ease-in-out alternate; width: 100%; + display: inline-block; height: 1rem; `; diff --git a/browser/data-browser/src/components/PropVal.tsx b/browser/data-browser/src/components/PropVal.tsx index 191cb7507..bb3108984 100644 --- a/browser/data-browser/src/components/PropVal.tsx +++ b/browser/data-browser/src/components/PropVal.tsx @@ -6,6 +6,7 @@ import { ErrorLook } from './ErrorLook'; import { ValueForm } from './forms/ValueForm'; import ValueComp from './ValueComp'; import { ALL_PROPS_CONTAINER } from '../helpers/containers'; +import { LoaderInline } from './Loader'; type Props = { propertyURL: string; @@ -33,9 +34,7 @@ function PropVal({ if (property.loading) { return ( - - loading... - + ); } @@ -90,6 +89,13 @@ export const PropertyLabel = styled.span` font-weight: bold; `; +const StyledLoader = styled(LoaderInline)` + grid-column: 1 / 3; + margin-inline: 1rem; + margin-block: 0.5rem; + width: calc(100% - 2rem); +`; + interface PropValRowProps { columns?: boolean; } diff --git a/browser/data-browser/src/components/TableEditor/TableEditor.tsx b/browser/data-browser/src/components/TableEditor/TableEditor.tsx index 335f39d48..04b279107 100644 --- a/browser/data-browser/src/components/TableEditor/TableEditor.tsx +++ b/browser/data-browser/src/components/TableEditor/TableEditor.tsx @@ -65,18 +65,17 @@ interface RowProps { type OnScroll = (props: ListOnScrollProps) => unknown; -export function FancyTable(props: FancyTableProps): JSX.Element { +export function FancyTable({ + rowHeight = 40, + ...props +}: FancyTableProps): JSX.Element { return ( - + ); } -FancyTable.defaultProps = { - rowHeight: 40, -}; - function FancyTableInner({ children, columns, diff --git a/browser/data-browser/src/components/WarningBlock.tsx b/browser/data-browser/src/components/WarningBlock.tsx index 8cfa3e718..f54ef27d3 100644 --- a/browser/data-browser/src/components/WarningBlock.tsx +++ b/browser/data-browser/src/components/WarningBlock.tsx @@ -9,8 +9,8 @@ export function WarningBlock({ } const Wrapper = styled.div` - background-color: ${p => lighten(0.2, p.theme.colors.warning)}; - border: 2px solid ${p => p.theme.colors.warning}; + background-color: ${p => lighten(0.4, p.theme.colors.warning)}; + border: 2px solid ${p => lighten(0.2, p.theme.colors.warning)}; border-radius: ${p => p.theme.radius}; padding: 1rem; `; diff --git a/browser/data-browser/src/components/forms/Field.tsx b/browser/data-browser/src/components/forms/Field.tsx index e92bed1a0..828953a51 100644 --- a/browser/data-browser/src/components/forms/Field.tsx +++ b/browser/data-browser/src/components/forms/Field.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import React, { useState } from 'react'; import { FaAsterisk, FaInfo, FaTrash } from 'react-icons/fa'; import { styled } from 'styled-components'; import { Collapse } from '../Collapse'; @@ -12,6 +12,34 @@ import { } from './InputStyles'; import { complement } from 'polished'; +interface FieldProps { + /** Label */ + label?: string; + /** Helper text / collapsible info */ + helper?: React.ReactNode; + /** Here goes the input */ + children: React.ReactNode; + /** If the field is requires. Shows an aterisk with hover text */ + required?: boolean; + disabled?: boolean; + /** The error to be shown in the component */ + error?: Error; + + /** The id of the field. This is used to link the label with the input */ + fieldId?: string; + labelId?: string; + /** + * If the field contains multiple inputs like an array. + * This will make the component render a fieldset + legend instead of a label. + */ + multiInput?: boolean; + /** + * This function will be called when the delete icon is clicked. This should + * remove the item from any parent list + */ + handleDelete?: (url: string) => unknown; +} + /** High level form field skeleton. Pass the actual input as a child component. */ function Field({ label, @@ -23,19 +51,21 @@ function Field({ disabled, fieldId, labelId, -}: IFieldProps): JSX.Element { + multiInput, +}: FieldProps): JSX.Element { const [collapsedHelper, setCollapsed] = useState(true); return ( - + - {label} + {label} {required && } {helper && ( @@ -89,29 +119,6 @@ export const FieldLabel = styled.label` font-weight: bold; `; -interface IFieldProps { - /** Label */ - label?: string; - /** Helper text / collapsible info */ - helper?: React.ReactNode; - /** Here goes the input */ - children: React.ReactNode; - /** If the field is requires. Shows an aterisk with hover text */ - required?: boolean; - disabled?: boolean; - /** The error to be shown in the component */ - error?: Error; - - /** The id of the field. This is used to link the label with the input */ - fieldId?: string; - labelId?: string; - /** - * This function will be called when the delete icon is clicked. This should - * remove the item from any parent list - */ - handleDelete?: (url: string) => unknown; -} - export default Field; const Astrisk = styled(FaAsterisk)` diff --git a/browser/data-browser/src/components/forms/InputResourceArray.tsx b/browser/data-browser/src/components/forms/InputResourceArray.tsx index d2c643f12..41638f174 100644 --- a/browser/data-browser/src/components/forms/InputResourceArray.tsx +++ b/browser/data-browser/src/components/forms/InputResourceArray.tsx @@ -29,6 +29,7 @@ export default function InputResourceArray({ property, commit, required, + id: _id, ...props }: InputResourceArrayProps): JSX.Element { const [draggingSubject, setDraggingSubject] = useState(); diff --git a/browser/data-browser/src/components/forms/InputStyles.tsx b/browser/data-browser/src/components/forms/InputStyles.tsx index d77cd93b6..8f4a6c2c7 100644 --- a/browser/data-browser/src/components/forms/InputStyles.tsx +++ b/browser/data-browser/src/components/forms/InputStyles.tsx @@ -1,7 +1,10 @@ import { styled, css } from 'styled-components'; export const FieldStyled = styled.div` - margin-bottom: ${props => props.theme.margin}rem; + padding: 0; + margin-bottom: ${props => props.theme.size()}; + border: none; + background-color: none; `; export const LabelWrapper = styled.div` @@ -15,7 +18,7 @@ export const LabelStyled = styled.label` display: block; `; -export const LabelHelper = styled.label` +export const LabelHelper = styled.div` font-size: 0.9em; display: block; `; diff --git a/browser/data-browser/src/components/forms/ResourceField.tsx b/browser/data-browser/src/components/forms/ResourceField.tsx index 43eb8a586..0b1cae45b 100644 --- a/browser/data-browser/src/components/forms/ResourceField.tsx +++ b/browser/data-browser/src/components/forms/ResourceField.tsx @@ -1,4 +1,4 @@ -import { useProperty, Resource, Property } from '@tomic/react'; +import { useProperty, Resource, Property, Datatype } from '@tomic/react'; import { styled } from 'styled-components'; import Field from './Field'; import Markdown from '../datatypes/Markdown'; @@ -63,7 +63,7 @@ function ResourceField({ void; + id?: string; } const INVALID_RESOURCE_ERROR = 'Invalid Resource'; @@ -74,6 +75,7 @@ export const ResourceSelector = memo(function ResourceSelector({ last = true, prefix, allowsOnly, + id, onBlur, }: ResourceSelectorProps): JSX.Element { const store = useStore(); @@ -164,6 +166,7 @@ export const ResourceSelector = memo(function ResourceSelector({ hideClearButton={hideClearButton} allowsOnly={allowsOnly} visualError={error || warning} + id={id} onChange={handleSubjectChange} onCreateItem={handleCreateItem} onClose={handleBlur} diff --git a/browser/data-browser/src/components/forms/SearchBox/SearchBox.tsx b/browser/data-browser/src/components/forms/SearchBox/SearchBox.tsx index 99b054d86..750c8426b 100644 --- a/browser/data-browser/src/components/forms/SearchBox/SearchBox.tsx +++ b/browser/data-browser/src/components/forms/SearchBox/SearchBox.tsx @@ -32,6 +32,7 @@ interface SearchBoxProps { prefix?: React.ReactNode; hideClearButton?: boolean; visualError?: string; + id?: string; onChange: (value: string | undefined) => void; onCreateItem?: (name: string, isA?: string) => void; onClose?: () => void; @@ -51,6 +52,7 @@ export function SearchBox({ hideClearButton, allowsOnly, visualError, + id, onChange, onCreateItem, onClose, @@ -164,6 +166,7 @@ export function SearchBox({ setOpen(true); setJustFocussed(true); }} + id={id} > {value ? ( {title} diff --git a/browser/data-browser/src/components/forms/formValidation/FormValidationContextProvider.tsx b/browser/data-browser/src/components/forms/formValidation/FormValidationContextProvider.tsx index ed8606f68..dbcb36e90 100644 --- a/browser/data-browser/src/components/forms/formValidation/FormValidationContextProvider.tsx +++ b/browser/data-browser/src/components/forms/formValidation/FormValidationContextProvider.tsx @@ -2,6 +2,7 @@ import { createContext, PropsWithChildren, useCallback, + useEffect, useMemo, useState, } from 'react'; @@ -31,15 +32,7 @@ export function FormValidationContextProvider({ const setValidations = useCallback( (update: (newValidations: Validations) => Validations) => { - _setValidations(prev => { - const updatedValidations = update(prev); - - onValidationChange( - Object.values(updatedValidations).every(v => v === undefined), - ); - - return updatedValidations; - }); + _setValidations(prev => update(prev)); }, [onValidationChange], ); @@ -52,6 +45,10 @@ export function FormValidationContextProvider({ [validations, setValidations], ); + useEffect(() => { + onValidationChange(Object.values(validations).every(v => v === undefined)); + }, [validations, onValidationChange]); + return ( {children} diff --git a/browser/data-browser/src/routes/NewResource/NewRoute.tsx b/browser/data-browser/src/routes/NewResource/NewRoute.tsx index d6b2cb45f..edb9e307c 100644 --- a/browser/data-browser/src/routes/NewResource/NewRoute.tsx +++ b/browser/data-browser/src/routes/NewResource/NewRoute.tsx @@ -69,7 +69,7 @@ function NewResourceSelector() { navigate(constructOpenURL(calculatedParent)); } }, - [parentSubject, navigate], + [calculatedParent, navigate], ); return ( @@ -86,7 +86,6 @@ function NewResourceSelector() { -

Classes

{isWaiting &&

Pruning, this might take a while...

} -

+

{result && `✅ ${result.props.responseMessage}`}

diff --git a/browser/data-browser/src/views/DrivePage.tsx b/browser/data-browser/src/views/DrivePage.tsx index 8c725b7b0..66843c550 100644 --- a/browser/data-browser/src/views/DrivePage.tsx +++ b/browser/data-browser/src/views/DrivePage.tsx @@ -1,33 +1,17 @@ -import { - useArray, - Datatype, - dataBrowser, - core, - server, - useProperty, - useCanWrite, -} from '@tomic/react'; +import { Datatype, core, server, useProperty, useCanWrite } from '@tomic/react'; import { ContainerNarrow } from '../components/Containers'; -import { Card, CardInsideFull, CardRow } from '../components/Card'; -import { ResourceInline } from './ResourceInline'; import { ValueForm } from '../components/forms/ValueForm'; import { Button } from '../components/Button'; import { useSettings } from '../helpers/AppSettings'; -import { AtomicLink } from '../components/AtomicLink'; -import { FaPlus } from 'react-icons/fa'; -import { paths } from '../routes/paths'; import { ResourcePageProps } from './ResourcePage'; import { EditableTitle } from '../components/EditableTitle'; import { Column, Row } from '../components/Row'; import { styled } from 'styled-components'; import InputSwitcher from '../components/forms/InputSwitcher'; +import { WarningBlock } from '../components/WarningBlock'; /** A View for Drives, which function similar to a homepage or dashboard. */ function DrivePage({ resource }: ResourcePageProps): JSX.Element { - const [subResources] = useArray( - resource, - dataBrowser.properties.subResources, - ); const { drive: baseURL, setDrive: setBaseURL } = useSettings(); const defaultOntologyProp = useProperty(server.properties.defaultOntology); @@ -62,28 +46,13 @@ function DrivePage({ resource }: ResourcePageProps): JSX.Element { disabled={!canEdit} />
- - Resources: - - {subResources.map(child => ( - - - - ))} - - - Create new resource - - - - {baseURL.startsWith('http://localhost') && ( -

+ You are running Atomic-Server on `localhost`, which means that it will not be available from any other machine than your current local device. If you want your Atomic-Server to be available from the web, you should set this up at a Domain on a server. -

+ )}
diff --git a/browser/data-browser/src/views/OntologyPage/Class/AddPropertyButton.tsx b/browser/data-browser/src/views/OntologyPage/Class/AddPropertyButton.tsx index 3c5f5490f..b8d3e40e7 100644 --- a/browser/data-browser/src/views/OntologyPage/Class/AddPropertyButton.tsx +++ b/browser/data-browser/src/views/OntologyPage/Class/AddPropertyButton.tsx @@ -65,6 +65,7 @@ export function AddPropertyButton({ await addProperty(createdSubject); + // After create a new property, we move focus to the description input of the new property. focusOffsetElement(-4, triggerRef.current!); }; diff --git a/browser/data-browser/src/views/ResourcePageDefault.tsx b/browser/data-browser/src/views/ResourcePageDefault.tsx index 9f8d89999..ca593d62f 100644 --- a/browser/data-browser/src/views/ResourcePageDefault.tsx +++ b/browser/data-browser/src/views/ResourcePageDefault.tsx @@ -1,4 +1,11 @@ -import { useString, core, server, commits, useCanWrite } from '@tomic/react'; +import { + useString, + core, + server, + commits, + useCanWrite, + dataBrowser, +} from '@tomic/react'; import AllProps from '../components/AllProps'; import { ClassDetail } from '../components/ClassDetail'; import { ContainerNarrow } from '../components/Containers'; @@ -37,6 +44,7 @@ export const defaultHiddenProps = [ core.properties.read, // Shown in CommitDetail commits.properties.lastCommit, + dataBrowser.properties.subResources, ]; /** diff --git a/browser/data-browser/src/views/TablePage/ExpandedRowDialog.tsx b/browser/data-browser/src/views/TablePage/ExpandedRowDialog.tsx index 4d3f70c6e..fe5076611 100644 --- a/browser/data-browser/src/views/TablePage/ExpandedRowDialog.tsx +++ b/browser/data-browser/src/views/TablePage/ExpandedRowDialog.tsx @@ -1,4 +1,4 @@ -import { commits, core, useResource } from '@tomic/react'; +import { commits, core, useResource, dataBrowser } from '@tomic/react'; import { useEffect } from 'react'; import { Dialog, @@ -20,6 +20,7 @@ const EXCLUDED_PROPS = [ commits.properties.lastCommit, core.properties.parent, core.properties.isA, + dataBrowser.properties.subResources, ]; export function ExpandedRowDialog({ diff --git a/browser/data-browser/src/views/TablePage/PropertyForm/PropertyForm.tsx b/browser/data-browser/src/views/TablePage/PropertyForm/PropertyForm.tsx index 45662e813..03da27222 100644 --- a/browser/data-browser/src/views/TablePage/PropertyForm/PropertyForm.tsx +++ b/browser/data-browser/src/views/TablePage/PropertyForm/PropertyForm.tsx @@ -92,7 +92,7 @@ export function PropertyForm({ { // Set datatype of new property to boolean const selectDatatypeOption = await fillSearchBox( page, - 'Search for a datatype or enter a URL', + 'Datatype', 'boolean', ); await selectDatatypeOption('boolean - Either `true` or `false`'); diff --git a/browser/e2e/tests/filePicker.spec.ts b/browser/e2e/tests/filePicker.spec.ts index 5d6fb3c60..310c844df 100644 --- a/browser/e2e/tests/filePicker.spec.ts +++ b/browser/e2e/tests/filePicker.spec.ts @@ -30,7 +30,7 @@ const uploadFile = async (page: Page, fileName: string) => { await fileChooser.setFiles(testFilePath(fileName)); - await expect(page.getByText(fileName)).toHaveCount(2); + await expect(page.getByText(fileName)).toBeVisible(); }; // Creates an ontology with a class we can use to test the file picker. diff --git a/browser/e2e/tests/search.spec.ts b/browser/e2e/tests/search.spec.ts index dc18a8d79..a65ea89a9 100644 --- a/browser/e2e/tests/search.spec.ts +++ b/browser/e2e/tests/search.spec.ts @@ -19,7 +19,9 @@ test.describe('search', async () => { await page.fill(addressBar, 'welcome'); await expect(page.locator('text=Welcome to your')).toBeVisible(); await page.keyboard.press('Enter'); - await expect(page.locator('text=resources:')).toBeVisible(); + await expect( + page.getByRole('heading', { name: 'Default Ontology' }), + ).toBeVisible(); }); test('scoped search', async ({ page }) => { diff --git a/browser/e2e/tests/test-utils.ts b/browser/e2e/tests/test-utils.ts index 53c4b0d0d..75b66cc88 100644 --- a/browser/e2e/tests/test-utils.ts +++ b/browser/e2e/tests/test-utils.ts @@ -115,7 +115,6 @@ export async function newDrive(page: Page) { await currentDialog(page).getByRole('button', { name: 'Create' }).click(); await expect(currentDriveTitle(page)).not.toHaveText('localhost'); await expect(currentDriveTitle(page)).toHaveText(driveTitle); - await expect(page.locator('text="Create new resource"')).toBeVisible(); const driveURL = await getCurrentSubject(page); expect(driveURL).toContain('localhost'); @@ -292,7 +291,9 @@ export async function changeDrive(subject: string, page: Page) { await expect(page.locator('text=Drive Configuration')).toBeVisible(); await page.fill('[data-test="server-url-input"]', subject); await page.click('[data-test="server-url-save"]'); - await expect(page.locator('text=Create new resource')).toBeVisible(); + await expect( + page.getByRole('heading', { name: 'Default Ontology' }), + ).toBeVisible(); } export async function editTitle(title: string, page: Page) { diff --git a/lib/defaults/default_store.json b/lib/defaults/default_store.json index be560b91b..d2e25c1b1 100644 --- a/lib/defaults/default_store.json +++ b/lib/defaults/default_store.json @@ -521,6 +521,16 @@ "https://atomicdata.dev/properties/parent": "https://atomicdata.dev/properties", "https://atomicdata.dev/properties/shortname": "overwrite-outside" }, + { + "@id": "https://atomicdata.dev/properties/localId", + "https://atomicdata.dev/properties/datatype": "https://atomicdata.dev/datatypes/string", + "https://atomicdata.dev/properties/description": "A `localId` is used to derive the Subject (`@id`, the URL) of an imported Resource. This is useful when some data owner has Atomic Data but does not have the technology to host all Resources at their respective URLs. [See docs](https://docs.atomicdata.dev/create-json-ad.html#preventing-duplication-with-localid).", + "https://atomicdata.dev/properties/isA": [ + "https://atomicdata.dev/classes/Property" + ], + "https://atomicdata.dev/properties/parent": "https://atomicdata.dev/properties", + "https://atomicdata.dev/properties/shortname": "local-id" + }, { "@id": "https://atomicdata.dev/properties/incomplete", "https://atomicdata.dev/properties/classtype": "https://atomicdata.dev/classes/Property", diff --git a/lib/src/resources.rs b/lib/src/resources.rs index 0963b0e3b..ee1001fa1 100644 --- a/lib/src/resources.rs +++ b/lib/src/resources.rs @@ -845,6 +845,8 @@ mod test { resource2.save_locally(&store).unwrap(); let mut resource3 = Resource::new_generate_subject(&store); + let resource3_subject = resource3.get_subject().to_string(); + resource3 .set( urls::PARENT.into(), @@ -852,6 +854,9 @@ mod test { &store, ) .unwrap(); + resource3 + .set(urls::NAME.into(), Value::String("resource3".into()), &store) + .unwrap(); let subject3 = resource3.get_subject().to_string(); resource3.save_locally(&store).unwrap(); @@ -876,5 +881,24 @@ mod test { assert_panics!({ store.get_resource(&subject1).unwrap() }); assert_panics!({ store.get_resource(&subject2).unwrap() }); assert_panics!({ store.get_resource(&subject3).unwrap() }); + + // Create a new resource with the same subject as resource 3 to check if there's no old data left. + let mut resource4 = Resource::new(resource3_subject.to_string()); + + resource4 + .set( + urls::DESCRIPTION.into(), + Value::Markdown("description thing".into()), + &store, + ) + .unwrap(); + resource4.save_locally(&store).unwrap(); + + assert_eq!( + resource4.get(urls::DESCRIPTION).unwrap().to_string(), + "description thing" + ); + + assert!(resource4.get(urls::NAME).is_err()); } }