Skip to content

Commit

Permalink
#648 Add Instances to ontologies
Browse files Browse the repository at this point in the history
  • Loading branch information
Polleps committed Oct 18, 2023
1 parent 00d6775 commit 43a7312
Show file tree
Hide file tree
Showing 25 changed files with 580 additions and 98 deletions.
4 changes: 1 addition & 3 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
"editor.formatOnSave": true,
"files.autoSave": "onFocusChange",
"rust-analyzer.checkOnSave.command": "clippy",
"editor.rulers": [
80
],

"search.exclude": {
"**/.git": true,
"**/node_modules": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@
border: none;
cursor: grab;
}

.react-flow__attribution {
background: unset;
}
4 changes: 0 additions & 4 deletions browser/data-browser/src/components/AtomicLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,6 @@ export const LinkView = styled.a<LinkViewProps>`
cursor: pointer;
pointer-events: ${props => (props.disabled ? 'none' : 'inherit')};
svg {
font-size: 60%;
}
&:hover {
color: ${props => props.theme.colors.mainLight};
text-decoration: ${p => (p.clean ? 'none' : 'underline')};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import {
Datatype,
classes,
properties,
useResource,
validateDatatype,
} from '@tomic/react';
import React, { FormEvent, useCallback, useState } from 'react';
import { Button } from '../Button';
import { Dialog, DialogActions, DialogContent, useDialog } from '../Dialog';
import Field from '../forms/Field';
import { InputStyled, InputWrapper } from '../forms/InputStyles';
import { Base } from './Base';
import { useCreateAndNavigate } from './useCreateAndNavigate';
import { NewInstanceButtonProps } from './NewInstanceButtonProps';
import { stringToSlug } from '../../helpers/stringToSlug';
import { styled } from 'styled-components';

export function NewOntologyButton({
klass,
subtle,
icon,
IconComponent,
parent,
children,
label,
}: NewInstanceButtonProps): JSX.Element {
const ontology = useResource(klass);

const [shortname, setShortname] = useState('');
const [valid, setValid] = useState(false);

const createResourceAndNavigate = useCreateAndNavigate(klass, parent);

const onSuccess = useCallback(async () => {
createResourceAndNavigate('ontology', {
[properties.shortname]: shortname,
[properties.isA]: [classes.ontology],
[properties.description]: 'description',
[properties.classes]: [],
[properties.properties]: [],
[properties.instances]: [],
});
}, [shortname]);

const [dialogProps, show, hide] = useDialog({ onSuccess });

const onShortnameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = stringToSlug(e.target.value);
setShortname(value);

try {
validateDatatype(value, Datatype.SLUG);
setValid(true);
} catch (_) {
setValid(false);
}
};

return (
<>
<Base
onClick={show}
title={ontology.title}
icon={icon}
IconComponent={IconComponent}
subtle={subtle}
label={label}
>
{children}
</Base>
<Dialog {...dialogProps}>
<H1>New Ontology</H1>
<DialogContent>
<form
onSubmit={(e: FormEvent) => {
e.preventDefault();
hide(true);
}}
>
<Explanation>
An ontology is a collection of classes and properties that
together describe a concept. Great for data models.
</Explanation>
<Field required label='Shortname'>
<InputWrapper>
<InputStyled
placeholder='my-ontology'
value={shortname}
autoFocus={true}
onChange={onShortnameChange}
/>
</InputWrapper>
</Field>
</form>
</DialogContent>
<DialogActions>
<Button onClick={() => hide(false)} subtle>
Cancel
</Button>
<Button onClick={() => hide(true)} disabled={!valid}>
Create
</Button>
</DialogActions>
</Dialog>
</>
);
}

const H1 = styled.h1`
margin: 0;
`;

const Explanation = styled.p`
color: ${p => p.theme.colors.textLight};
max-width: 60ch;
`;
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import { NewInstanceButtonProps } from './NewInstanceButtonProps';
import { NewInstanceButtonDefault } from './NewInstanceButtonDefault';
import { useSettings } from '../../helpers/AppSettings';
import { NewTableButton } from './NewTableButton';
import { NewOntologyButton } from './NewOntologyButton';

type InstanceButton = (props: NewInstanceButtonProps) => JSX.Element;

/** If your New Instance button requires custom logic, such as a custom dialog */
const classMap = new Map<string, InstanceButton>([
[classes.bookmark, NewBookmarkButton],
[classes.table, NewTableButton],
[classes.ontology, NewOntologyButton],
]);

/** A button for creating a new instance of some thing */
Expand Down
10 changes: 2 additions & 8 deletions browser/data-browser/src/components/SideBar/AppMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
import { constructOpenURL } from '../../helpers/navigation';
import { useCurrentSubject } from '../../helpers/useCurrentSubject';
import { SideBarMenuItem } from './SideBarMenuItem';
import styled from 'styled-components';
import { paths } from '../../routes/paths';
import { unknownSubject, useCurrentAgent, useResource } from '@tomic/react';

Expand Down Expand Up @@ -57,7 +56,7 @@ export function AppMenu({ onItemClick }: AppMenuProps): JSX.Element {
}, []);

return (
<Section aria-label='App menu'>
<section aria-label='App menu'>
<SideBarMenuItem
icon={<FaUser />}
label={agent ? agentResource.title : 'Login'}
Expand Down Expand Up @@ -95,11 +94,6 @@ export function AppMenu({ onItemClick }: AppMenuProps): JSX.Element {
onClick={install}
/>
)}
</Section>
</section>
);
}

const Section = styled.section`
border-top: 1px solid ${p => p.theme.colors.bg2};
padding-top: ${p => p.theme.margin}rem;
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import React from 'react';
import styled from 'styled-components';
import {
Collection,
unknownSubject,
urls,
useCollection,
useMemberFromCollection,
} from '@tomic/react';
import { SideBarItem } from '../SideBarItem';
import { Row } from '../../Row';
import { AtomicLink } from '../../AtomicLink';
import { getIconForClass } from '../../../views/FolderPage/iconMap';
import { ScrollArea } from '../../ScrollArea';
import { ErrorLook } from '../../ErrorLook';

export function OntologiesPanel(): JSX.Element | null {
const { collection } = useCollection({
property: urls.properties.isA,
value: urls.classes.ontology,
});

return (
<Wrapper>
<StyledScrollArea>
{[...Array(collection.totalMembers).keys()].map(index => (
<Item key={index} collection={collection} index={index} />
))}
</StyledScrollArea>
</Wrapper>
);
}

const Wrapper = styled.div`
padding-top: 0;
max-height: 10rem;
overflow: hidden;
`;

const StyledScrollArea = styled(ScrollArea)`
height: 10rem;
overflow-x: hidden;
`;

interface ItemProps {
index: number;
collection: Collection;
}

function Item({ index, collection }: ItemProps): JSX.Element {
const resource = useMemberFromCollection(collection, index);

const Icon = getIconForClass(urls.classes.ontology);

if (resource.loading) {
return <div>loading</div>;
}

if (resource.error || resource.getSubject() === unknownSubject) {
return (
<SideBarItem>
<ErrorLook>Invalid Resource</ErrorLook>
</SideBarItem>
);
}

return (
<StyledLink subject={resource.getSubject()} clean>
<SideBarItem>
<Row gap='1ch' center>
<Icon />
{resource.title}
</Row>
</SideBarItem>
</StyledLink>
);
}

const StyledLink = styled(AtomicLink)`
flex: 1;
overflow: hidden;
white-space: nowrap;
`;
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,6 @@ const TextWrapper = styled.span`
display: inline-flex;
align-items: center;
gap: 0.4rem;
svg {
/* color: ${p => p.theme.colors.text}; */
font-size: 0.8em;
}
`;

const SideBarErrorWrapper = styled(TextWrapper)`
Expand Down
4 changes: 4 additions & 0 deletions browser/data-browser/src/components/SideBar/SideBarItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,8 @@ export const SideBarItem = styled('span')<SideBarItemProps>`
&:active {
background-color: ${p => p.theme.colors.bg2};
}
svg {
font-size: 0.8rem;
}
`;
75 changes: 75 additions & 0 deletions browser/data-browser/src/components/SideBar/SideBarPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React from 'react';
import styled from 'styled-components';
import { Collapse } from '../Collapse';
import { FaCaretRight } from 'react-icons/fa';
import { transition } from '../../helpers/transition';

interface SideBarPanelProps {
title: string;
}

export function SideBarPanel({
children,
title,
}: React.PropsWithChildren<SideBarPanelProps>): JSX.Element {
const [open, setOpen] = React.useState(true);

return (
<Wrapper>
<DeviderButton onClick={() => setOpen(prev => !prev)}>
<PanelDevider>
<Arrow $open={open} />
{title}
</PanelDevider>
</DeviderButton>
<Collapse open={open}>{children}</Collapse>
</Wrapper>
);
}

export const PanelDevider = styled.h2`
font-size: inherit;
font-weight: normal;
font-family: inherit;
width: 100%;
display: flex;
align-items: center;
gap: 1ch;
margin-bottom: 0;
&::before,
&::after {
content: '';
flex: 1;
border-top: 1px solid ${p => p.theme.colors.bg2};
}
cursor: pointer;
&:hover,
&:focus {
&::before,
&::after {
border-color: ${p => p.theme.colors.text};
}
}
`;

const DeviderButton = styled.button`
background: none;
border: none;
margin: 0;
padding: 0;
`;

const Arrow = styled(FaCaretRight)<{ $open: boolean }>`
transform: rotate(${p => (p.$open ? '90deg' : '0deg')});
${transition('transform')}
`;

const Wrapper = styled.div`
width: 100%;
max-height: fit-content;
display: flex;
flex-direction: column;
`;
Loading

0 comments on commit 43a7312

Please sign in to comment.