Skip to content

Commit

Permalink
perf(client): extract heavy parts of add tag form
Browse files Browse the repository at this point in the history
  • Loading branch information
neopostmodern committed Apr 30, 2023
1 parent 76b61bb commit a37b0f8
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 89 deletions.
67 changes: 67 additions & 0 deletions client/src/renderer/components/AddTagButtonOrForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { AddCircleOutline as PlusIcon } from '@mui/icons-material';
import { Button, IconButton } from '@mui/material';
import { INTERNAL_TAG_PREFIX } from '@structure/common';
import { useCallback, useState } from 'react';
import useShortcut from '../hooks/useShortcut';
import { SHORTCUTS } from '../utils/keyboard';
import AddTagForm, { AddTagFormProps } from './AddTagForm';
import TooltipWithShortcut from './TooltipWithShortcut';

const AddTagButtonOrForm = ({
currentTags,
withShortcuts,
...tagFormProps
}: AddTagFormProps & {
withShortcuts?: boolean;
}) => {
const [addingNew, setAddingNew] = useState<boolean>(false);

const showNewTagForm = useCallback((): void => {
setAddingNew(true);
}, [setAddingNew]);

const hideNewTagForm = useCallback((): void => {
setAddingNew(false);
}, [setAddingNew]);

useShortcut(withShortcuts ? SHORTCUTS.ADD_TAG : null, showNewTagForm, true);

if (addingNew) {
return (
<AddTagForm
currentTags={currentTags}
onHideTagForm={hideNewTagForm}
{...tagFormProps}
/>
);
}

if (
currentTags.filter((tag) => !tag.name.startsWith(INTERNAL_TAG_PREFIX))
.length === 0
) {
return (
<TooltipWithShortcut
title=""
shortcut={withShortcuts ? SHORTCUTS.ADD_TAG : undefined}
>
<Button variant="outlined" size="small" onClick={showNewTagForm}>
Add tag
</Button>
</TooltipWithShortcut>
);
}

return (
<TooltipWithShortcut
title="Add tag"
shortcut={withShortcuts ? SHORTCUTS.ADD_TAG : undefined}
>
<IconButton size="small" onClick={showNewTagForm}>
<PlusIcon />
</IconButton>
</TooltipWithShortcut>
);
};

export default AddTagButtonOrForm;
119 changes: 33 additions & 86 deletions client/src/renderer/components/AddTagForm.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { gql, useMutation, useQuery } from '@apollo/client';
import {
AddCircleOutline as PlusIcon,
Warning as WarningIcon,
} from '@mui/icons-material';
import { Button, IconButton, Skeleton, Tooltip } from '@mui/material';
import { Warning as WarningIcon } from '@mui/icons-material';
import { Skeleton, Tooltip } from '@mui/material';
import { INTERNAL_TAG_PREFIX } from '@structure/common';
import { useState } from 'react';
import { TAGS_QUERY } from '../containers/TagsPage';
Expand All @@ -13,10 +10,7 @@ import {
TagsQuery,
TagsWithCountsQuery,
} from '../generated/graphql';
import useIsOnline from '../hooks/useIsOnline';
import useShortcut from '../hooks/useShortcut';
import useUserId from '../hooks/useUserId';
import { SHORTCUTS } from '../utils/keyboard';
import {
ADD_TAG_BY_NAME_TO_NOTE_MUTATION,
BASE_TAG_FRAGMENT,
Expand All @@ -25,7 +19,6 @@ import { DisplayOnlyTag } from '../utils/types';
import useDataState, { DataState } from '../utils/useDataState';
import ErrorSnackbar from './ErrorSnackbar';
import InlineTagForm from './InlineTagForm';
import TooltipWithShortcut from './TooltipWithShortcut';

export const TAGS_WITH_COUNTS_QUERY = gql`
query TagsWithCounts {
Expand All @@ -37,18 +30,16 @@ export const TAGS_WITH_COUNTS_QUERY = gql`
${BASE_TAG_FRAGMENT}
`;

export type AddTagFormProps = {
noteId: string;
currentTags: Array<DisplayOnlyTag>;
};
const AddTagForm = ({
noteId,
withShortcuts,
currentTags,
}: {
noteId: string;
withShortcuts?: boolean;
currentTags: Array<DisplayOnlyTag>;
}) => {
onHideTagForm,
}: AddTagFormProps & { onHideTagForm: () => void }) => {
const userId = useUserId();

const [addingNew, setAddingNew] = useState<boolean>(false);
const [submittedTag, setSubmittedTag] = useState<string | null>(null);

const tagsQuery = useDataState(
Expand Down Expand Up @@ -85,98 +76,54 @@ const AddTagForm = ({
},
});

const showNewTagForm = (): void => {
setAddingNew(true);
};

const hideNewTagForm = (): void => {
setAddingNew(false);
};

useShortcut(withShortcuts ? SHORTCUTS.ADD_TAG : null, showNewTagForm, true);

const handleAddTag = (tagName: string): void => {
const tagValue = tagName.trim();
if (tagValue.length > 0) {
addTagToNote({ variables: { noteId, tagName: tagValue } });
setSubmittedTag(tagValue);
hideNewTagForm();
onHideTagForm();
} else {
// eslint-disable-next-line no-undef
alert("Can't add empty tag."); // todo: error handling in UI
}
};

const isOnline = useIsOnline();
if (!isOnline) {
return null;
}
if (addTagToNoteMutation.loading) {
return (
<Skeleton variant="text" height={'2.2rem'}>
<div>{submittedTag}</div>
</Skeleton>
);
}
if (addingNew) {
if (tagsQuery.state === DataState.ERROR) {
return (
<>
<ErrorSnackbar
error={tagsQuery.error}
actionDescription="load tags"
/>
<Tooltip title="Failed to load tags">
<WarningIcon color="error" />
</Tooltip>
</>
);
}

return (
<InlineTagForm
tags={
tagsQuery.state === DataState.LOADING
? 'loading'
: tagsQuery.data.tags.filter(
(tag) =>
!currentTags.some(({ _id }) => _id === tag._id) &&
!tag.name.startsWith(INTERNAL_TAG_PREFIX) &&
tag.permissions.some(
(permission) =>
permission.user._id === userId && permission.tag.use
)
)
}
onAddTag={handleAddTag}
onAbort={hideNewTagForm}
/>
);
} else if (
currentTags.filter((tag) => !tag.name.startsWith(INTERNAL_TAG_PREFIX))
.length === 0
) {
if (tagsQuery.state === DataState.ERROR) {
return (
<TooltipWithShortcut
title=""
shortcut={withShortcuts ? SHORTCUTS.ADD_TAG : undefined}
>
<Button variant="outlined" size="small" onClick={showNewTagForm}>
Add tag
</Button>
</TooltipWithShortcut>
<>
<ErrorSnackbar error={tagsQuery.error} actionDescription="load tags" />
<Tooltip title="Failed to load tags">
<WarningIcon color="error" />
</Tooltip>
</>
);
}

return (
<TooltipWithShortcut
title="Add tag"
shortcut={withShortcuts ? SHORTCUTS.ADD_TAG : undefined}
>
<IconButton size="small" onClick={showNewTagForm}>
<PlusIcon />
</IconButton>
</TooltipWithShortcut>
<InlineTagForm
tags={
tagsQuery.state === DataState.LOADING
? 'loading'
: tagsQuery.data.tags.filter(
(tag) =>
!currentTags.some(({ _id }) => _id === tag._id) &&
!tag.name.startsWith(INTERNAL_TAG_PREFIX) &&
tag.permissions.some(
(permission) =>
permission.user._id === userId && permission.tag.use
)
)
}
onAddTag={handleAddTag}
onAbort={onHideTagForm}
/>
);
};

Expand Down
8 changes: 5 additions & 3 deletions client/src/renderer/components/Tags.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import { INTERNAL_TAG_PREFIX } from '@structure/common';
import React from 'react';
import styled from 'styled-components';
import useHasPermission from '../hooks/useHasPermission';
import useIsOnline from '../hooks/useIsOnline';
import { BASE_TAG_FRAGMENT } from '../utils/sharedQueriesAndFragments';
import { DisplayOnlyTag } from '../utils/types';
import AddTagForm from './AddTagForm';
import AddTagButtonOrForm from './AddTagButtonOrForm';
import TagWithContextMenu from './TagWithContextMenu';

export const REMOVE_TAG_MUTATION = gql`
Expand Down Expand Up @@ -54,6 +55,7 @@ const Tags: React.FC<TagsProps> = ({
withShortcuts = false,
}) => {
const readOnly = !useHasPermission({ tags }, 'notes', 'write');
const isOnline = useIsOnline();

return (
<TagContainer>
Expand All @@ -68,8 +70,8 @@ const Tags: React.FC<TagsProps> = ({
noteReadOnly={readOnly}
/>
))}
{!readOnly && (
<AddTagForm
{!readOnly && isOnline && (
<AddTagButtonOrForm
withShortcuts={withShortcuts}
noteId={noteId}
currentTags={tags}
Expand Down

0 comments on commit a37b0f8

Please sign in to comment.