From 57bd754fc05def8150759955615ea48e1469849e Mon Sep 17 00:00:00 2001 From: Pushkar Anand Date: Sat, 20 Mar 2021 20:28:12 +0530 Subject: [PATCH] Implemented tile selection feature --- src/components/Delete.tsx | 20 +++++ src/pages/gallery/components/PreviewCard.tsx | 86 ++++++++++++++++++-- src/pages/gallery/index.tsx | 36 +++++++- src/utils/common/useLongPress.ts | 27 ++++++ 4 files changed, 162 insertions(+), 7 deletions(-) create mode 100644 src/components/Delete.tsx create mode 100644 src/utils/common/useLongPress.ts diff --git a/src/components/Delete.tsx b/src/components/Delete.tsx new file mode 100644 index 000000000..e4d7a0975 --- /dev/null +++ b/src/components/Delete.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +export default function Delete(props) { + return ( + + + + ); +} + +Delete.defaultProps = { + height: 24, + width: 24, + viewBox: '0 0 24 24', +}; diff --git a/src/pages/gallery/components/PreviewCard.tsx b/src/pages/gallery/components/PreviewCard.tsx index 87ccfb882..7626a2bf6 100644 --- a/src/pages/gallery/components/PreviewCard.tsx +++ b/src/pages/gallery/components/PreviewCard.tsx @@ -1,20 +1,74 @@ -import React, { useEffect, useState } from 'react'; +import React, { SyntheticEvent, useEffect, useState } from 'react'; import { file } from 'services/fileService'; import styled from 'styled-components'; import PlayCircleOutline from 'components/PlayCircleOutline'; import DownloadManager from 'services/downloadManager'; import { getToken } from 'utils/common/key'; +import useLongPress from 'utils/common/useLongPress'; interface IProps { data: file; updateUrl: (url: string) => void; onClick?: () => void; forcedEnable?: boolean; + selectable?: boolean; + selected?: boolean; + onSelect?: (checked: boolean) => void; + selectOnClick?: boolean; } -const Cont = styled.div<{ disabled: boolean }>` +const Check = styled.input` + appearance: none; + position: absolute; + opacity: 0; + outline: none; + + &::before { + content: ''; + width: 16px; + height: 16px; + border: 2px solid #fff; + background-color: rgba(0,0,0,0.5); + display: inline-block; + border-radius: 50%; + vertical-align: bottom; + margin: 8px 8px; + text-align: center; + line-height: 16px; + transition: background-color .3s ease; + } + &::after { + content: ''; + width: 5px; + height: 10px; + border-right: 2px solid #fff; + border-bottom: 2px solid #fff; + transform: translate(-18px, 8px); + opacity: 0; + transition: transform .3s ease; + position: absolute; + } + + /** checked */ + &:checked::before { + content: ''; + background-color: #2dc262; + border-color: #2dc262; + color: #fff; + } + &:checked::after { + opacity: 1; + transform: translate(-18px, 10px) rotate(45deg); + } + + &:checked { + opacity: 1; + } +`; + +const Cont = styled.div<{ disabled: boolean, selected: boolean }>` background: #222; - display: block; + display: flex; width: fit-content; height: 192px; min-width: 100%; @@ -26,6 +80,8 @@ const Cont = styled.div<{ disabled: boolean }>` object-fit: cover; max-width: 100%; min-height: 100%; + flex: 1; + ${props => props.selected && 'border: 5px solid #2dc262;'} } & > svg { @@ -39,11 +95,15 @@ const Cont = styled.div<{ disabled: boolean }>` left: -25px; filter: drop-shadow(3px 3px 2px rgba(0, 0, 0, 0.7)); } + + &:hover ${Check} { + opacity: 1; + } `; export default function PreviewCard(props: IProps) { const [imgSrc, setImgSrc] = useState(); - const { data, onClick, updateUrl, forcedEnable } = props; + const { data, onClick, updateUrl, forcedEnable, selectable, selected, onSelect, selectOnClick } = props; useEffect(() => { if (data && !data.msrc) { @@ -58,17 +118,31 @@ export default function PreviewCard(props: IProps) { }, [data]); const handleClick = () => { - if (data?.msrc || imgSrc) { + if (selectOnClick) { + onSelect?.(!selected); + } else if (data?.msrc || imgSrc) { onClick?.(); } }; + const handleSelect = (e: SyntheticEvent) => { + e.stopPropagation(); + onSelect?.(e.target.checked); + } + + const longPressCallback = () => { + onSelect(!selected); + } + return ( - + {selectable && } + {(data?.msrc || imgSrc) && } {data?.metadata.fileType === 1 && } ); diff --git a/src/pages/gallery/index.tsx b/src/pages/gallery/index.tsx index 75c4d2118..859cda006 100644 --- a/src/pages/gallery/index.tsx +++ b/src/pages/gallery/index.tsx @@ -25,6 +25,7 @@ import { import constants from 'utils/strings/constants'; import AlertBanner from './components/AlertBanner'; import { Alert, Button, Jumbotron } from 'react-bootstrap'; +import Delete from 'components/delete'; const DATE_CONTAINER_HEIGHT = 45; const IMAGE_CONTAINER_HEIGHT = 200; @@ -118,7 +119,26 @@ interface Props { setNavbarIconView; err; } -export default function Gallery(props: Props) { + +const DeleteBtn = styled.button` + border: none; + background-color: #ff6666; + position: fixed; + z-index: 1; + bottom: 10px; + right: 10px; + width: 50px; + height: 50px; + border-radius: 50%; + color: #fff; +`; + +type selectedState = { + [k:number]: boolean; + count: number; +} + +export default function Gallery(props) { const router = useRouter(); const [collections, setCollections] = useState([]); const [ @@ -133,6 +153,7 @@ export default function Gallery(props: Props) { const [bannerErrorCode, setBannerErrorCode] = useState(null); const [sinceTime, setSinceTime] = useState(0); const [isFirstLoad, setIsFirstLoad] = useState(false); + const [selected, setSelected] = useState({ count: 0 }); const loadingBar = useRef(null); useEffect(() => { @@ -245,6 +266,14 @@ export default function Gallery(props: Props) { setOpen(true); }; + const handleSelect = (id: number) => (checked: boolean) => { + setSelected({ + ...selected, + [id]: checked, + count: checked ? selected.count + 1: selected.count - 1, + }); + } + const getThumbnail = (file: file[], index: number) => { return ( 0} /> ); }; @@ -526,6 +559,7 @@ export default function Gallery(props: Props) { {constants.INSTALL_MOBILE_APP()} )} + {selected.count && } ); } diff --git a/src/utils/common/useLongPress.ts b/src/utils/common/useLongPress.ts new file mode 100644 index 000000000..35b9c7873 --- /dev/null +++ b/src/utils/common/useLongPress.ts @@ -0,0 +1,27 @@ +// https://stackoverflow.com/a/54749871/2760968 +import { useState, useEffect } from 'react'; + +export default function useLongPress(callback: () => void, ms = 300) { + const [startLongPress, setStartLongPress] = useState(false); + + useEffect(() => { + let timerId: NodeJS.Timeout; + if (startLongPress) { + timerId = setTimeout(callback, ms); + } else { + clearTimeout(timerId); + } + + return () => { + clearTimeout(timerId); + }; + }, [callback, ms, startLongPress]); + + return { + onMouseDown: () => setStartLongPress(true), + onMouseUp: () => setStartLongPress(false), + onMouseLeave: () => setStartLongPress(false), + onTouchStart: () => setStartLongPress(true), + onTouchEnd: () => setStartLongPress(false), + }; +} \ No newline at end of file