Skip to content

Commit

Permalink
feat: adapt-essentials-asset-fields search and bugfixes (#1828)
Browse files Browse the repository at this point in the history
  • Loading branch information
zzzarius committed May 24, 2024
1 parent 6aa2ad3 commit 5d60616
Show file tree
Hide file tree
Showing 21 changed files with 485 additions and 91 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { Button, Flex, Select, Stack, TextInput } from '@contentful/f36-components';
import { FilterIcon, PlusIcon } from '@contentful/f36-icons';
import { css } from 'emotion';
import tokens from '@contentful/f36-tokens';
import { useState } from 'react';

export function AdvancedSearch() {
const [searchTerm, setSearchTerm] = useState('');

const styles = {
searchWrapper: css({
border: `1px solid ${tokens.gray200}`,
borderRadius: '0.3rem',
}),
searchInput: css({
border: 'none',
}),
searchInputFilter: css({
border: 'none',
fontSize: '0.8rem',
color: tokens.blue600,
'&:hover': {
color: tokens.blue600,
},
}),
icon: css({
border: `1px solid ${tokens.gray600}`,
borderRadius: '50%',
}),
filterButton: css({
border: `1px dashed ${tokens.gray600}`,
borderRadius: '0.8rem',
padding: '0.2rem 0.5rem 0.2rem 0.2rem',
minHeight: '0',
fontSize: '0.8rem',
color: tokens.gray600,
backgroundColor: tokens.gray100,
}),
filters: css({
marginTop: '0.3rem',
}),
};

return (
<Flex flexDirection="column">
<TextInput.Group className={styles.searchWrapper}>
<Flex gap="1rem">
<Flex>
<Flex alignItems="center">filterName</Flex>
<Select id="optionSelect-controlled" name="optionSelect-controlled" value={''}>
<Select.Option value="optionOne">Option 1</Select.Option>
<Select.Option value="optionTwo">Long Option 2</Select.Option>
</Select>
<Select id="optionSelect-controlled" name="optionSelect-controlled" value={''}>
<Select.Option value="optionOne">Option 1</Select.Option>
<Select.Option value="optionTwo">Long Option 2</Select.Option>
</Select>
</Flex>
<Flex>
<Flex alignItems="center">filterName</Flex>
<Select id="optionSelect-controlled" name="optionSelect-controlled" value={''}>
<Select.Option value="optionOne">Option 1</Select.Option>
<Select.Option value="optionTwo">Long Option 2</Select.Option>
</Select>
<Select id="optionSelect-controlled" name="optionSelect-controlled" value={''}>
<Select.Option value="optionOne">Option 1</Select.Option>
<Select.Option value="optionTwo">Long Option 2</Select.Option>
</Select>
</Flex>
</Flex>
<TextInput
id="search"
name="search"
className={styles.searchInput}
placeholder="Type to search for assets"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<Button variant="secondary" className={styles.searchInputFilter} startIcon={<FilterIcon />} onClick={() => {}} aria-label="Filter">
Filter
</Button>
</TextInput.Group>
<Stack className={styles.filters}>
<Button variant="secondary" size="small" className={styles.filterButton} startIcon={<PlusIcon className={styles.icon} />}>
Created by me
</Button>
<Button variant="secondary" className={styles.filterButton} startIcon={<PlusIcon className={styles.icon} />}>
Status is
</Button>
<Button variant="secondary" className={styles.filterButton} startIcon={<PlusIcon className={styles.icon} />}>
Tag is one of
</Button>
</Stack>
</Flex>
);
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { useDebounce } from '@uidotdev/usehooks';
import { useState, useEffect } from 'react';
import { useCMA, useSDK } from '@contentful/react-apps-toolkit';
import { useSDK } from '@contentful/react-apps-toolkit';
import { useCMA } from './hooks/useCMA';
import { PageAppSDK } from '@contentful/app-sdk';
import { TextInput, Textarea, FormControl, Caption } from '@contentful/f36-components';
import { AssetProps } from 'contentful-management/dist/typings/entities/asset';
import { extractContentfulFieldError } from './utils/entries.ts';
import useLocales from './hooks/useLocales.tsx';
import useAssetEntries from './hooks/useAssetEntries.tsx';
import styles from './styles.module.css';

interface AssetInputFieldTextComponentProps {
asset: AssetProps;
Expand All @@ -15,9 +17,12 @@ interface AssetInputFieldTextComponentProps {
rows?: number;
as?: 'TextInput' | 'Textarea';
showLocaleLabel?: boolean;
isDisabled?: boolean;
}

const AssetInputFieldTextComponent = ({ asset, field, locale, as = 'TextInput', rows = 1, showLocaleLabel = false }: AssetInputFieldTextComponentProps) => {
const AssetInputFieldTextComponent = ({
asset, field, locale, as = 'TextInput', rows = 1, showLocaleLabel = false, isDisabled = false
}: AssetInputFieldTextComponentProps) => {
const fieldValueProp = asset.fields[field]?.[locale] ?? asset.fields.file?.[locale]?.[field] ?? '';
const assetId = asset.sys.id;
const { updateAssetEntry } = useAssetEntries();
Expand Down Expand Up @@ -82,15 +87,23 @@ const AssetInputFieldTextComponent = ({ asset, field, locale, as = 'TextInput',
}, [debouncedFieldValue]);

const { localeNames } = useLocales();
const InputComponent = as === 'Textarea' ? Textarea : TextInput;
const isTextarea = as === 'Textarea';
const InputComponent = isTextarea ? Textarea : TextInput;
const inputChangeHandler = (event) => {
setNewFieldValue(event.target.value);
};

return (
<>
{showLocaleLabel && <Caption>{localeNames[locale]}</Caption>}
<InputComponent key={`${assetId}-${locale}`} value={newFieldValue} onChange={inputChangeHandler} rows={rows} />
<InputComponent
key={`${assetId}-${locale}`}
value={newFieldValue}
onChange={inputChangeHandler}
rows={rows}
isDisabled={isDisabled}
className={isTextarea ? styles.textarea : styles.textInput}
/>
{error && <FormControl.ValidationMessage>{error}</FormControl.ValidationMessage>}
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { EntryStatus } from './EntryStatus';
import useLocales from './hooks/useLocales';
import { AvailableColumns } from './hooks/useColumns';
import useUser from './hooks/useUser';
import styles from './styles.module.css';

interface BodyInputCellResolverProps {
column: AvailableColumns;
Expand All @@ -30,8 +31,8 @@ export const BodyInputCellResolver = ({ column, asset, loading = false, ...rest

if (loading && column !== 'status')
return (
<TableCell key={column} {...rest}>
<SkeletonContainer style={{ height: '60px' }}>
<TableCell key={column} {...rest} className={[styles.cellSkeleton, rest.className].filter(Boolean).join(' ')}>
<SkeletonContainer className={styles.cellSkeletonContainer}>
<SkeletonBodyText numberOfLines={2} />
</SkeletonContainer>
</TableCell>
Expand All @@ -53,7 +54,12 @@ export const BodyInputCellResolver = ({ column, asset, loading = false, ...rest
case 'filename':
return (
<TableCell key={column} {...rest}>
<AssetInputFieldText asset={asset} locales={enabledLocales} field={'fileName'} />
<AssetInputFieldText
asset={asset}
locales={enabledLocales.filter((locale) => asset.fields?.file?.[locale]?.fileName)}
field={'fileName'}
isDisabled={true}
/>
</TableCell>
);
case 'createdAt':
Expand All @@ -73,7 +79,13 @@ export const BodyInputCellResolver = ({ column, asset, loading = false, ...rest
<TableCell key={column} {...rest}>
<Flex gap="spacingXs" alignItems="center">
{user?.avatarUrl && (
<Image src={user.avatarUrl} height="25px" width="25px" style={{ borderRadius: '50%' }} alt={`${user.firstName} ${user.lastName}`} />
<Image
className={styles.avatar}
src={user.avatarUrl}
height="24px"
width="24px"
alt={`${user.firstName} ${user.lastName}`}
/>
)}
{user?.firstName && user?.lastName && (
<Text fontColor="gray900">
Expand All @@ -85,10 +97,10 @@ export const BodyInputCellResolver = ({ column, asset, loading = false, ...rest
);
case 'status':
return (
<TableCell key={column} style={{ width: '50px', maxWidth: '50px' }} {...rest}>
<TableCell key={column} className={styles.statusCell} {...rest}>
{!loading && <EntryStatus sys={asset.sys} />}
{loading && (
<SkeletonContainer style={{ height: '60px' }}>
<SkeletonContainer className={styles.statusSkeleton}>
<SkeletonDisplayText />
</SkeletonContainer>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import TableBody from './TableBody';
import VisibleColumnsCell from './VisibleColumnsCell';
import Paginator from './Paginator';
import useUsers from './hooks/useUsers';
import styles from './styles.module.css';

export default function Dashboard() {
useUsers();
Expand All @@ -16,8 +17,8 @@ export default function Dashboard() {
<Box marginTop="spacingXl">
<Table>
<Table.Head>
<Table.Row style={{ position: 'sticky', top: '-35px', zIndex: '1' }}>
<Table.Cell style={{ verticalAlign: 'middle' }}>
<Table.Row className={styles.dashboardRow}>
<Table.Cell className={styles.dashboardCheckboxCell}>
<SelectAllCheckbox />
</Table.Cell>
<VisibleColumnsCell />
Expand Down
10 changes: 7 additions & 3 deletions apps/adapt-essentials-asset-fields/src/components/Paginator.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Pagination } from '@contentful/f36-components';
import { useCMA } from '@contentful/react-apps-toolkit';
import { useEffect } from 'react';
import useEntriesSelection from './hooks/useEntriesSelection';
import useAssetEntries from './hooks/useAssetEntries';
Expand All @@ -9,8 +8,11 @@ import useActivePage from './hooks/useActivePage';
import useSkip from './hooks/useSkip';
import useOrder from './hooks/useOrder';
import useLimit from './hooks/useLimit';
import { useCMA } from './hooks/useCMA';
import useQuery from './hooks/useQuery';

const Paginator = () => {
const cma = useCMA();
const { setIsLoading } = useEntriesLoading();
const { total, setTotal } = useTotal();
const { activePage, setActivePage } = useActivePage();
Expand All @@ -20,7 +22,7 @@ const Paginator = () => {
const { assetEntries, setAssetEntries } = useAssetEntries();
const { setSelectedEntries } = useEntriesSelection();
const { selectedEntries } = useEntriesSelection();
const cma = useCMA();
const { query } = useQuery();

useEffect(() => {
setIsLoading(true);
Expand All @@ -36,6 +38,7 @@ const Paginator = () => {
skip,
limit,
order,
query,
},
});
setTotal(assetResponse.total);
Expand All @@ -44,7 +47,7 @@ const Paginator = () => {
}
fetchData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [cma.asset, setAssetEntries, skip, limit, order]);
}, [cma.asset, setAssetEntries, skip, limit, order, query]);

const pageChangeHandler = (activePage) => {
setActivePage(activePage);
Expand All @@ -54,6 +57,7 @@ const Paginator = () => {
return (
selectedEntries.length < 1 && (
<Pagination
key={query}
activePage={activePage}
onPageChange={pageChangeHandler}
isLastPage={total <= activePage * limit}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import useAssetEntries from './hooks/useAssetEntries';
const SelectAllCheckbox = () => {
const { assetEntries } = useAssetEntries();
const { selectedEntries, setSelectedEntries } = useEntriesSelection();
const selectedAll = selectedEntries.length === assetEntries.length;
const selectedAll = selectedEntries.length > 0 && selectedEntries.length === assetEntries.length;
return (
<Checkbox
isChecked={selectedAll}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import useEntriesSelection from './hooks/useEntriesSelection';
import useColumns from './hooks/useColumns';
import useAssetEntries from './hooks/useAssetEntries';
import { EntryStatus, getEntryStatus } from './utils/entries';
import { useCMA, useSDK } from '@contentful/react-apps-toolkit';
import { useSDK } from '@contentful/react-apps-toolkit';
import { useCMA } from './hooks/useCMA';
import { AssetProps } from 'contentful-management';

const SelectionControlsTableRow = () => {
Expand Down Expand Up @@ -33,12 +34,13 @@ const SelectionControlsTableRow = () => {

const republishableAssets = useMemo(() => {
return selectedAssets.filter(
(assetEntry) => getEntryStatus(assetEntry.sys) === EntryStatus.CHANGED || getEntryStatus(assetEntry.sys) === EntryStatus.DRAFT,
(assetEntry) =>
getEntryStatus(assetEntry.sys) === EntryStatus.CHANGED || (getEntryStatus(assetEntry.sys) === EntryStatus.DRAFT && assetEntry.fields.file),
);
}, [selectedAssets]);

const publishableAssets = useMemo(() => {
return selectedAssets.filter((assetEntry) => getEntryStatus(assetEntry.sys) === EntryStatus.DRAFT);
return selectedAssets.filter((assetEntry) => getEntryStatus(assetEntry.sys) === EntryStatus.DRAFT && assetEntry.fields.file);
}, [selectedAssets]);

const archivableAssets = useMemo(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useState } from 'react';
import useLocales from './hooks/useLocales';
import useColumns from './hooks/useColumns';
import useLimit from './hooks/useLimit';
import styles from './styles.module.css';

export const SettingsPopover = () => {
const [showSettingsPopup, setShowSettingsPopup] = useState(false);
Expand All @@ -13,7 +14,8 @@ export const SettingsPopover = () => {
const { limit, setLimit } = useLimit();

const handlePageSizeChange = (event) => {
setLimit(Number(event.target.value));
const value = Number(event.target.value);
setLimit(value);
};

return (
Expand All @@ -30,7 +32,7 @@ export const SettingsPopover = () => {
<Popover.Content>
<FocusLock>
<Stack padding="spacingM" margin="none" spacing="spacingS" flexDirection="column" paddingBottom="none">
<Box style={{ alignSelf: 'flex-start', fontWeight: '600' }}>Columns</Box>
<Box className={styles.settingsPopoverLabel}>Columns</Box>
{columns.map((column) => {
const { label, isVisible } = columnDetails[column];
return (
Expand All @@ -47,15 +49,9 @@ export const SettingsPopover = () => {
})}
</Stack>
<Stack padding="spacingM" margin="none" spacing="spacingS" flexDirection="column">
<Box style={{ alignSelf: 'flex-start', fontWeight: '600' }}>Locales</Box>
<Box className={styles.settingsPopoverLabel}>Locales</Box>
{locales.map((locale) => (
<Flex
key={locale}
fullWidth
style={{
display: 'flex',
justifyContent: 'flex-start',
}}>
<Flex key={locale} fullWidth className={styles.localeSwitchWrapper}>
<Switch
size="small"
isChecked={enabledLocales.includes(locale)}
Expand All @@ -65,11 +61,9 @@ export const SettingsPopover = () => {
</Switch>
</Flex>
))}
<Box className={styles.settingsPopoverLabel}>Page size</Box>
<TextInput type="number" min={5} max={100} className={styles.pageSizeInput} value={String(limit)} onChange={handlePageSizeChange} />
</Stack>
<Box padding="spacingM" margin="none" flexDirection="column">
<Box marginBottom="spacingS">Page size</Box>
<TextInput type="number" min={5} max={100} style={{ maxWidth: 'fit-content' }} value={String(limit)} onChange={handlePageSizeChange} />
</Box>
</FocusLock>
</Popover.Content>
</Popover>
Expand Down

0 comments on commit 5d60616

Please sign in to comment.