Skip to content

Commit

Permalink
#906 Reset resource after cancel edit
Browse files Browse the repository at this point in the history
  • Loading branch information
Polleps committed Jul 15, 2024
1 parent f4e3b15 commit 9c5ac27
Show file tree
Hide file tree
Showing 13 changed files with 201 additions and 115 deletions.
10 changes: 7 additions & 3 deletions browser/data-browser/src/components/EditableTitle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
transitionName,
} from '../helpers/transitionName';
import { ViewTransitionProps } from '../helpers/ViewTransitionProps';
import { UnsavedIndicator } from './UnsavedIndicator';

export interface EditableTitleProps {
resource: Resource;
Expand Down Expand Up @@ -84,11 +85,14 @@ export function EditableTitle({
data-test='editable-title'
onClick={handleClick}
subtle={!!canEdit && !text}
subject={resource.getSubject()}
subject={resource.subject}
className={className}
>
<>
{text || placeholder}
<span>
{text || placeholder}
<UnsavedIndicator resource={resource} />
</span>
{canEdit && <Icon />}
</>
</Title>
Expand All @@ -109,7 +113,7 @@ const Title = styled.h1<TitleProps & ViewTransitionProps>`
${TitleShared}
display: flex;
align-items: center;
gap: ${p => p.theme.margin}rem;
gap: ${p => p.theme.size()};
cursor: ${props => (props.canEdit ? 'pointer' : 'initial')};
opacity: ${props => (props.subtle ? 0.5 : 1)};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,7 @@ const IconButtonBase = styled.button<ButtonBaseProps>`
border: none;
user-select: none;
padding: var(--button-padding);
width: calc(${p => p.size} + var(--button-padding) * 2);
height: calc(${p => p.size} + var(--button-padding) * 2);
aspect-ratio: 1/1;
margin-inline-start: ${p =>
p.edgeAlign === 'start' ? 'calc(var(--button-padding) * -1)' : '0'};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
useCanWrite,
core,
dataBrowser,
unknownSubject,
} from '@tomic/react';
import { useCurrentSubject } from '../../../helpers/useCurrentSubject';
import { SideBarItem } from '../SideBarItem';
Expand All @@ -33,12 +34,12 @@ interface ResourceSideBarProps {
}

/** Renders a Resource as a nav item for in the sidebar. */
export function ResourceSideBar({
export const ResourceSideBar: React.FC<ResourceSideBarProps> = ({
subject,
renderedHierargy,
ancestry,
onClick,
}: ResourceSideBarProps): JSX.Element {
}) => {
if (renderedHierargy.length === 0) {
throw new Error('renderedHierargy should not be empty');
}
Expand Down Expand Up @@ -103,6 +104,10 @@ export function ResourceSideBar({
}
}, [ancestry]);

if (!subject || subject === unknownSubject) {
return null;
}

if (resource.loading) {
return (
<SideBarItem
Expand Down Expand Up @@ -158,7 +163,7 @@ export function ResourceSideBar({
</Details>
</Wrapper>
);
}
};

const Wrapper = styled.div<{ highlight: boolean }>`
background-color: ${p =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
import { useSettings } from '../../../helpers/AppSettings';
import { IconButton } from '../../IconButton/IconButton';
import { FaGripVertical } from 'react-icons/fa6';
import { UnsavedIndicator } from '../../UnsavedIndicator';

interface SidebarItemTitleProps {
subject: string;
Expand Down Expand Up @@ -70,6 +71,7 @@ export const SidebarItemTitle = forwardRef<
<FaGripVertical />
</StyledIconButton>
{resource.title}
<UnsavedIndicator resource={resource} />
</TextWrapper>
</SideBarItem>
</StyledLink>
Expand All @@ -90,6 +92,7 @@ export const SidebarItemTitle = forwardRef<
<TextWrapper>
<Icon />
{resource.title}
<UnsavedIndicator resource={resource} />
</TextWrapper>
</SideBarItem>
</StyledLink>
Expand Down
31 changes: 31 additions & 0 deletions browser/data-browser/src/components/UnsavedIndicator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ResourceEvents, type Resource } from '@tomic/react';
import { useEffect, useState } from 'react';
import styled from 'styled-components';

interface UnsavedIndicatorProps {
resource: Resource;
}

export const UnsavedIndicator: React.FC<UnsavedIndicatorProps> = ({
resource,
}) => {
const [hasChanges, setHasChanges] = useState(resource.hasUnsavedChanges());

useEffect(() => {
setHasChanges(resource.hasUnsavedChanges());

return resource.on(ResourceEvents.LocalChange, () => {
setHasChanges(resource.hasUnsavedChanges());
});
}, [resource]);

if (!hasChanges) {
return null;
}

return <Indicator>*</Indicator>;
};

const Indicator = styled.span`
color: ${p => p.theme.colors.warning};
`;
49 changes: 24 additions & 25 deletions browser/data-browser/src/components/forms/ResourceForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@ import { useNavigate } from 'react-router-dom';
import {
useArray,
useResource,
useString,
Resource,
classes,
properties,
urls,
useDebounce,
useCanWrite,
Client,
useStore,
core,
commits,
} from '@tomic/react';
import { FaCaretDown, FaCaretRight } from 'react-icons/fa';

Expand Down Expand Up @@ -44,14 +42,15 @@ export interface ResourceFormProps {

variant?: ResourceFormVariant;
onSave?: () => void;
onCancel?: () => void;
}

const nonEssentialProps = [
properties.isA,
properties.parent,
properties.read,
properties.write,
properties.commit.lastCommit,
const nonEssentialProps: string[] = [
core.properties.isA,
core.properties.parent,
core.properties.write,
core.properties.read,
commits.properties.lastCommit,
];

/** Form for editing and creating a Resource */
Expand All @@ -60,8 +59,9 @@ export function ResourceForm({
resource,
variant,
onSave,
onCancel,
}: ResourceFormProps): JSX.Element {
const [isAArray] = useArray(resource, properties.isA);
const [isAArray] = useArray(resource, core.properties.isA);

if (classSubject === undefined && isAArray?.length > 0) {
// This is not entirely accurate, as Atomic Data supports having multiple
Expand All @@ -70,9 +70,8 @@ export function ResourceForm({
}

const klass = useResource(classSubject);
const [requires] = useArray(klass, properties.requires);
const [recommends] = useArray(klass, properties.recommends);
const [klassIsa] = useString(klass, properties.isA);
const [requires] = useArray(klass, core.properties.requires);
const [recommends] = useArray(klass, core.properties.recommends);
const [newPropErr, setNewPropErr] = useState<Error | undefined>(undefined);
const navigate = useNavigate();
/** A list of custom properties, set by the User while editing this form */
Expand Down Expand Up @@ -126,7 +125,7 @@ export function ResourceForm({
return <>Loading class...</>;
}

if (klassIsa && klassIsa !== classes.class) {
if (!klass.hasClasses(core.classes.class)) {
return (
<ErrMessage>
{classSubject} is not a Class. Only resources with valid classes can be
Expand Down Expand Up @@ -169,7 +168,7 @@ export function ResourceForm({
}

return (
<form about={resource.getSubject()} onSubmit={save}>
<form about={resource.subject} onSubmit={save}>
<Column>
{classSubject && klass.error && (
<ErrMessage>
Expand Down Expand Up @@ -234,25 +233,25 @@ export function ResourceForm({
handleAddProp(set);
}}
error={newPropErr}
isA={urls.classes.property}
isA={core.classes.property}
/>
</div>
{newPropErr && <ErrMessage>{newPropErr.message}</ErrMessage>}
</Field>
<ResourceField propertyURL={properties.isA} resource={resource} />
<ResourceField propertyURL={properties.parent} resource={resource} />
<ResourceField propertyURL={properties.write} resource={resource} />
<ResourceField propertyURL={properties.read} resource={resource} />
<ResourceField
propertyURL={properties.commit.lastCommit}
resource={resource}
/>
{nonEssentialProps.map(prop => (
<ResourceField key={prop} propertyURL={prop} resource={resource} />
))}
</Column>
</StyledCollapse>
{variant !== ResourceFormVariant.Dialog && (
<>
{err && <ErrMessage>{err.message}</ErrMessage>}
<Row justify='flex-end'>
{onCancel && (
<Button subtle onClick={onCancel}>
Cancel
</Button>
)}
<Button disabled={saving} data-test='save' type='submit'>
<FaFloppyDisk />
{saving ? 'wait...' : 'Save'}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,9 @@ import { useHotkeys } from 'react-hotkeys-hook';
import { FaEdit } from 'react-icons/fa';
import { styled } from 'styled-components';
import { useProperty, useValue, Datatype, Resource } from '@tomic/react';
import { Button } from '../Button';
import ValueComp from '../ValueComp';
import { ErrMessage } from './InputStyles';
import InputSwitcher from './InputSwitcher';
import { useSettings } from '../../helpers/AppSettings';
import toast from 'react-hot-toast';
import { Column, Row } from '../Row';
import ValueComp from '../../ValueComp';
import { useSettings } from '../../../helpers/AppSettings';
import { ValueFormEdit } from './ValueFormEdit';

interface ValueFormProps {
// Maybe pass Value instead of Resource?
Expand All @@ -31,6 +27,7 @@ export function ValueForm({ resource, propertyURL, datatype }: ValueFormProps) {
const property = useProperty(propertyURL);
const [value] = useValue(resource, propertyURL);
const { agent } = useSettings();

useHotkeys(
'esc',
() => {
Expand All @@ -40,27 +37,8 @@ export function ValueForm({ resource, propertyURL, datatype }: ValueFormProps) {
enableOnTags: ['INPUT', 'TEXTAREA', 'SELECT'],
},
);
const [err, setErr] = useState<Error | undefined>(undefined);
const haveAgent = agent !== undefined;

function handleCancel() {
setErr(undefined);
setEditMode(false);
// Should this maybe also remove the edits to the resource?
// https://github.com/atomicdata-dev/atomic-data-browser/issues/36
}

async function handleSave() {
try {
await resource.save();
setEditMode(false);
toast.success('Resource saved');
} catch (e) {
setErr(e);
setEditMode(true);
toast.error('Could not save resource...');
}
}
const hasAgent = agent !== undefined;

if (value === undefined) {
return null;
Expand All @@ -74,41 +52,21 @@ export function ValueForm({ resource, propertyURL, datatype }: ValueFormProps) {
return (
<ValueFormWrapper>
<ValueComp value={value} datatype={datatype || property.datatype} />
<EditButton title='Edit value'>
<FaEdit onClick={() => setEditMode(!editMode)} />
</EditButton>
{hasAgent && (
<EditButton title='Edit value'>
<FaEdit onClick={() => setEditMode(!editMode)} />
</EditButton>
)}
</ValueFormWrapper>
);
}

return (
<ValueFormWrapper>
<Column gap='0.5rem'>
<InputSwitcher
data-test={`input-${property.subject}`}
resource={resource}
property={property}
autoFocus
/>
{err && <ErrMessage>{err.message}</ErrMessage>}
<Row gap='0.5rem'>
<Button subtle onClick={handleCancel}>
cancel
</Button>
<Button
disabled={!haveAgent}
title={
haveAgent
? 'Save the edits'
: 'You cannot save - there is no Agent set. Go to settings.'
}
onClick={handleSave}
>
save
</Button>
</Row>
</Column>
</ValueFormWrapper>
<ValueFormEdit
resource={resource}
property={property}
onClose={() => setEditMode(false)}
/>
);
}

Expand Down
Loading

0 comments on commit 9c5ac27

Please sign in to comment.