Implemented tile selection feature

This commit is contained in:
Pushkar Anand 2021-03-20 20:28:12 +05:30
parent 90eeca6f52
commit 57bd754fc0
4 changed files with 162 additions and 7 deletions

20
src/components/Delete.tsx Normal file
View file

@ -0,0 +1,20 @@
import React from 'react';
export default function Delete(props) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
height={props.height}
viewBox={props.viewBox}
width={props.width}
>
<path d="M0 0h24v24H0z" fill="none"/><path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/>
</svg>
);
}
Delete.defaultProps = {
height: 24,
width: 24,
viewBox: '0 0 24 24',
};

View file

@ -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<string>();
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<HTMLInputElement>) => {
e.stopPropagation();
onSelect?.(e.target.checked);
}
const longPressCallback = () => {
onSelect(!selected);
}
return (
<Cont
onClick={handleClick}
disabled={!forcedEnable && !data?.msrc && !imgSrc}
selected={selected}
{...(selectable ? useLongPress(longPressCallback,500) : {})}
>
<img src={data?.msrc || imgSrc} />
{selectable && <Check type='checkbox' checked={selected} onClick={handleSelect}/>}
{(data?.msrc || imgSrc) && <img src={data?.msrc || imgSrc} />}
{data?.metadata.fileType === 1 && <PlayCircleOutline />}
</Cont>
);

View file

@ -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<collection[]>([]);
const [
@ -133,6 +153,7 @@ export default function Gallery(props: Props) {
const [bannerErrorCode, setBannerErrorCode] = useState<number>(null);
const [sinceTime, setSinceTime] = useState(0);
const [isFirstLoad, setIsFirstLoad] = useState(false);
const [selected, setSelected] = useState<selectedState>({ 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 (
<PreviewCard
@ -252,6 +281,10 @@ export default function Gallery(props: Props) {
data={file[index]}
updateUrl={updateUrl(file[index].dataIndex)}
onClick={onThumbnailClick(index)}
selectable
onSelect={handleSelect(file[index].id)}
selected={selected[file[index].id]}
selectOnClick={selected.count > 0}
/>
);
};
@ -526,6 +559,7 @@ export default function Gallery(props: Props) {
{constants.INSTALL_MOBILE_APP()}
</Alert>
)}
{selected.count && <DeleteBtn><Delete /></DeleteBtn>}
</>
);
}

View file

@ -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),
};
}