diff --git a/cypress/e2e/item/duplicate/duplicateItem.cy.ts b/cypress/e2e/item/duplicate/duplicateItem.cy.ts index 2f25757b7..c21698af4 100644 --- a/cypress/e2e/item/duplicate/duplicateItem.cy.ts +++ b/cypress/e2e/item/duplicate/duplicateItem.cy.ts @@ -6,7 +6,7 @@ import { SAMPLE_ITEMS } from '../../../fixtures/items'; import duplicateItem from '../../../support/actionsUtils'; describe('duplicate Item in Home', () => { - Object.values(ItemLayoutMode).forEach((view) => { + Object.values([ItemLayoutMode.Grid, ItemLayoutMode.List]).forEach((view) => { it(`duplicate item on Home in ${view} view`, () => { cy.setUpApi(SAMPLE_ITEMS); cy.visit(HOME_PATH); @@ -25,7 +25,7 @@ describe('duplicate Item in Home', () => { }); }); describe('duplicate Item in item', () => { - Object.values(ItemLayoutMode).forEach((view) => { + Object.values([ItemLayoutMode.Grid, ItemLayoutMode.List]).forEach((view) => { it(`duplicate item in item in ${view} view`, () => { cy.setUpApi(SAMPLE_ITEMS); const { id, path } = SAMPLE_ITEMS.items[0]; diff --git a/package.json b/package.json index 5207041d5..be291cb32 100644 --- a/package.json +++ b/package.json @@ -20,10 +20,11 @@ "@emotion/react": "11.11.4", "@emotion/styled": "11.11.0", "@graasp/chatbox": "3.1.0", - "@graasp/query-client": "2.8.0", + "@graasp/map": "1.4.0", + "@graasp/query-client": "2.9.0", "@graasp/sdk": "4.1.0", "@graasp/translations": "1.25.2", - "@graasp/ui": "4.9.0", + "@graasp/ui": "4.9.1", "@mui/icons-material": "5.15.11", "@mui/lab": "5.0.0-alpha.166", "@mui/material": "5.15.11", diff --git a/src/components/item/MapView.tsx b/src/components/item/MapView.tsx new file mode 100644 index 000000000..346d0e5f6 --- /dev/null +++ b/src/components/item/MapView.tsx @@ -0,0 +1,45 @@ +import { Typography } from '@mui/material'; + +import { Map } from '@graasp/map'; +import { type DiscriminatedItem, redirect } from '@graasp/sdk'; + +import { buildGraaspPlayerView } from '@/config/externalPaths'; +import { hooks, mutations } from '@/config/queryClient'; +import { buildPlayerTabName } from '@/config/selectors'; + +type Props = { + parentId?: DiscriminatedItem['id']; + title: string; +}; + +const MapView = ({ parentId, title }: Props): JSX.Element => { + const { data: currentMember } = hooks.useCurrentMember(); + + return ( + <> + + {title} + +
+ { + redirect(window, buildGraaspPlayerView(item.id), { + name: buildPlayerTabName(item.id), + openInNewTab: true, + }); + }} + currentMember={currentMember} + itemId={parentId} + /> +
+ + ); +}; + +export default MapView; diff --git a/src/components/item/header/ModeButton.tsx b/src/components/item/header/ModeButton.tsx index b80fe3863..95dd9b30c 100644 --- a/src/components/item/header/ModeButton.tsx +++ b/src/components/item/header/ModeButton.tsx @@ -4,7 +4,7 @@ import { List as ListIcon, ViewModule as ViewModuleIcon, } from '@mui/icons-material'; -// import MapIcon from '@mui/icons-material/Map'; +import MapIcon from '@mui/icons-material/Map'; import { IconButton } from '@mui/material'; import Menu from '@mui/material/Menu'; import MenuItem from '@mui/material/MenuItem'; @@ -16,8 +16,8 @@ import { useLayoutContext } from '../../context/LayoutContext'; const modeToIcon = (mode: ItemLayoutMode) => { switch (mode) { - // case ItemLayoutMode.Map: - // return ; + case ItemLayoutMode.Map: + return ; case ItemLayoutMode.Grid: return ; case ItemLayoutMode.List: diff --git a/src/components/item/settings/GeolocationPicker.tsx b/src/components/item/settings/GeolocationPicker.tsx index 80f8e7558..da32cd82d 100644 --- a/src/components/item/settings/GeolocationPicker.tsx +++ b/src/components/item/settings/GeolocationPicker.tsx @@ -1,27 +1,16 @@ -import { ChangeEventHandler } from 'react'; - import Clear from '@mui/icons-material/Clear'; -import { - Box, - IconButton, - InputAdornment, - LinearProgress, - List, - ListItemButton, - OutlinedInput, - Stack, - Tooltip, - Typography, -} from '@mui/material'; +import { IconButton, Stack, Tooltip, Typography } from '@mui/material'; +import { + type GeolocationPickerProps, + GeolocationPicker as MapGeolocationPicker, +} from '@graasp/map'; import { DiscriminatedItem } from '@graasp/sdk'; import { useBuilderTranslation } from '@/config/i18n'; import { hooks, mutations } from '@/config/queryClient'; import { BUILDER } from '@/langs/constants'; -import { OpenStreetMapResult, useSearchAddress } from './hooks'; - const GeolocationPicker = ({ item, }: { @@ -32,42 +21,26 @@ const GeolocationPicker = ({ const { mutate: putGeoloc } = mutations.usePutItemGeolocation(); const { mutate: deleteGeoloc } = mutations.useDeleteItemGeolocation(); - const { - query, - setQuery, - isDebounced, - setResults, - results, - loading, - clearQuery, - } = useSearchAddress({ - lang: item.lang, - geoloc, - }); - - const onChange: ChangeEventHandler = (e) => { - setQuery(e.target.value); - }; - - const onChangeOption = (option: OpenStreetMapResult): void => { - const { - raw: { lat, lon: lng }, - label, - } = option; + const onChangeOption: GeolocationPickerProps['onChangeOption'] = (option: { + addressLabel: string; + lat: number; + lng: number; + country?: string; + }): void => { + const { addressLabel, lat, lng, country } = option; putGeoloc({ itemId: item.id, geolocation: { - lng: parseFloat(lng), - lat: parseFloat(lat), - addressLabel: label, + addressLabel, + lat, + lng, + country, }, }); - setResults([]); }; const clearGeoloc = () => { deleteGeoloc({ itemId: item.id }); - clearQuery(); }; // the input is disabled if the geoloc is defined in parent @@ -86,30 +59,18 @@ const GeolocationPicker = ({ - - - - - - - - ) : undefined - } + - {loading && ( - - - - )} + + + + + + + {isDisabled && ( @@ -117,26 +78,6 @@ const GeolocationPicker = ({ {t(BUILDER.ITEM_SETTINGS_GEOLOCATION_INHERITED_EXPLANATION)} )} - - {results && ( - - {results.map((r) => ( - onChangeOption(r)} - > - {r.label} - - ))} - {!results.length && - query && - query !== geoloc?.addressLabel && - !loading && - !isDebounced && - t(BUILDER.ITEM_SETTINGS_GEOLOCATION_NO_ADDRESS)} - - )} - ); }; diff --git a/src/components/item/settings/hooks.ts b/src/components/item/settings/hooks.ts deleted file mode 100644 index d6c0ae0fe..000000000 --- a/src/components/item/settings/hooks.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { Dispatch, useCallback, useEffect, useMemo, useState } from 'react'; - -import { ItemGeolocation } from '@graasp/sdk'; - -import { OpenStreetMapProvider } from 'leaflet-geosearch'; - -import useDebouncedCallback from '@/utils/useDebounce'; - -type Props = { - geoloc?: ItemGeolocation | null; - lang: string; -}; - -const DELAY_MS = 1000; - -export type OpenStreetMapResult = Awaited< - ReturnType ->[0]; - -type ReturnedValue = { - clearQuery: () => void; - isDebounced: boolean; - loading: boolean; - query?: string | null; - results: OpenStreetMapResult[]; - setQuery: Dispatch; - setResults: Dispatch; -}; - -// eslint-disable-next-line import/prefer-default-export -export const useSearchAddress = ({ lang, geoloc }: Props): ReturnedValue => { - const [results, setResults] = useState([]); - const [loading, setLoading] = useState(false); - const [query, setQuery] = useState(); - - const provider = useMemo( - () => - new OpenStreetMapProvider({ - params: { - 'accept-language': lang, - }, - }), - [lang], - ); - - useEffect(() => { - setQuery(geoloc?.addressLabel); - }, [geoloc]); - - const callback = useCallback(() => { - if (query && query !== geoloc?.addressLabel) { - setLoading(true); - provider - .search({ query }) - .then((e) => { - setResults(e); - }) - .finally(() => { - setLoading(false); - }); - } - }, [query, geoloc?.addressLabel, provider]); - - const clearQuery = () => { - setQuery(''); - }; - - const { isDebounced } = useDebouncedCallback(callback, DELAY_MS); - - return { - results, - query, - setQuery, - isDebounced, - setResults, - loading, - clearQuery, - }; -}; diff --git a/src/components/main/Items.tsx b/src/components/main/Items.tsx index aa7ad9028..4407e75f7 100644 --- a/src/components/main/Items.tsx +++ b/src/components/main/Items.tsx @@ -5,6 +5,7 @@ import { ShowOnlyMeChangeType } from '@/config/types'; import { hooks } from '../../config/queryClient'; import { ItemLayoutMode } from '../../enums'; import { useLayoutContext } from '../context/LayoutContext'; +import MapView from '../item/MapView'; import { useItemsStatuses } from '../table/BadgesCellRenderer'; import ItemsGrid from './ItemsGrid'; import ItemsTable from './ItemsTable'; @@ -75,6 +76,8 @@ const Items = ({ itemsTags, }); switch (mode) { + case ItemLayoutMode.Map: + return ; case ItemLayoutMode.Grid: return ( =5.15.0" + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + "@types/react": + optional: true + checksum: 10/b5e5dc242f850819516f5e6108769bf496086a41b7e59fe2bd98e3dcd682eed551f4846ca48c6b9f31b659ee2a8360d77098bd9b2785fd204d82f6d2d91ea156 + languageName: node + linkType: hard + "@mui/material@npm:5.15.11": version: 5.15.11 resolution: "@mui/material@npm:5.15.11" @@ -1794,6 +1916,39 @@ __metadata: languageName: node linkType: hard +"@mui/material@npm:5.15.12": + version: 5.15.12 + resolution: "@mui/material@npm:5.15.12" + dependencies: + "@babel/runtime": "npm:^7.23.9" + "@mui/base": "npm:5.0.0-beta.38" + "@mui/core-downloads-tracker": "npm:^5.15.12" + "@mui/system": "npm:^5.15.12" + "@mui/types": "npm:^7.2.13" + "@mui/utils": "npm:^5.15.12" + "@types/react-transition-group": "npm:^4.4.10" + clsx: "npm:^2.1.0" + csstype: "npm:^3.1.3" + prop-types: "npm:^15.8.1" + react-is: "npm:^18.2.0" + react-transition-group: "npm:^4.4.5" + peerDependencies: + "@emotion/react": ^11.5.0 + "@emotion/styled": ^11.3.0 + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + "@types/react": + optional: true + checksum: 10/adee362d8367c4dec08fd4cec6b41fbe05699290b98e334c0203a73a4a13c24f2f4387b7f54b2000704b33182c3704fe873222227ccb58cc2099f9124bf02763 + languageName: node + linkType: hard + "@mui/private-theming@npm:^5.15.11": version: 5.15.11 resolution: "@mui/private-theming@npm:5.15.11" @@ -1811,6 +1966,23 @@ __metadata: languageName: node linkType: hard +"@mui/private-theming@npm:^5.15.12": + version: 5.15.12 + resolution: "@mui/private-theming@npm:5.15.12" + dependencies: + "@babel/runtime": "npm:^7.23.9" + "@mui/utils": "npm:^5.15.12" + prop-types: "npm:^15.8.1" + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/9e4e144d3de79536ec7cb46c98cabbe334ec7368c49ec3aa5a312e37fb8571cc95bad60b799e3252bbce7f2de6cc467f250cb217ca60316ead5303b5f5f2a660 + languageName: node + linkType: hard + "@mui/styled-engine@npm:^5.15.11": version: 5.15.11 resolution: "@mui/styled-engine@npm:5.15.11" @@ -1860,6 +2032,34 @@ __metadata: languageName: node linkType: hard +"@mui/system@npm:^5.15.12": + version: 5.15.12 + resolution: "@mui/system@npm:5.15.12" + dependencies: + "@babel/runtime": "npm:^7.23.9" + "@mui/private-theming": "npm:^5.15.12" + "@mui/styled-engine": "npm:^5.15.11" + "@mui/types": "npm:^7.2.13" + "@mui/utils": "npm:^5.15.12" + clsx: "npm:^2.1.0" + csstype: "npm:^3.1.3" + prop-types: "npm:^15.8.1" + peerDependencies: + "@emotion/react": ^11.5.0 + "@emotion/styled": ^11.3.0 + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + "@types/react": + optional: true + checksum: 10/23642eea1f5948cf0c57674569af9df3e7630d888da80d5d34afe6259a21631424f86b23a431a76a4046a6caa738d81d8a1b36ca438c6695aa93ca62b08afd50 + languageName: node + linkType: hard + "@mui/types@npm:^7.2.13": version: 7.2.13 resolution: "@mui/types@npm:7.2.13" @@ -1890,6 +2090,24 @@ __metadata: languageName: node linkType: hard +"@mui/utils@npm:^5.15.12": + version: 5.15.12 + resolution: "@mui/utils@npm:5.15.12" + dependencies: + "@babel/runtime": "npm:^7.23.9" + "@types/prop-types": "npm:^15.7.11" + prop-types: "npm:^15.8.1" + react-is: "npm:^18.2.0" + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/e4c40d73a0367ccc89491835fb98baafaea046fbcdd46d407689b8cda95ead1d45bb3b4364a135c1fa20d1c3b23ad69705e228943e5995d597860be9b5ab8c2b + languageName: node + linkType: hard + "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -1953,6 +2171,17 @@ __metadata: languageName: node linkType: hard +"@react-leaflet/core@npm:^2.1.0": + version: 2.1.0 + resolution: "@react-leaflet/core@npm:2.1.0" + peerDependencies: + leaflet: ^1.9.0 + react: ^18.0.0 + react-dom: ^18.0.0 + checksum: 10/12ce28b85cf6712a1a7b7c49466b941fc619bc7b1535308bc5711a35f7e89eb16298babfd62f6b3a92e64abf94dcf517b2bc460f59fcf20599821bc6ab3b3048 + languageName: node + linkType: hard + "@remix-run/router@npm:1.15.2": version: 1.15.2 resolution: "@remix-run/router@npm:1.15.2" @@ -1960,6 +2189,13 @@ __metadata: languageName: node linkType: hard +"@remix-run/router@npm:1.15.3": + version: 1.15.3 + resolution: "@remix-run/router@npm:1.15.3" + checksum: 10/43d402b4ad3dff6dee5c1bc0822aeeb4d885d11c74c45fca7f2f4d7e57853fddbbb813c372919bb3fcc63f95fbcffdd1d4ac1c406857ea07b9d09a09d0562c8e + languageName: node + linkType: hard + "@rollup/pluginutils@npm:^5.0.2": version: 5.1.0 resolution: "@rollup/pluginutils@npm:5.1.0" @@ -7231,10 +7467,11 @@ __metadata: "@emotion/react": "npm:11.11.4" "@emotion/styled": "npm:11.11.0" "@graasp/chatbox": "npm:3.1.0" - "@graasp/query-client": "npm:2.8.0" + "@graasp/map": "npm:1.4.0" + "@graasp/query-client": "npm:2.9.0" "@graasp/sdk": "npm:4.1.0" "@graasp/translations": "npm:1.25.2" - "@graasp/ui": "npm:4.9.0" + "@graasp/ui": "npm:4.9.1" "@mui/icons-material": "npm:5.15.11" "@mui/lab": "npm:5.0.0-alpha.166" "@mui/material": "npm:5.15.11" @@ -7611,6 +7848,15 @@ __metadata: languageName: node linkType: hard +"i18next@npm:23.7.16": + version: 23.7.16 + resolution: "i18next@npm:23.7.16" + dependencies: + "@babel/runtime": "npm:^7.23.2" + checksum: 10/77e74c07a73316f6fb6678a5a3e8ce58a6e66be457dd1ccd23941e9fc57ad8e1da55193fa6328c70b86073337b776cd267f3c13c6309f548b3116f27a1e41787 + languageName: node + linkType: hard + "iconv-lite@npm:0.4.24": version: 0.4.24 resolution: "iconv-lite@npm:0.4.24" @@ -8668,6 +8914,15 @@ __metadata: languageName: node linkType: hard +"leaflet-easybutton@npm:2.4.0": + version: 2.4.0 + resolution: "leaflet-easybutton@npm:2.4.0" + dependencies: + leaflet: "npm:^1.0.1" + checksum: 10/02c94dcfa19344d33908b329fa2e4531dfbebc97bdaa0b22fe0f9872ff8f5173ef28da54f7c5ea0ab38cbda71305107c3d7938e141e1029e8fdc5149cfe8d353 + languageName: node + linkType: hard + "leaflet-geosearch@npm:3.11.1": version: 3.11.1 resolution: "leaflet-geosearch@npm:3.11.1" @@ -8683,7 +8938,7 @@ __metadata: languageName: node linkType: hard -"leaflet@npm:^1.6.0": +"leaflet@npm:^1.0.1, leaflet@npm:^1.6.0, leaflet@npm:^1.9.4": version: 1.9.4 resolution: "leaflet@npm:1.9.4" checksum: 10/7b6a74d503980961a85bdabebf9d1162c26db0e88195800ceea311682b653d621718f2ada3c9aab903a735af9862c9ae278ba550d4429acbd954d43449cd0d77 @@ -11012,7 +11267,7 @@ __metadata: languageName: node linkType: hard -"react-dom@npm:18.2.0": +"react-dom@npm:18.2.0, react-dom@npm:^18.2.0": version: 18.2.0 resolution: "react-dom@npm:18.2.0" dependencies: @@ -11076,6 +11331,24 @@ __metadata: languageName: node linkType: hard +"react-i18next@npm:14.0.8": + version: 14.0.8 + resolution: "react-i18next@npm:14.0.8" + dependencies: + "@babel/runtime": "npm:^7.23.9" + html-parse-stringify: "npm:^3.0.1" + peerDependencies: + i18next: ">= 23.2.3" + react: ">= 16.8.0" + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + checksum: 10/5481cafa322476663e339d6eed2e4fb13e14fcb575bce9fc205bbc71da6d3a3392a1377d7eb59371357d14994b147d36214ec319ca2f7d85cd3526abfda54705 + languageName: node + linkType: hard + "react-image-crop@npm:11.0.5": version: 11.0.5 resolution: "react-image-crop@npm:11.0.5" @@ -11113,6 +11386,19 @@ __metadata: languageName: node linkType: hard +"react-leaflet@npm:^4.2.1": + version: 4.2.1 + resolution: "react-leaflet@npm:4.2.1" + dependencies: + "@react-leaflet/core": "npm:^2.1.0" + peerDependencies: + leaflet: ^1.9.0 + react: ^18.0.0 + react-dom: ^18.0.0 + checksum: 10/01cee12dc32e86d0153c989894fdba1c5c50fa41ad8d712352fa616f7b0dd32844aa17bb06c66f7569133675e2946c669090b4c496610cce3fa37c22254ca89f + languageName: node + linkType: hard + "react-markdown@npm:^9.0.1": version: 9.0.1 resolution: "react-markdown@npm:9.0.1" @@ -11252,6 +11538,19 @@ __metadata: languageName: node linkType: hard +"react-router-dom@npm:6.22.3": + version: 6.22.3 + resolution: "react-router-dom@npm:6.22.3" + dependencies: + "@remix-run/router": "npm:1.15.3" + react-router: "npm:6.22.3" + peerDependencies: + react: ">=16.8" + react-dom: ">=16.8" + checksum: 10/868a530c3167e1903f170818c0162760b8fbe9b10a7a7a79e5998990df341cd7127ba7819af4a9105af72c13453c7c4d76b2b07a70b56fff012fa0508b51940e + languageName: node + linkType: hard + "react-router@npm:6.22.2": version: 6.22.2 resolution: "react-router@npm:6.22.2" @@ -11263,6 +11562,17 @@ __metadata: languageName: node linkType: hard +"react-router@npm:6.22.3": + version: 6.22.3 + resolution: "react-router@npm:6.22.3" + dependencies: + "@remix-run/router": "npm:1.15.3" + peerDependencies: + react: ">=16.8" + checksum: 10/df3948afd6500faf4b82a72375b9177536d878d54cad18e20a175efcbfdd0d94852aac59660d786946636ed325284d94a8f46652d898df304d6a29c9a3932afd + languageName: node + linkType: hard + "react-text-mask@npm:5.5.0": version: 5.5.0 resolution: "react-text-mask@npm:5.5.0" @@ -11301,7 +11611,7 @@ __metadata: languageName: node linkType: hard -"react@npm:18.2.0": +"react@npm:18.2.0, react@npm:^18.2.0": version: 18.2.0 resolution: "react@npm:18.2.0" dependencies: