diff --git a/.eslintrc b/.eslintrc
index f1b1cc9e..a7e5165c 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -39,6 +39,7 @@
"curly": "error",
"@typescript-eslint/indent": "off",
"@typescript-eslint/brace-style": "off",
+ "import/prefer-default-export": "off",
"no-underscore-dangle": [
"error",
{
diff --git a/package.json b/package.json
index 01aae47f..0f8cb0c6 100644
--- a/package.json
+++ b/package.json
@@ -34,7 +34,7 @@
"@emotion/server": "11.11.0",
"@emotion/styled": "11.13.0",
"@graasp/query-client": "5.0.0",
- "@graasp/sdk": "4.32.1",
+ "@graasp/sdk": "4.33.0",
"@graasp/stylis-plugin-rtl": "2.2.0",
"@graasp/translations": "1.40.0",
"@graasp/ui": "5.4.0",
diff --git a/src/components/collection/Collection.tsx b/src/components/collection/Collection.tsx
index 3b785dff..93082dfc 100644
--- a/src/components/collection/Collection.tsx
+++ b/src/components/collection/Collection.tsx
@@ -4,7 +4,7 @@ import { validate } from 'uuid';
import { useContext, useEffect } from 'react';
-import { Box } from '@mui/material';
+import { Box, Skeleton } from '@mui/material';
import {
AccountType,
@@ -27,11 +27,7 @@ type Props = {
};
const Collection = ({ id }: Props) => {
const { hooks, mutations } = useContext(QueryClientContext);
- const {
- data: collection,
- isLoading: isLoadingItem,
- isError,
- } = hooks.useItem(id);
+ const { data: collection, isLoading: isLoadingItem } = hooks.useItem(id);
const { data: currentMember } = hooks.useCurrentMember();
// get item published
const {
@@ -68,47 +64,50 @@ const Collection = ({ id }: Props) => {
);
}
- if (isError) {
+ if (currentMember?.type === AccountType.Guest) {
+ return null;
+ }
+ if (collection) {
return (
-
-
-
+ <>
+
+
+
+
+ >
);
}
- if (currentMember?.type === AccountType.Guest) {
- return null;
+ if (isLoadingItem) {
+ return ;
}
return (
- <>
-
-
-
-
- >
+
+
+
);
};
diff --git a/src/components/collection/summary/Summary.tsx b/src/components/collection/summary/Summary.tsx
index df9ee5df..3c5ececa 100644
--- a/src/components/collection/summary/Summary.tsx
+++ b/src/components/collection/summary/Summary.tsx
@@ -16,7 +16,7 @@ import SummaryDetails from './SummaryDetails';
import SummaryHeader from './SummaryHeader';
type SummaryProps = {
- collection?: DiscriminatedItem;
+ collection: DiscriminatedItem;
publishedRoot?: ItemPublished | null;
isLoading: boolean;
totalViews: number;
diff --git a/src/components/collection/summary/SummaryDetails.tsx b/src/components/collection/summary/SummaryDetails.tsx
index 1e00fca3..6c3e6d63 100644
--- a/src/components/collection/summary/SummaryDetails.tsx
+++ b/src/components/collection/summary/SummaryDetails.tsx
@@ -18,6 +18,7 @@ import {
DiscriminatedItem,
formatDate,
} from '@graasp/sdk';
+import { DEFAULT_LANG, langs } from '@graasp/translations';
import { CATEGORY_COLORS, UrlSearch } from '../../../config/constants';
import {
@@ -90,7 +91,7 @@ const CategoryDisplay = ({
};
type SummaryDetailsProps = {
- collection?: DiscriminatedItem;
+ collection: DiscriminatedItem;
publishedRootItem?: DiscriminatedItem;
lang: string;
isLoading: boolean;
@@ -126,10 +127,11 @@ const SummaryDetails: React.FC = ({
?.filter((c) => c.category.type === CategoryType.Discipline)
?.map((c) => c.category);
- // TODO: should use item language
- const languages = itemCategories
- ?.filter((c) => c.category.type === CategoryType.Language)
- ?.map((c) => c.category);
+ let langValue = langs[DEFAULT_LANG];
+ if (collection.lang in langs) {
+ // @ts-ignore
+ langValue = langs[collection.lang];
+ }
return (
= ({
- {t(LIBRARY.COLLECTION_LANGUAGES_TITLE)}
+ {t(LIBRARY.COLLECTION_LANGUAGE_TITLE)}
- {languages ? (
-
- ) : (
-
- )}
+
diff --git a/src/components/filters/CategoryFilter.tsx b/src/components/filters/CategoryFilter.tsx
new file mode 100644
index 00000000..7bfafa61
--- /dev/null
+++ b/src/components/filters/CategoryFilter.tsx
@@ -0,0 +1,43 @@
+import React from 'react';
+
+import { Category } from '@graasp/sdk';
+
+import { useCategoriesTranslation } from '../../config/i18n';
+import { buildSearchFilterCategoryId } from '../../config/selectors';
+import { Filter } from './Filter';
+
+type FilterProps = {
+ category: string;
+ title: string;
+ options?: Category[];
+ // IDs of selected options.
+ selectedOptions: string[];
+ onOptionChange: (key: string, newValue: boolean) => void;
+ onClearOptions: () => void;
+ isLoading: boolean;
+};
+
+// eslint-disable-next-line react/function-component-definition
+export function CategoryFilter({
+ category,
+ title,
+ onOptionChange,
+ onClearOptions,
+ options,
+ selectedOptions,
+ isLoading,
+}: FilterProps) {
+ const { t: translateCategories } = useCategoriesTranslation();
+
+ return (
+ [c.id, translateCategories(c.name)])}
+ selectedOptions={selectedOptions}
+ onOptionChange={onOptionChange}
+ onClearOptions={onClearOptions}
+ />
+ );
+}
diff --git a/src/components/filters/Filter.tsx b/src/components/filters/Filter.tsx
new file mode 100644
index 00000000..5f6a3e79
--- /dev/null
+++ b/src/components/filters/Filter.tsx
@@ -0,0 +1,136 @@
+import { useEffect, useRef, useState } from 'react';
+
+import { ExpandMoreRounded } from '@mui/icons-material';
+import { Box, Button, Skeleton, Stack, Typography } from '@mui/material';
+
+import { GRAASP_COLOR } from '../../config/constants';
+import { useLibraryTranslation } from '../../config/i18n';
+import { buildSearchFilterPopperButtonId } from '../../config/selectors';
+import LIBRARY from '../../langs/constants';
+import { FilterPopper, FilterPopperProps } from './FilterPopper';
+
+type FilterProps = {
+ title: string;
+ // IDs of selected options.
+ selectedOptions: string[];
+ isLoading?: boolean;
+ onClearOptions: FilterPopperProps['onClearOptions'];
+ onOptionChange: FilterPopperProps['onOptionChange'];
+ id: string;
+ options?: FilterPopperProps['options'];
+};
+
+// eslint-disable-next-line import/prefer-default-export
+export const Filter = ({
+ title,
+ selectedOptions,
+ isLoading,
+ onClearOptions,
+ onOptionChange,
+ id,
+ options,
+}: FilterProps) => {
+ const { t } = useLibraryTranslation();
+ const [showPopper, setShowPopper] = useState(false);
+ const togglePopper = () => {
+ setShowPopper((oldVal) => !oldVal);
+ };
+
+ const popperAnchor = useRef(null);
+ const popper = useRef(null);
+
+ const onDocumentScrolled = () => {
+ setShowPopper(() => false);
+ };
+
+ const onDocumentClicked = (event: MouseEvent) => {
+ if (
+ !popper.current?.contains(event.target as Node) &&
+ !popperAnchor.current?.contains(event.target as Node)
+ ) {
+ setShowPopper(() => false);
+ }
+ };
+ // Listens for clicks outside of the popper to dismiss it when we click outside.
+ useEffect(() => {
+ if (showPopper) {
+ document.addEventListener('click', onDocumentClicked);
+ document.addEventListener('scroll', onDocumentScrolled);
+ }
+ return () => {
+ document.removeEventListener('click', onDocumentClicked);
+ document.removeEventListener('scroll', onDocumentScrolled);
+ };
+ }, [showPopper]);
+
+ const content = isLoading ? (
+
+ ) : (
+ }
+ sx={{
+ textTransform: 'none',
+ alignItems: 'center',
+ paddingRight: 3,
+ justifyContent: 'space-between',
+ }}
+ >
+
+ {options?.find((o) => o[0] === selectedOptions[0])?.[1] ??
+ t(LIBRARY.FILTER_DROPDOWN_NO_FILTER)}
+
+
+ {selectedOptions.length > 1 && (
+
+ {`+${selectedOptions.length - 1}`}
+
+ )}
+
+ );
+
+ return (
+
+
+ {title}
+
+
+ {content}
+
+
+
+ );
+};
diff --git a/src/components/filters/FilterHeader.tsx b/src/components/filters/FilterHeader.tsx
index a986db0c..5056e9b7 100644
--- a/src/components/filters/FilterHeader.tsx
+++ b/src/components/filters/FilterHeader.tsx
@@ -2,23 +2,19 @@ import groupBy from 'lodash.groupby';
import React, { FC, useContext, useEffect, useRef, useState } from 'react';
-import { ExpandMoreRounded } from '@mui/icons-material';
import {
Box,
- Button,
Checkbox,
Container,
Divider,
FormControlLabel,
- Skeleton,
Stack,
Typography,
styled,
} from '@mui/material';
-import { Category, CategoryType } from '@graasp/sdk';
+import { CategoryType } from '@graasp/sdk';
-import { GRAASP_COLOR } from '../../config/constants';
import {
useCategoriesTranslation,
useLibraryTranslation,
@@ -26,155 +22,12 @@ import {
import {
ALL_COLLECTIONS_TITLE_ID,
ENABLE_IN_DEPTH_SEARCH_CHECKBOX_ID,
- buildSearchFilterCategoryId,
- buildSearchFilterPopperButtonId,
} from '../../config/selectors';
import LIBRARY from '../../langs/constants';
import { QueryClientContext } from '../QueryClientContext';
import Search from '../search/Search';
-import FilterPopper from './FilterPopper';
-
-type FilterProps = {
- category: string;
- title: string;
- options?: Category[];
- // IDs of selected options.
- selectedOptions: string[];
- onOptionChange: (key: string, newValue: boolean) => void;
- onClearOptions: () => void;
- isLoading: boolean;
-};
-
-const Filter: React.FC = ({
- category,
- title,
- onOptionChange,
- onClearOptions,
- options,
- selectedOptions,
- isLoading,
-}) => {
- const { t: translateCategories } = useCategoriesTranslation();
- const { t } = useLibraryTranslation();
- const [showPopper, setShowPopper] = useState(false);
- const togglePopper = () => {
- setShowPopper((oldVal) => !oldVal);
- };
-
- const popperAnchor = useRef(null);
- const popper = useRef(null);
-
- const onDocumentScrolled = () => {
- setShowPopper(() => false);
- };
-
- const onDocumentClicked = (event: MouseEvent) => {
- if (
- !popper.current?.contains(event.target as Node) &&
- !popperAnchor.current?.contains(event.target as Node)
- ) {
- setShowPopper(() => false);
- }
- };
-
- const selectionCount = React.useMemo(
- () =>
- selectedOptions.filter((id) => options?.find((opt) => opt.id === id))
- .length,
- [selectedOptions, options],
- );
-
- const selectionStr = React.useMemo(() => {
- const optionsStr =
- options
- ?.filter((it) => selectedOptions.includes(it.id))
- .map((it) => translateCategories(it.name))?.[0] ??
- t(LIBRARY.FILTER_DROPDOWN_NO_FILTER);
- return optionsStr;
- }, [selectedOptions, options]);
-
- // Listens for clicks outside of the popper to dismiss it when we click outside.
- useEffect(() => {
- if (showPopper) {
- document.addEventListener('click', onDocumentClicked);
- document.addEventListener('scroll', onDocumentScrolled);
- }
- return () => {
- document.removeEventListener('click', onDocumentClicked);
- document.removeEventListener('scroll', onDocumentScrolled);
- };
- }, [showPopper]);
-
- const content = isLoading ? (
-
- ) : (
- }
- sx={{
- textTransform: 'none',
- alignItems: 'center',
- paddingRight: 3,
- justifyContent: 'space-between',
- }}
- >
-
- {selectionStr}
-
-
- {selectionCount > 1 && (
-
- {`+${selectionCount - 1}`}
-
- )}
-
- );
-
- return (
-
-
- {title}
-
-
- {content}
-
-
-
- );
-};
+import { CategoryFilter } from './CategoryFilter';
+import { LangFilter } from './LangFilter';
const StyledFilterContainer = styled(Stack)(() => ({
backgroundColor: 'white',
@@ -212,17 +65,21 @@ type FilterHeaderProps = {
searchPreset?: string;
categoryPreset?: string[][];
isLoadingResults: boolean;
+ setLangs: (langs: string[]) => void;
+ langs: string[];
};
const FilterHeader: FC = ({
onFiltersChanged,
onChangeSearch,
+ setLangs,
onSearch,
searchPreset,
categoryPreset,
isLoadingResults,
onIncludeContentChange,
shouldIncludeContent,
+ langs,
}) => {
const { t: translateCategories } = useCategoriesTranslation();
const { t } = useLibraryTranslation();
@@ -239,26 +96,6 @@ const FilterHeader: FC = ({
const allCategories = groupBy(categories, (entry) => entry.type);
const levelList = allCategories[CategoryType.Level];
const disciplineList = allCategories[CategoryType.Discipline];
- const languageList = allCategories[CategoryType.Language];
-
- // TODO: Replace with real values.
- // const licenseList: List = convertJs([
- // {
- // id: '3f811e5f-5221-4d22-a20c-1086af809bda',
- // name: 'Public Domain (CC0)',
- // type: '3f811e5f-5221-4d22-a20c-1086af809bd0',
- // },
- // {
- // id: '3f811e5f-5221-4d22-a20c-1086af809bdb',
- // name: 'For Commercial Use',
- // type: '3f811e5f-5221-4d22-a20c-1086af809bd0',
- // },
- // {
- // id: '3f811e5f-5221-4d22-a20c-1086af809bdc',
- // name: 'Derivable',
- // type: '3f811e5f-5221-4d22-a20c-1086af809bd0',
- // },
- // ]);
useEffect(() => {
setSelectedFilters(categoryPreset ? categoryPreset.flat() : []);
@@ -322,47 +159,40 @@ const FilterHeader: FC = ({
/>
);
+ const selectedDisciplineOptions = selectedFilters.filter((id) =>
+ disciplineList?.find((opt) => opt.id === id),
+ );
+ const selectedLevelOptions = selectedFilters.filter((id) =>
+ levelList?.find((opt) => opt.id === id),
+ );
+
const filters = [
- onClearCategory(levelList?.map((l) => l.id))}
isLoading={isCategoriesLoading}
/>,
- onClearCategory(disciplineList?.map((d) => d.id))}
isLoading={isCategoriesLoading}
/>,
- onClearCategory(languageList?.map((d) => d.id))}
- isLoading={isCategoriesLoading}
+ selectedOptions={langs}
+ setLangs={setLangs}
/>,
- // onClearCategory(licenseList?.map((d) => d.id))}
- // isLoading={isCategoriesLoading}
- // />,
];
return (
diff --git a/src/components/filters/FilterPopper.tsx b/src/components/filters/FilterPopper.tsx
index b1f7d3f5..4149ea86 100644
--- a/src/components/filters/FilterPopper.tsx
+++ b/src/components/filters/FilterPopper.tsx
@@ -8,17 +8,11 @@ import {
Grow,
Popper,
Stack,
- Typography,
styled,
} from '@mui/material';
import { TransitionProps as MUITransitionProps } from '@mui/material/transitions';
-import { Category } from '@graasp/sdk';
-
-import {
- useCategoriesTranslation,
- useLibraryTranslation,
-} from '../../config/i18n';
+import { useLibraryTranslation } from '../../config/i18n';
import {
CLEAR_FILTER_POPPER_BUTTON_ID,
FILTER_POPPER_ID,
@@ -35,29 +29,28 @@ const StyledPopper = styled(Stack)(() => ({
boxShadow: '0 2px 15px rgba(0, 0, 0, 0.08)',
}));
-type FilterPopperProps = {
+export type FilterPopperProps = {
open: boolean;
anchorEl: HTMLElement | null;
- options?: Category[];
+ options?: [k: string, v: string][];
// IDs of selected options.
selectedOptions: string[];
onOptionChange: (id: string, newSelected: boolean) => void;
onClearOptions: () => void;
};
-const FilterPopper = React.forwardRef(
+export const FilterPopper = React.forwardRef(
(
{
+ options,
anchorEl,
onOptionChange,
open,
- options,
selectedOptions,
onClearOptions,
},
ref,
) => {
- const { t: translateCategories } = useCategoriesTranslation();
const { t } = useLibraryTranslation();
return (
(
// eslint-disable-next-line react/jsx-props-no-spreading
- {options
- ?.map((c) => ({ ...c, name: translateCategories(c.name) }))
- ?.sort(compare)
- .map((option, idx) => {
- const isSelected = selectedOptions.includes(option.id);
- return (
-
-
-
- onOptionChange(option.id, !isSelected)
- }
- />
- }
- label={option.name}
- labelPlacement="end"
- />
-
-
- );
- }) || (
-
- {t(LIBRARY.FILTER_DROPDOWN_NO_CATEGORIES_AVAILABLE)}
-
- )}
+ {options?.sort(compare).map(([k, v], idx) => {
+ const isSelected = selectedOptions.includes(k);
+ return (
+
+
+ onOptionChange(k, !isSelected)}
+ />
+ }
+ label={v}
+ labelPlacement="end"
+ />
+
+
+ );
+ })}