mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-21 06:59:09 +00:00
feat: expand collapse
This commit is contained in:
@@ -0,0 +1,60 @@
|
|||||||
|
const { atom } = require('jotai');
|
||||||
|
|
||||||
|
const PATH = 'option-collapse';
|
||||||
|
|
||||||
|
// collapse options object, initialised from local storage
|
||||||
|
// REFRACT: When do we read from local storage?
|
||||||
|
// on every render?
|
||||||
|
|
||||||
|
export const collapseAtom = atom(JSON.parse(localStorage.getItem(PATH)) || {});
|
||||||
|
|
||||||
|
// get a single option, if it exists
|
||||||
|
export const SelectCollapse = (id) => {
|
||||||
|
return atom((get) => get(collapseAtom)[id]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// change a single item in object
|
||||||
|
export const HandleCollapse = atom(null, (get, set, payload) => {
|
||||||
|
const updatedVal = {
|
||||||
|
...get(collapseAtom),
|
||||||
|
...payload,
|
||||||
|
};
|
||||||
|
set(collapseAtom, updatedVal);
|
||||||
|
localStorage.setItem(PATH, JSON.stringify(updatedVal));
|
||||||
|
});
|
||||||
|
|
||||||
|
// change collapsed in several items
|
||||||
|
export const BatchOperation = atom(null, (get, set, payload) => {
|
||||||
|
let prevOptions = get(collapseAtom);
|
||||||
|
|
||||||
|
let newOptions = {};
|
||||||
|
|
||||||
|
for (const item of payload.items) {
|
||||||
|
newOptions[item.id] = payload.isCollapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload.clear) {
|
||||||
|
// clear object
|
||||||
|
prevOptions = {};
|
||||||
|
// clear localstorage
|
||||||
|
localStorage.removeItem(PATH);
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = { ...prevOptions, ...newOptions };
|
||||||
|
|
||||||
|
set(collapseAtom, options);
|
||||||
|
|
||||||
|
localStorage.setItem(PATH, JSON.stringify(options));
|
||||||
|
});
|
||||||
|
|
||||||
|
// change collapsed in all items
|
||||||
|
export const setAll = async (items, isCollapsed) => {
|
||||||
|
// clear storage
|
||||||
|
localStorage.removeItem(PATH);
|
||||||
|
|
||||||
|
// clear local object
|
||||||
|
console.log('debug collapse here', isCollapsed);
|
||||||
|
|
||||||
|
// call batch
|
||||||
|
BatchOperation({ items: items, isCollapsed: isCollapsed });
|
||||||
|
};
|
||||||
@@ -7,7 +7,7 @@ export default function CollapseBtn(props) {
|
|||||||
<Button
|
<Button
|
||||||
size={props.size || 'xs'}
|
size={props.size || 'xs'}
|
||||||
leftIcon={<FiChevronsUp />}
|
leftIcon={<FiChevronsUp />}
|
||||||
colorScheme='whiteAlpha'
|
colorScheme='white'
|
||||||
variant='outline'
|
variant='outline'
|
||||||
onClick={clickhandler}
|
onClick={clickhandler}
|
||||||
_focus={{ boxShadow: 'none' }}
|
_focus={{ boxShadow: 'none' }}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export default function ExpandBtn(props) {
|
|||||||
<Button
|
<Button
|
||||||
size={props.size || 'xs'}
|
size={props.size || 'xs'}
|
||||||
leftIcon={<FiChevronsDown />}
|
leftIcon={<FiChevronsDown />}
|
||||||
colorScheme='whiteAlpha'
|
colorScheme='white'
|
||||||
variant='outline'
|
variant='outline'
|
||||||
onClick={clickhandler}
|
onClick={clickhandler}
|
||||||
_focus={{ boxShadow: 'none' }}
|
_focus={{ boxShadow: 'none' }}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import Icon from '@chakra-ui/icon';
|
import Icon from '@chakra-ui/icon';
|
||||||
import { FiChevronDown, FiChevronUp, FiMoreVertical } from 'react-icons/fi';
|
import { FiChevronDown, FiChevronUp, FiMoreVertical } from 'react-icons/fi';
|
||||||
import { useState } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { Draggable } from 'react-beautiful-dnd';
|
import { Draggable } from 'react-beautiful-dnd';
|
||||||
import EventTimes from 'common/components/eventTimes/EventTimes';
|
import EventTimes from 'common/components/eventTimes/EventTimes';
|
||||||
import EventTimesVertical from 'common/components/eventTimes/EventTimesVertical';
|
import EventTimesVertical from 'common/components/eventTimes/EventTimesVertical';
|
||||||
@@ -10,6 +10,8 @@ import VisibleIconBtn from 'common/components/buttons/VisibleIconBtn';
|
|||||||
import DeleteIconBtn from 'common/components/buttons/DeleteIconBtn';
|
import DeleteIconBtn from 'common/components/buttons/DeleteIconBtn';
|
||||||
import { millisToMinutes } from 'common/dateConfig';
|
import { millisToMinutes } from 'common/dateConfig';
|
||||||
import style from './EventBlock.module.css';
|
import style from './EventBlock.module.css';
|
||||||
|
import { SelectCollapse, HandleCollapse } from 'app/context/collapseAtom';
|
||||||
|
import { useAtom } from 'jotai';
|
||||||
|
|
||||||
const ExpandedBlock = (props) => {
|
const ExpandedBlock = (props) => {
|
||||||
const { provided, data, next, delay, delayValue, actionHandler } = props;
|
const { provided, data, next, delay, delayValue, actionHandler } = props;
|
||||||
@@ -85,7 +87,7 @@ const ExpandedBlock = (props) => {
|
|||||||
as={FiChevronUp}
|
as={FiChevronUp}
|
||||||
marginTop='0.2em'
|
marginTop='0.2em'
|
||||||
gridArea='more'
|
gridArea='more'
|
||||||
onClick={() => props.setExpanded(false)}
|
onClick={() => props.setCollapsed(true)}
|
||||||
/>
|
/>
|
||||||
<div className={style.actionOverlay}>
|
<div className={style.actionOverlay}>
|
||||||
<VisibleIconBtn actionHandler={actionHandler} active={data.isPublic} />
|
<VisibleIconBtn actionHandler={actionHandler} active={data.isPublic} />
|
||||||
@@ -138,7 +140,7 @@ const CollapsedBlock = (props) => {
|
|||||||
as={FiChevronDown}
|
as={FiChevronDown}
|
||||||
marginTop='0.2em'
|
marginTop='0.2em'
|
||||||
gridArea='more'
|
gridArea='more'
|
||||||
onClick={() => props.setExpanded(true)}
|
onClick={() => props.setCollapsed(false)}
|
||||||
/>
|
/>
|
||||||
<div className={style.actionOverlay}>
|
<div className={style.actionOverlay}>
|
||||||
<VisibleIconBtn actionHandler={actionHandler} active={data.isPublic} />
|
<VisibleIconBtn actionHandler={actionHandler} active={data.isPublic} />
|
||||||
@@ -156,17 +158,27 @@ const CollapsedBlock = (props) => {
|
|||||||
export default function EventBlock(props) {
|
export default function EventBlock(props) {
|
||||||
const { data, selected, delay, index, actionHandler } = props;
|
const { data, selected, delay, index, actionHandler } = props;
|
||||||
|
|
||||||
const [expanded, setExpanded] = useState(true);
|
// const [collapsed, setCollapsed] = useState(checkLocalStorage(data.id));
|
||||||
|
|
||||||
|
// const collapsed = useSelector(itemsAtom, (state) => state === data.id);
|
||||||
|
const [collapsed] = useAtom(
|
||||||
|
useMemo(() => SelectCollapse(data.id), [data.id])
|
||||||
|
);
|
||||||
|
const [, setCollapsed] = useAtom(HandleCollapse);
|
||||||
|
|
||||||
// TODO: should this go inside useEffect()
|
// TODO: should this go inside useEffect()
|
||||||
// Would I then need to add this to state?
|
// Would I then need to add this to state?
|
||||||
const isSelected = selected ? style.active : '';
|
const isSelected = selected ? style.active : '';
|
||||||
const isExpanded = expanded ? style.expanded : style.collapsed;
|
const isCollapsed = collapsed ? style.collapsed : style.expanded;
|
||||||
const classSelect = `${style.event} ${isExpanded} ${isSelected}`;
|
const classSelect = `${style.event} ${isCollapsed} ${isSelected}`;
|
||||||
|
|
||||||
// Calculate delay in min
|
// Calculate delay in min
|
||||||
const delayValue = delay > 0 ? millisToMinutes(delay) : null;
|
const delayValue = delay > 0 ? millisToMinutes(delay) : null;
|
||||||
|
|
||||||
|
const handleCollapse = (isCollapsed) => {
|
||||||
|
setCollapsed({ [data.id]: isCollapsed });
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Draggable key={data.id} draggableId={data.id} index={index}>
|
<Draggable key={data.id} draggableId={data.id} index={index}>
|
||||||
{(provided) => (
|
{(provided) => (
|
||||||
@@ -175,17 +187,7 @@ export default function EventBlock(props) {
|
|||||||
{...provided.draggableProps}
|
{...provided.draggableProps}
|
||||||
ref={provided.innerRef}
|
ref={provided.innerRef}
|
||||||
>
|
>
|
||||||
{expanded ? (
|
{collapsed ? (
|
||||||
<ExpandedBlock
|
|
||||||
provided={provided}
|
|
||||||
data={data}
|
|
||||||
next={props.next}
|
|
||||||
delay={delay}
|
|
||||||
delayValue={delayValue}
|
|
||||||
actionHandler={actionHandler}
|
|
||||||
setExpanded={setExpanded}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<CollapsedBlock
|
<CollapsedBlock
|
||||||
provided={provided}
|
provided={provided}
|
||||||
data={data}
|
data={data}
|
||||||
@@ -193,7 +195,17 @@ export default function EventBlock(props) {
|
|||||||
delay={delay}
|
delay={delay}
|
||||||
delayValue={delayValue}
|
delayValue={delayValue}
|
||||||
actionHandler={actionHandler}
|
actionHandler={actionHandler}
|
||||||
setExpanded={setExpanded}
|
setCollapsed={handleCollapse}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<ExpandedBlock
|
||||||
|
provided={provided}
|
||||||
|
data={data}
|
||||||
|
next={props.next}
|
||||||
|
delay={delay}
|
||||||
|
delayValue={delayValue}
|
||||||
|
actionHandler={actionHandler}
|
||||||
|
setCollapsed={handleCollapse}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,8 +15,11 @@ import { showErrorToast } from 'common/helpers/toastManager';
|
|||||||
import { useFetch } from 'app/hooks/useFetch.js';
|
import { useFetch } from 'app/hooks/useFetch.js';
|
||||||
import Empty from 'common/state/Empty';
|
import Empty from 'common/state/Empty';
|
||||||
import { EVENTS_TABLE } from 'app/api/apiConstants';
|
import { EVENTS_TABLE } from 'app/api/apiConstants';
|
||||||
|
import { BatchOperation } from 'app/context/collapseAtom';
|
||||||
|
import { useAtom } from 'jotai';
|
||||||
|
|
||||||
export default function EventListWrapper() {
|
export default function EventListWrapper() {
|
||||||
|
const [, setCollapsed] = useAtom(BatchOperation);
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { data, status, isError, refetch } = useFetch(
|
const { data, status, isError, refetch } = useFetch(
|
||||||
EVENTS_TABLE,
|
EVENTS_TABLE,
|
||||||
@@ -283,8 +286,17 @@ export default function EventListWrapper() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log('debug m apply', Date.now() - t);
|
console.log('debug m apply', Date.now() - t);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'collapseall':
|
||||||
|
if (data == null) return;
|
||||||
|
setCollapsed({ clear: true, items: data, isCollapsed: true });
|
||||||
|
break;
|
||||||
|
case 'expandall':
|
||||||
|
if (data == null) return;
|
||||||
|
setCollapsed({ clear: true, items: data, isCollapsed: false });
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
showErrorToast('Unrecognised request', action);
|
showErrorToast('Unrecognised request', action);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -73,8 +73,11 @@ const EventListMenu = ({ eventsHandler }) => {
|
|||||||
</MenuList>
|
</MenuList>
|
||||||
</Menu>
|
</Menu>
|
||||||
<CurrentBtn size='sm' />
|
<CurrentBtn size='sm' />
|
||||||
<CollapseBtn size='sm' />
|
<CollapseBtn
|
||||||
<ExpandBtn size='sm' />
|
size='sm'
|
||||||
|
clickhandler={() => eventsHandler('collapseall')}
|
||||||
|
/>
|
||||||
|
<ExpandBtn size='sm' clickhandler={() => eventsHandler('expandall')} />
|
||||||
<MenuActionButtons actionHandler={actionHandler} size='sm' />
|
<MenuActionButtons actionHandler={actionHandler} size='sm' />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user