Implemented tile selection feature
This commit is contained in:
parent
90eeca6f52
commit
57bd754fc0
20
src/components/Delete.tsx
Normal file
20
src/components/Delete.tsx
Normal 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',
|
||||
};
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
27
src/utils/common/useLongPress.ts
Normal file
27
src/utils/common/useLongPress.ts
Normal 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),
|
||||
};
|
||||
}
|
Loading…
Reference in a new issue