mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-20 14:39:06 +00:00
feat: draggable events
This commit is contained in:
@@ -13,6 +13,7 @@
|
|||||||
"date-fns": "^2.20.1",
|
"date-fns": "^2.20.1",
|
||||||
"framer-motion": "^4.1.6",
|
"framer-motion": "^4.1.6",
|
||||||
"react": "^17.0.1",
|
"react": "^17.0.1",
|
||||||
|
"react-beautiful-dnd": "^13.1.0",
|
||||||
"react-dom": "^17.0.1",
|
"react-dom": "^17.0.1",
|
||||||
"react-icons": "^4.2.0",
|
"react-icons": "^4.2.0",
|
||||||
"react-qr-code": "^1.1.1",
|
"react-qr-code": "^1.1.1",
|
||||||
|
|||||||
@@ -30,6 +30,13 @@ export const requestPatch = async (data) => {
|
|||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const requestReorder = async (data) => {
|
||||||
|
const reorder = 'reorder';
|
||||||
|
console.log('debug got reorder with payload', data);
|
||||||
|
const res = await axios.patch(eventsURL + '/' + reorder, data);
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
export const requestDelete = async (eventId) => {
|
export const requestDelete = async (eventId) => {
|
||||||
const res = await axios.delete(eventsURL + '/' + eventId);
|
const res = await axios.delete(eventsURL + '/' + eventId);
|
||||||
return res;
|
return res;
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ export const sampleData = {
|
|||||||
events: [
|
events: [
|
||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
order: 1,
|
|
||||||
title: 'Is the internet a fad?',
|
title: 'Is the internet a fad?',
|
||||||
subtitle: 'It is',
|
subtitle: 'It is',
|
||||||
presenter: 'Carlos Valente',
|
presenter: 'Carlos Valente',
|
||||||
@@ -24,13 +23,11 @@ export const sampleData = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 0.4849093424577693,
|
id: 0.4849093424577693,
|
||||||
order: 2,
|
|
||||||
duration: 25*60000,
|
duration: 25*60000,
|
||||||
type: 'delay',
|
type: 'delay',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '2',
|
id: '2',
|
||||||
order: 3,
|
|
||||||
title: 'Is reddit a dictatorship?',
|
title: 'Is reddit a dictatorship?',
|
||||||
subtitle: 'It is',
|
subtitle: 'It is',
|
||||||
presenter: 'Carlos Valente',
|
presenter: 'Carlos Valente',
|
||||||
@@ -42,7 +39,6 @@ export const sampleData = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '3',
|
id: '3',
|
||||||
order: 4,
|
|
||||||
title: 'Out of words',
|
title: 'Out of words',
|
||||||
subtitle: '',
|
subtitle: '',
|
||||||
presenter: 'Carlos Valente',
|
presenter: 'Carlos Valente',
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { useInterval } from '../../../app/hooks/useInterval';
|
|||||||
export default function Paginator(props) {
|
export default function Paginator(props) {
|
||||||
const { events, selectedId } = props;
|
const { events, selectedId } = props;
|
||||||
const LIMIT_PER_PAGE = props.limit || 8;
|
const LIMIT_PER_PAGE = props.limit || 8;
|
||||||
const SCROLL_TIME = (props.time * 1000) || 5000;
|
const SCROLL_TIME = props.time * 1000 || 5000;
|
||||||
const SCROLL_PAST = false;
|
const SCROLL_PAST = false;
|
||||||
const [numEvents, setNumEvents] = useState(0);
|
const [numEvents, setNumEvents] = useState(0);
|
||||||
const [page, setPage] = useState([]);
|
const [page, setPage] = useState([]);
|
||||||
@@ -42,7 +42,7 @@ export default function Paginator(props) {
|
|||||||
|
|
||||||
if (s.length < 1) return;
|
if (s.length < 1) return;
|
||||||
|
|
||||||
setSelected(s[0].order);
|
setSelected(s[0].id);
|
||||||
}, [events, selectedId]);
|
}, [events, selectedId]);
|
||||||
|
|
||||||
// every SCROLL_TIME go to the next array
|
// every SCROLL_TIME go to the next array
|
||||||
@@ -67,8 +67,8 @@ export default function Paginator(props) {
|
|||||||
<div className={style.entries}>
|
<div className={style.entries}>
|
||||||
{page.map((e) => {
|
{page.map((e) => {
|
||||||
let selectedState = 0;
|
let selectedState = 0;
|
||||||
if (e.order === selected) selectedState = 1;
|
if (e.id === selected) selectedState = 1;
|
||||||
else if (e.order > selected) selectedState = 2;
|
else if (e.id > selected) selectedState = 2;
|
||||||
return (
|
return (
|
||||||
<TodayItem
|
<TodayItem
|
||||||
key={e.id}
|
key={e.id}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Draggable } from 'react-beautiful-dnd';
|
||||||
|
import { FiMoreVertical } from 'react-icons/fi';
|
||||||
import DeleteIconBtn from '../../../common/components/buttons/DeleteIconBtn';
|
import DeleteIconBtn from '../../../common/components/buttons/DeleteIconBtn';
|
||||||
import ActionButtons from './ActionButtons';
|
import ActionButtons from './ActionButtons';
|
||||||
import style from './Block.module.css';
|
import style from './Block.module.css';
|
||||||
@@ -16,16 +18,27 @@ export default function BlockBlock(props) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={style.block}>
|
<Draggable key={data.id} draggableId={data.id} index={index}>
|
||||||
<div className={style.actionOverlay}>
|
{(provided) => (
|
||||||
<DeleteIconBtn clickhandler={deleteHandler} />
|
<div
|
||||||
<ActionButtons
|
className={style.block}
|
||||||
showAdd
|
{...provided.draggableProps}
|
||||||
addHandler={addHandler}
|
ref={provided.innerRef}
|
||||||
showDelay
|
>
|
||||||
delayHandler={delayHandler}
|
<span className={style.drag} {...provided.dragHandleProps}>
|
||||||
/>
|
<FiMoreVertical />
|
||||||
</div>
|
</span>
|
||||||
</div>
|
<div className={style.actionOverlay}>
|
||||||
|
<DeleteIconBtn clickhandler={deleteHandler} />
|
||||||
|
<ActionButtons
|
||||||
|
showAdd
|
||||||
|
addHandler={addHandler}
|
||||||
|
showDelay
|
||||||
|
delayHandler={delayHandler}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Draggable>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Draggable } from 'react-beautiful-dnd';
|
||||||
|
import { FiMoreVertical } from 'react-icons/fi';
|
||||||
import { millisToMinutes } from '../../../common/dateConfig';
|
import { millisToMinutes } from '../../../common/dateConfig';
|
||||||
import TimeInput from '../../../common/input/TimeInput';
|
import TimeInput from '../../../common/input/TimeInput';
|
||||||
import style from './Block.module.css';
|
import style from './Block.module.css';
|
||||||
@@ -20,17 +22,26 @@ export default function DelayBlock(props) {
|
|||||||
eventsHandler('delete', data.id);
|
eventsHandler('delete', data.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
let delayValue = (data.duration != null) ? millisToMinutes(data.duration) : undefined;
|
let delayValue =
|
||||||
|
data.duration != null ? millisToMinutes(data.duration) : undefined;
|
||||||
return (
|
return (
|
||||||
<div className={style.delay}>
|
<Draggable key={data.id} draggableId={data.id} index={index}>
|
||||||
<TimeInput
|
{(provided) => (
|
||||||
value={delayValue}
|
<div
|
||||||
submitHandler={submitHandler}
|
className={style.delay}
|
||||||
/>
|
{...provided.draggableProps}
|
||||||
<div className={style.actionOverlay}>
|
ref={provided.innerRef}
|
||||||
<DeleteIconBtn clickhandler={deleteHandler} />
|
>
|
||||||
<ActionButtons showAdd addHandler={addHandler} />
|
<span className={style.drag} {...provided.dragHandleProps}>
|
||||||
</div>
|
<FiMoreVertical />
|
||||||
</div>
|
</span>
|
||||||
|
<TimeInput value={delayValue} submitHandler={submitHandler} />
|
||||||
|
<div className={style.actionOverlay}>
|
||||||
|
<DeleteIconBtn clickhandler={deleteHandler} />
|
||||||
|
<ActionButtons showAdd addHandler={addHandler} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Draggable>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { FiChevronDown, FiChevronUp, FiMoreVertical } from 'react-icons/fi';
|
import { FiChevronDown, FiChevronUp, FiMoreVertical } from 'react-icons/fi';
|
||||||
import { memo, useEffect, useState } from 'react';
|
import { memo, useEffect, useState } from 'react';
|
||||||
import EventTimes from '../../../common/components/eventTimes/EventTimes';
|
|
||||||
import { showErrorToast } from '../../../common/helpers/toastManager';
|
import { showErrorToast } from '../../../common/helpers/toastManager';
|
||||||
|
import { Draggable } from 'react-beautiful-dnd';
|
||||||
|
import EventTimes from '../../../common/components/eventTimes/EventTimes';
|
||||||
import style from './Block.module.css';
|
import style from './Block.module.css';
|
||||||
import EditableText from '../../../common/input/EditableText';
|
import EditableText from '../../../common/input/EditableText';
|
||||||
import DelayValue from '../../../common/input/DelayValue';
|
import DelayValue from '../../../common/input/DelayValue';
|
||||||
@@ -10,15 +11,11 @@ import VisibleIconBtn from '../../../common/components/buttons/VisibleIconBtn';
|
|||||||
import DeleteIconBtn from '../../../common/components/buttons/DeleteIconBtn';
|
import DeleteIconBtn from '../../../common/components/buttons/DeleteIconBtn';
|
||||||
|
|
||||||
const areEqual = (prevProps, nextProps) => {
|
const areEqual = (prevProps, nextProps) => {
|
||||||
let shouldNotRerender =
|
|
||||||
prevProps.data.revision === nextProps.data.revision &&
|
|
||||||
prevProps.selected === nextProps.selected &&
|
|
||||||
prevProps.next === nextProps.next;
|
|
||||||
console.log('debug memo2', shouldNotRerender);
|
|
||||||
return (
|
return (
|
||||||
prevProps.data.revision === nextProps.data.revision &&
|
prevProps.data.revision === nextProps.data.revision &&
|
||||||
prevProps.selected === nextProps.selected &&
|
prevProps.selected === nextProps.selected &&
|
||||||
prevProps.next === nextProps.next
|
prevProps.next === nextProps.next &&
|
||||||
|
prevProps.index === nextProps.index
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -77,74 +74,86 @@ const EventBlock = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={selected ? style.eventRowActive : style.eventRow}>
|
<Draggable key={data.id} draggableId={data.id} index={index}>
|
||||||
<span className={style.drag}>
|
{(provided) => (
|
||||||
<FiMoreVertical />
|
<div
|
||||||
</span>
|
className={selected ? style.eventRowActive : style.eventRow}
|
||||||
<div className={style.indicators}>
|
{...provided.draggableProps}
|
||||||
<div className={next ? style.next : style.nextDisabled}>Next</div>
|
ref={provided.innerRef}
|
||||||
<DelayValue delay={delay} />
|
>
|
||||||
</div>
|
<span className={style.drag} {...provided.dragHandleProps}>
|
||||||
<EventTimes
|
<FiMoreVertical />
|
||||||
updateValues={updateValues}
|
</span>
|
||||||
timeStart={data.timeStart}
|
<div className={style.indicators}>
|
||||||
timeEnd={data.timeEnd}
|
<div className={next ? style.next : style.nextDisabled}>Next</div>
|
||||||
delay={delay}
|
<DelayValue delay={delay} />
|
||||||
/>
|
</div>
|
||||||
<div className={style.rowDetailed}>
|
<EventTimes
|
||||||
{more ? (
|
updateValues={updateValues}
|
||||||
<div className={style.detailedContainer}>
|
timeStart={data.timeStart}
|
||||||
<EditableText
|
timeEnd={data.timeEnd}
|
||||||
label='Title'
|
delay={delay}
|
||||||
defaultValue={data.title}
|
/>
|
||||||
placeholder='Add Title'
|
<div className={style.rowDetailed}>
|
||||||
submitHandler={handleTitleSubmit}
|
{more ? (
|
||||||
underlined
|
<div className={style.detailedContainer}>
|
||||||
|
<EditableText
|
||||||
|
label='Title'
|
||||||
|
defaultValue={data.title}
|
||||||
|
placeholder='Add Title'
|
||||||
|
submitHandler={handleTitleSubmit}
|
||||||
|
underlined
|
||||||
|
/>
|
||||||
|
<EditableText
|
||||||
|
label='Presenter'
|
||||||
|
defaultValue={data.presenter}
|
||||||
|
placeholder='Add Presenter name'
|
||||||
|
submitHandler={handlePresenterSubmit}
|
||||||
|
underlined
|
||||||
|
/>
|
||||||
|
<EditableText
|
||||||
|
label='Subtitle'
|
||||||
|
defaultValue={data.subtitle}
|
||||||
|
placeholder='Add Subtitle'
|
||||||
|
submitHandler={handleSubtitleSubmit}
|
||||||
|
underlined
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className={style.titleContainer}>
|
||||||
|
<EditableText
|
||||||
|
label='Title'
|
||||||
|
defaultValue={data.title}
|
||||||
|
placeholder='Add Title'
|
||||||
|
submitHandler={handleTitleSubmit}
|
||||||
|
isTight
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className={style.more} onClick={() => setMore(!more)}>
|
||||||
|
{more ? <FiChevronUp /> : <FiChevronDown />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={style.actionOverlay}>
|
||||||
|
<VisibleIconBtn
|
||||||
|
clickhandler={handleVisibleToggle}
|
||||||
|
active={visible}
|
||||||
/>
|
/>
|
||||||
<EditableText
|
<DeleteIconBtn clickhandler={deleteHandler} />
|
||||||
label='Presenter'
|
<ActionButtons
|
||||||
defaultValue={data.presenter}
|
showAdd
|
||||||
placeholder='Add Presenter name'
|
addHandler={addHandler}
|
||||||
submitHandler={handlePresenterSubmit}
|
showDelay
|
||||||
underlined
|
delayHandler={delayHandler}
|
||||||
/>
|
showBlock
|
||||||
<EditableText
|
blockHandler={blockHandler}
|
||||||
label='Subtitle'
|
|
||||||
defaultValue={data.subtitle}
|
|
||||||
placeholder='Add Subtitle'
|
|
||||||
submitHandler={handleSubtitleSubmit}
|
|
||||||
underlined
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
|
||||||
<div className={style.titleContainer}>
|
|
||||||
<EditableText
|
|
||||||
label='Title'
|
|
||||||
defaultValue={data.title}
|
|
||||||
placeholder='Add Title'
|
|
||||||
submitHandler={handleTitleSubmit}
|
|
||||||
isTight
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className={style.more} onClick={() => setMore(!more)}>
|
|
||||||
{more ? <FiChevronUp /> : <FiChevronDown />}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
<div className={style.actionOverlay}>
|
</Draggable>
|
||||||
<VisibleIconBtn clickhandler={handleVisibleToggle} active={visible} />
|
|
||||||
<DeleteIconBtn clickhandler={deleteHandler} />
|
|
||||||
<ActionButtons
|
|
||||||
showAdd
|
|
||||||
addHandler={addHandler}
|
|
||||||
showDelay
|
|
||||||
delayHandler={delayHandler}
|
|
||||||
showBlock
|
|
||||||
blockHandler={blockHandler}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(EventBlock, areEqual);
|
export default memo(EventBlock, areEqual);
|
||||||
|
// export default EventBlock;
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import style from './List.module.css';
|
import style from './List.module.css';
|
||||||
|
|
||||||
import { Fragment, useEffect, useState } from 'react';
|
import { Fragment, useEffect, useState } from 'react';
|
||||||
import { useSocket } from '../../../app/context/socketContext';
|
import { useSocket } from '../../../app/context/socketContext';
|
||||||
import tinykeys from 'tinykeys';
|
import tinykeys from 'tinykeys';
|
||||||
import Empty from '../../../common/state/Empty';
|
import Empty from '../../../common/state/Empty';
|
||||||
import EventListItem from './EventListItem';
|
import EventListItem from './EventListItem';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
|
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
|
||||||
|
|
||||||
export default function EventList(props) {
|
export default function EventList(props) {
|
||||||
const { events, eventsHandler } = props;
|
const { events, eventsHandler } = props;
|
||||||
@@ -90,6 +90,19 @@ export default function EventList(props) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// DND
|
||||||
|
const handleOnDragEnd = (result) => {
|
||||||
|
// drop outside of area
|
||||||
|
if (!result.destination) return;
|
||||||
|
|
||||||
|
// Call API
|
||||||
|
eventsHandler('reorder', {
|
||||||
|
index: result.draggableId,
|
||||||
|
from: result.source.index,
|
||||||
|
to: result.destination.index,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
console.log('EventList: events in event list', events);
|
console.log('EventList: events in event list', events);
|
||||||
let cumulativeDelay = 0;
|
let cumulativeDelay = 0;
|
||||||
|
|
||||||
@@ -106,35 +119,47 @@ export default function EventList(props) {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
|
<DragDropContext onDragEnd={handleOnDragEnd}>
|
||||||
{events.map((e, index) => {
|
<Droppable droppableId='eventlist'>
|
||||||
if (e.type === 'delay') cumulativeDelay += e.duration;
|
{(provided) => (
|
||||||
else if (e.type === 'block') cumulativeDelay = 0;
|
<div
|
||||||
return (
|
className={style.list}
|
||||||
<Fragment key={index}>
|
{...provided.droppableProps}
|
||||||
<EventListItem
|
ref={provided.innerRef}
|
||||||
type={e.type}
|
>
|
||||||
index={index}
|
{events.map((e, index) => {
|
||||||
data={e}
|
if (e.type === 'delay') cumulativeDelay += e.duration;
|
||||||
selected={selected === e.id}
|
else if (e.type === 'block') cumulativeDelay = 0;
|
||||||
next={next === e.id}
|
return (
|
||||||
eventsHandler={eventsHandler}
|
<Fragment key={e.id}>
|
||||||
delay={cumulativeDelay}
|
<EventListItem
|
||||||
/>
|
type={e.type}
|
||||||
<AnimatePresence>
|
index={index}
|
||||||
{cursor === index && (
|
data={e}
|
||||||
<motion.div
|
selected={selected === e.id}
|
||||||
className={style.cursor}
|
next={next === e.id}
|
||||||
variants={cursorVariants}
|
eventsHandler={eventsHandler}
|
||||||
initial='hidden'
|
delay={cumulativeDelay}
|
||||||
animate='visible'
|
/>
|
||||||
exit='exit'
|
<AnimatePresence>
|
||||||
/>
|
{cursor === index && (
|
||||||
)}
|
<motion.div
|
||||||
</AnimatePresence>
|
className={style.cursor}
|
||||||
</Fragment>
|
variants={cursorVariants}
|
||||||
);
|
initial='hidden'
|
||||||
})}
|
animate='visible'
|
||||||
|
exit='exit'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{provided.placeholder}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Droppable>
|
||||||
|
</DragDropContext>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ import {
|
|||||||
requestPost,
|
requestPost,
|
||||||
requestPut,
|
requestPut,
|
||||||
requestDelete,
|
requestDelete,
|
||||||
|
requestReorder,
|
||||||
} from '../../../app/api/eventsApi.js';
|
} from '../../../app/api/eventsApi.js';
|
||||||
import EventList from './EventList';
|
import EventList from './EventList';
|
||||||
import EventListMenu from '../../menu/EventListMenu.jsx';
|
import EventListMenu from '../../menu/EventListMenu.jsx';
|
||||||
import { showErrorToast } from '../../../common/helpers/toastManager';
|
import { showErrorToast } from '../../../common/helpers/toastManager';
|
||||||
import { Skeleton } from '@chakra-ui/skeleton';
|
import { Skeleton } from '@chakra-ui/skeleton';
|
||||||
import style from './List.module.css';
|
|
||||||
import { useFetch } from '../../../app/hooks/useFetch.js';
|
import { useFetch } from '../../../app/hooks/useFetch.js';
|
||||||
|
|
||||||
export default function EventListWrapper() {
|
export default function EventListWrapper() {
|
||||||
@@ -21,6 +21,7 @@ export default function EventListWrapper() {
|
|||||||
eventsNamespace,
|
eventsNamespace,
|
||||||
fetchAllEvents
|
fetchAllEvents
|
||||||
);
|
);
|
||||||
|
|
||||||
const addEvent = useMutation(requestPost, {
|
const addEvent = useMutation(requestPost, {
|
||||||
// we optimistically update here
|
// we optimistically update here
|
||||||
onMutate: async (newEvent) => {
|
onMutate: async (newEvent) => {
|
||||||
@@ -58,6 +59,7 @@ export default function EventListWrapper() {
|
|||||||
queryClient.invalidateQueries(eventsNamespace);
|
queryClient.invalidateQueries(eventsNamespace);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateEvent = useMutation(requestPut, {
|
const updateEvent = useMutation(requestPut, {
|
||||||
// we optimistically update here
|
// we optimistically update here
|
||||||
onMutate: async (newEvent) => {
|
onMutate: async (newEvent) => {
|
||||||
@@ -154,6 +156,37 @@ export default function EventListWrapper() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const reorderEvent = useMutation(requestReorder, {
|
||||||
|
// we optimistically update here
|
||||||
|
onMutate: async (data) => {
|
||||||
|
// cancel ongoing queries
|
||||||
|
queryClient.cancelQueries(eventsNamespace, { exact: true });
|
||||||
|
|
||||||
|
// Snapshot the previous value
|
||||||
|
const previousEvents = queryClient.getQueryData(eventsNamespace);
|
||||||
|
|
||||||
|
const e = [...previousEvents];
|
||||||
|
const [reorderedItem] = e.splice(data.from, 1);
|
||||||
|
e.splice(data.to, 0, reorderedItem);
|
||||||
|
|
||||||
|
// optimistically update object
|
||||||
|
queryClient.setQueryData(eventsNamespace, e);
|
||||||
|
|
||||||
|
// Return a context with the previous and new todo
|
||||||
|
return { previousEvents };
|
||||||
|
},
|
||||||
|
|
||||||
|
// Mutation fails, rollback undos optimist update
|
||||||
|
onError: (error, eventId, context) => {
|
||||||
|
queryClient.setQueryData(eventsNamespace, context.previousEvents);
|
||||||
|
},
|
||||||
|
// Mutation finished, failed or successful
|
||||||
|
// Fetch anyway, just to be sure
|
||||||
|
onSettled: () => {
|
||||||
|
queryClient.invalidateQueries(eventsNamespace);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Show toasts on errors
|
// Show toasts on errors
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isError) {
|
if (isError) {
|
||||||
@@ -200,6 +233,15 @@ export default function EventListWrapper() {
|
|||||||
showErrorToast('Error deleting event', error.message);
|
showErrorToast('Error deleting event', error.message);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'reorder':
|
||||||
|
try {
|
||||||
|
let t = Date.now();
|
||||||
|
await reorderEvent.mutateAsync(payload);
|
||||||
|
console.log('debug m reorder', Date.now() - t);
|
||||||
|
} catch (error) {
|
||||||
|
showErrorToast('Error reordering event', error.message);
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
showErrorToast('Unrecognised request', action);
|
showErrorToast('Unrecognised request', action);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 5px;
|
|
||||||
background-color: rgba(0, 0, 0, 0.05);
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@@ -11,6 +10,11 @@
|
|||||||
height: 73vh;
|
height: 73vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
.empty {
|
.empty {
|
||||||
opacity: 0.3;
|
opacity: 0.3;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
|
|||||||
@@ -4,10 +4,3 @@ export const sortByDate = (arr) => {
|
|||||||
};
|
};
|
||||||
return arr.sort(sorter);
|
return arr.sort(sorter);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sortByOrderVal = (arr) => {
|
|
||||||
const sorter = (a, b) => {
|
|
||||||
return a.order - b.order;
|
|
||||||
};
|
|
||||||
return arr.sort(sorter);
|
|
||||||
};
|
|
||||||
|
|||||||
+87
-3
@@ -2422,6 +2422,14 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/hoist-non-react-statics@^3.3.0":
|
||||||
|
version "3.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
|
||||||
|
integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
|
||||||
|
dependencies:
|
||||||
|
"@types/react" "*"
|
||||||
|
hoist-non-react-statics "^3.3.0"
|
||||||
|
|
||||||
"@types/html-minifier-terser@^5.0.0":
|
"@types/html-minifier-terser@^5.0.0":
|
||||||
version "5.1.1"
|
version "5.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz#3c9ee980f1a10d6021ae6632ca3e79ca2ec4fb50"
|
resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz#3c9ee980f1a10d6021ae6632ca3e79ca2ec4fb50"
|
||||||
@@ -2501,11 +2509,35 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.2.3.tgz#ef65165aea2924c9359205bf748865b8881753c0"
|
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.2.3.tgz#ef65165aea2924c9359205bf748865b8881753c0"
|
||||||
integrity sha512-PijRCG/K3s3w1We6ynUKdxEc5AcuuH3NBmMDP8uvKVp6X43UY7NQlTzczakXP3DJR0F4dfNQIGjU2cUeRYs2AA==
|
integrity sha512-PijRCG/K3s3w1We6ynUKdxEc5AcuuH3NBmMDP8uvKVp6X43UY7NQlTzczakXP3DJR0F4dfNQIGjU2cUeRYs2AA==
|
||||||
|
|
||||||
|
"@types/prop-types@*":
|
||||||
|
version "15.7.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
|
||||||
|
integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
|
||||||
|
|
||||||
"@types/q@^1.5.1":
|
"@types/q@^1.5.1":
|
||||||
version "1.5.4"
|
version "1.5.4"
|
||||||
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24"
|
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24"
|
||||||
integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==
|
integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==
|
||||||
|
|
||||||
|
"@types/react-redux@^7.1.16":
|
||||||
|
version "7.1.16"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.16.tgz#0fbd04c2500c12105494c83d4a3e45c084e3cb21"
|
||||||
|
integrity sha512-f/FKzIrZwZk7YEO9E1yoxIuDNRiDducxkFlkw/GNMGEnK9n4K8wJzlJBghpSuOVDgEUHoDkDF7Gi9lHNQR4siw==
|
||||||
|
dependencies:
|
||||||
|
"@types/hoist-non-react-statics" "^3.3.0"
|
||||||
|
"@types/react" "*"
|
||||||
|
hoist-non-react-statics "^3.3.0"
|
||||||
|
redux "^4.0.0"
|
||||||
|
|
||||||
|
"@types/react@*":
|
||||||
|
version "17.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.5.tgz#3d887570c4489011f75a3fc8f965bf87d09a1bea"
|
||||||
|
integrity sha512-bj4biDB9ZJmGAYTWSKJly6bMr4BLUiBrx9ujiJEoP9XIDY9CTaPGxE5QWN/1WjpPLzYF7/jRNnV2nNxNe970sw==
|
||||||
|
dependencies:
|
||||||
|
"@types/prop-types" "*"
|
||||||
|
"@types/scheduler" "*"
|
||||||
|
csstype "^3.0.2"
|
||||||
|
|
||||||
"@types/resolve@0.0.8":
|
"@types/resolve@0.0.8":
|
||||||
version "0.0.8"
|
version "0.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.8.tgz#f26074d238e02659e323ce1a13d041eee280e194"
|
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.8.tgz#f26074d238e02659e323ce1a13d041eee280e194"
|
||||||
@@ -2513,6 +2545,11 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/scheduler@*":
|
||||||
|
version "0.16.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.1.tgz#18845205e86ff0038517aab7a18a62a6b9f71275"
|
||||||
|
integrity sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA==
|
||||||
|
|
||||||
"@types/source-list-map@*":
|
"@types/source-list-map@*":
|
||||||
version "0.1.2"
|
version "0.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9"
|
resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9"
|
||||||
@@ -4421,7 +4458,7 @@ css-blank-pseudo@^0.1.4:
|
|||||||
dependencies:
|
dependencies:
|
||||||
postcss "^7.0.5"
|
postcss "^7.0.5"
|
||||||
|
|
||||||
css-box-model@1.2.1:
|
css-box-model@1.2.1, css-box-model@^1.2.0:
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.2.1.tgz#59951d3b81fd6b2074a62d49444415b0d2b4d7c1"
|
resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.2.1.tgz#59951d3b81fd6b2074a62d49444415b0d2b4d7c1"
|
||||||
integrity sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==
|
integrity sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==
|
||||||
@@ -6329,7 +6366,7 @@ hmac-drbg@^1.0.1:
|
|||||||
minimalistic-assert "^1.0.0"
|
minimalistic-assert "^1.0.0"
|
||||||
minimalistic-crypto-utils "^1.0.1"
|
minimalistic-crypto-utils "^1.0.1"
|
||||||
|
|
||||||
hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.1:
|
hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2:
|
||||||
version "3.3.2"
|
version "3.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
||||||
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
|
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
|
||||||
@@ -8012,6 +8049,11 @@ media-typer@0.3.0:
|
|||||||
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
||||||
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
|
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
|
||||||
|
|
||||||
|
memoize-one@^5.1.1:
|
||||||
|
version "5.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
|
||||||
|
integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
|
||||||
|
|
||||||
memory-fs@^0.4.1:
|
memory-fs@^0.4.1:
|
||||||
version "0.4.1"
|
version "0.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552"
|
resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552"
|
||||||
@@ -9913,6 +9955,11 @@ queue-microtask@^1.2.2:
|
|||||||
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
|
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
|
||||||
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
|
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
|
||||||
|
|
||||||
|
raf-schd@^4.0.2:
|
||||||
|
version "4.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a"
|
||||||
|
integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==
|
||||||
|
|
||||||
raf@^3.4.1:
|
raf@^3.4.1:
|
||||||
version "3.4.1"
|
version "3.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
|
resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
|
||||||
@@ -9962,6 +10009,19 @@ react-app-polyfill@^2.0.0:
|
|||||||
regenerator-runtime "^0.13.7"
|
regenerator-runtime "^0.13.7"
|
||||||
whatwg-fetch "^3.4.1"
|
whatwg-fetch "^3.4.1"
|
||||||
|
|
||||||
|
react-beautiful-dnd@^13.1.0:
|
||||||
|
version "13.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-13.1.0.tgz#ec97c81093593526454b0de69852ae433783844d"
|
||||||
|
integrity sha512-aGvblPZTJowOWUNiwd6tNfEpgkX5OxmpqxHKNW/4VmvZTNTbeiq7bA3bn5T+QSF2uibXB0D1DmJsb1aC/+3cUA==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.9.2"
|
||||||
|
css-box-model "^1.2.0"
|
||||||
|
memoize-one "^5.1.1"
|
||||||
|
raf-schd "^4.0.2"
|
||||||
|
react-redux "^7.2.0"
|
||||||
|
redux "^4.0.4"
|
||||||
|
use-memo-one "^1.1.1"
|
||||||
|
|
||||||
react-clientside-effect@^1.2.2:
|
react-clientside-effect@^1.2.2:
|
||||||
version "1.2.5"
|
version "1.2.5"
|
||||||
resolved "https://registry.yarnpkg.com/react-clientside-effect/-/react-clientside-effect-1.2.5.tgz#e2c4dc3c9ee109f642fac4f5b6e9bf5bcd2219a3"
|
resolved "https://registry.yarnpkg.com/react-clientside-effect/-/react-clientside-effect-1.2.5.tgz#e2c4dc3c9ee109f642fac4f5b6e9bf5bcd2219a3"
|
||||||
@@ -10035,7 +10095,7 @@ react-icons@^4.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.2.0.tgz#6dda80c8a8f338ff96a1851424d63083282630d0"
|
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.2.0.tgz#6dda80c8a8f338ff96a1851424d63083282630d0"
|
||||||
integrity sha512-rmzEDFt+AVXRzD7zDE21gcxyBizD/3NqjbX6cmViAgdqfJ2UiLer8927/QhhrXQV7dEj/1EGuOTPp7JnLYVJKQ==
|
integrity sha512-rmzEDFt+AVXRzD7zDE21gcxyBizD/3NqjbX6cmViAgdqfJ2UiLer8927/QhhrXQV7dEj/1EGuOTPp7JnLYVJKQ==
|
||||||
|
|
||||||
react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1:
|
react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1:
|
||||||
version "16.13.1"
|
version "16.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||||
@@ -10063,6 +10123,18 @@ react-query@^3.13.5:
|
|||||||
broadcast-channel "^3.4.1"
|
broadcast-channel "^3.4.1"
|
||||||
match-sorter "^6.0.2"
|
match-sorter "^6.0.2"
|
||||||
|
|
||||||
|
react-redux@^7.2.0:
|
||||||
|
version "7.2.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.4.tgz#1ebb474032b72d806de2e0519cd07761e222e225"
|
||||||
|
integrity sha512-hOQ5eOSkEJEXdpIKbnRyl04LhaWabkDPV+Ix97wqQX3T3d2NQ8DUblNXXtNMavc7DpswyQM6xfaN4HQDKNY2JA==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.12.1"
|
||||||
|
"@types/react-redux" "^7.1.16"
|
||||||
|
hoist-non-react-statics "^3.3.2"
|
||||||
|
loose-envify "^1.4.0"
|
||||||
|
prop-types "^15.7.2"
|
||||||
|
react-is "^16.13.1"
|
||||||
|
|
||||||
react-refresh@^0.8.3:
|
react-refresh@^0.8.3:
|
||||||
version "0.8.3"
|
version "0.8.3"
|
||||||
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"
|
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"
|
||||||
@@ -10288,6 +10360,13 @@ redent@^3.0.0:
|
|||||||
indent-string "^4.0.0"
|
indent-string "^4.0.0"
|
||||||
strip-indent "^3.0.0"
|
strip-indent "^3.0.0"
|
||||||
|
|
||||||
|
redux@^4.0.0, redux@^4.0.4:
|
||||||
|
version "4.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.0.tgz#eb049679f2f523c379f1aff345c8612f294c88d4"
|
||||||
|
integrity sha512-uI2dQN43zqLWCt6B/BMGRMY6db7TTY4qeHHfGeKb3EOhmOKjU3KdWvNLJyqaHRksv/ErdNH7cFZWg9jXtewy4g==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.9.2"
|
||||||
|
|
||||||
regenerate-unicode-properties@^8.2.0:
|
regenerate-unicode-properties@^8.2.0:
|
||||||
version "8.2.0"
|
version "8.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec"
|
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec"
|
||||||
@@ -12038,6 +12117,11 @@ use-callback-ref@^1.2.1, use-callback-ref@^1.2.3:
|
|||||||
resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.2.5.tgz#6115ed242cfbaed5915499c0a9842ca2912f38a5"
|
resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.2.5.tgz#6115ed242cfbaed5915499c0a9842ca2912f38a5"
|
||||||
integrity sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg==
|
integrity sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg==
|
||||||
|
|
||||||
|
use-memo-one@^1.1.1:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.2.tgz#0c8203a329f76e040047a35a1197defe342fab20"
|
||||||
|
integrity sha512-u2qFKtxLsia/r8qG0ZKkbytbztzRb317XCkT7yP8wxL0tZ/CzK2G+WWie5vWvpyeP7+YoPIwbJoIHJ4Ba4k0oQ==
|
||||||
|
|
||||||
use-sidecar@^1.0.1:
|
use-sidecar@^1.0.1:
|
||||||
version "1.0.5"
|
version "1.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.0.5.tgz#ffff2a17c1df42e348624b699ba6e5c220527f2b"
|
resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.0.5.tgz#ffff2a17c1df42e348624b699ba6e5c220527f2b"
|
||||||
|
|||||||
+2
-1
@@ -6,6 +6,7 @@ const low = require('lowdb');
|
|||||||
const FileSync = require('lowdb/adapters/FileSync');
|
const FileSync = require('lowdb/adapters/FileSync');
|
||||||
const adapter = new FileSync(config.database.filename);
|
const adapter = new FileSync(config.database.filename);
|
||||||
const db = low(adapter);
|
const db = low(adapter);
|
||||||
|
|
||||||
// TODO: move defaults to config file
|
// TODO: move defaults to config file
|
||||||
db.defaults({
|
db.defaults({
|
||||||
events: [],
|
events: [],
|
||||||
@@ -65,7 +66,7 @@ app.use((err, req, res, next) => {
|
|||||||
const server = http.createServer(app);
|
const server = http.createServer(app);
|
||||||
|
|
||||||
// get data (if any)
|
// get data (if any)
|
||||||
const eventlist = db.get('events').sortBy('order').value();
|
const eventlist = db.get('events').value();
|
||||||
|
|
||||||
// init timer
|
// init timer
|
||||||
global.timer = new EventTimer(server, config);
|
global.timer = new EventTimer(server, config);
|
||||||
|
|||||||
@@ -5,20 +5,6 @@ const db = require('../app.js').db;
|
|||||||
const { nanoid } = require('nanoid');
|
const { nanoid } = require('nanoid');
|
||||||
const eventDefs = require('../data/eventsDefinition.js');
|
const eventDefs = require('../data/eventsDefinition.js');
|
||||||
|
|
||||||
// incrementFrom
|
|
||||||
function incrementFrom(start, incr = 1) {
|
|
||||||
let entries = db.get('events').sortBy('order').value();
|
|
||||||
|
|
||||||
entries.map((e) => {
|
|
||||||
if (e.order < start) return;
|
|
||||||
db.get('events')
|
|
||||||
.find({ id: e.id })
|
|
||||||
.assign({ order: e.order + incr })
|
|
||||||
.update('revision', (n) => n + 1)
|
|
||||||
.write();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function _getEventsCount() {
|
function _getEventsCount() {
|
||||||
return db.get('events').size().value();
|
return db.get('events').size().value();
|
||||||
}
|
}
|
||||||
@@ -27,17 +13,40 @@ function _pushNew(entry) {
|
|||||||
return db.get('events').push(entry).write();
|
return db.get('events').push(entry).write();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _insertAt(entry, index) {
|
||||||
|
// get events
|
||||||
|
let events = db.get('events').value();
|
||||||
|
let count = events.length;
|
||||||
|
let order = entry.order;
|
||||||
|
|
||||||
|
// Remove order field from object
|
||||||
|
delete entry.order;
|
||||||
|
|
||||||
|
// Insert at beggining
|
||||||
|
if (order === 0) {
|
||||||
|
events.unshift(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert at end
|
||||||
|
else if (order >= count) {
|
||||||
|
events.push(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert in the middle
|
||||||
|
else {
|
||||||
|
events.splice(index, 0, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
// save events
|
||||||
|
db.set('events', events).write();
|
||||||
|
}
|
||||||
|
|
||||||
function _removeById(eventId) {
|
function _removeById(eventId) {
|
||||||
return db.get('events').remove({ id: eventId }).write();
|
return db.get('events').remove({ id: eventId }).write();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEventEvents() {
|
function getEventEvents() {
|
||||||
return db
|
return db.get('events').chain().filter({ type: 'event' }).value();
|
||||||
.get('events')
|
|
||||||
.chain()
|
|
||||||
.filter({ type: 'event' })
|
|
||||||
.sortBy('order')
|
|
||||||
.value();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updates timer object
|
// Updates timer object
|
||||||
@@ -54,7 +63,7 @@ function _updateTimersSingle(id, entry) {
|
|||||||
// Create controller for GET request to '/events'
|
// Create controller for GET request to '/events'
|
||||||
// Returns -
|
// Returns -
|
||||||
exports.eventsGetAll = async (req, res) => {
|
exports.eventsGetAll = async (req, res) => {
|
||||||
const results = db.get('events').sortBy('order').value();
|
const results = db.get('events').value();
|
||||||
res.json(results);
|
res.json(results);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -97,13 +106,11 @@ exports.eventsPost = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// increment count if necessary
|
// get place where event should be
|
||||||
const c = _getEventsCount();
|
const index = newEvent.order || 0;
|
||||||
|
|
||||||
if (newEvent.order < c) incrementFrom(newEvent.order);
|
// add new event in place
|
||||||
|
_insertAt(newEvent, index);
|
||||||
// add new event
|
|
||||||
_pushNew(newEvent);
|
|
||||||
|
|
||||||
// update timers
|
// update timers
|
||||||
_updateTimers();
|
_updateTimers();
|
||||||
@@ -111,6 +118,7 @@ exports.eventsPost = async (req, res) => {
|
|||||||
// reply OK
|
// reply OK
|
||||||
res.sendStatus(201);
|
res.sendStatus(201);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
res.status(400).send(error);
|
res.status(400).send(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -134,6 +142,7 @@ exports.eventsPut = async (req, res) => {
|
|||||||
db.get('events')
|
db.get('events')
|
||||||
.find({ id: req.body.id })
|
.find({ id: req.body.id })
|
||||||
.assign({ ...req.body })
|
.assign({ ...req.body })
|
||||||
|
.update('revision', (n) => n + 1)
|
||||||
.write();
|
.write();
|
||||||
|
|
||||||
// update timer
|
// update timer
|
||||||
@@ -148,30 +157,51 @@ exports.eventsPut = async (req, res) => {
|
|||||||
// Create controller for PATCH request to '/events/'
|
// Create controller for PATCH request to '/events/'
|
||||||
// Returns -
|
// Returns -
|
||||||
exports.eventsPatch = async (req, res) => {
|
exports.eventsPatch = async (req, res) => {
|
||||||
// no valid params
|
// Code is the same as put, call that
|
||||||
|
this.eventsPut(req, res);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.eventsReorder = async (req, res) => {
|
||||||
|
// TODO: Validate event
|
||||||
if (!req.body) {
|
if (!req.body) {
|
||||||
res.status(400).send(`No object found`);
|
res.status(400).send(`No object found in request`);
|
||||||
|
console.log(`No object found in request`);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let eventId = req.body.id;
|
const { index, from, to } = req.body;
|
||||||
if (!eventId) {
|
|
||||||
res.status(400).send(`No id found`);
|
console.log(req.body);
|
||||||
|
|
||||||
|
// get events
|
||||||
|
let events = db.get('events').value();
|
||||||
|
let idx = events.findIndex((e) => e.id === index, from);
|
||||||
|
|
||||||
|
// Check if item is at given index
|
||||||
|
if (idx !== from) {
|
||||||
|
res.status(400).send(`Id not found at index`);
|
||||||
|
console.log(`Id not found at index`, idx, from);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
db.get('events')
|
// remove item at from
|
||||||
.find({ id: req.body.id })
|
const [reorderedItem] = events.splice(from, 1);
|
||||||
.assign({ ...req.body })
|
|
||||||
.update('revision', (n) => n + 1)
|
|
||||||
.write();
|
|
||||||
|
|
||||||
|
// reinsert item at to
|
||||||
|
events.splice(to, 0, reorderedItem);
|
||||||
|
|
||||||
|
// save events
|
||||||
|
db.set('events', events).write();
|
||||||
|
|
||||||
|
// TODO: would it be more efficient to reorder at timer?
|
||||||
// update timer
|
// update timer
|
||||||
_updateTimersSingle(req.body.id, req.body);
|
_updateTimers();
|
||||||
|
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
res.status(400).send(error);
|
res.status(400).send(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -187,18 +217,7 @@ exports.eventsDelete = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// increment count if necessary
|
// remove new event
|
||||||
const c = _getEventsCount();
|
|
||||||
const e = db.get('events').find({ id: req.params.eventId }).value();
|
|
||||||
|
|
||||||
if (e == null) {
|
|
||||||
res.status(400).send(`No match`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c > 0 && e.order < c) incrementFrom(e.order, -1);
|
|
||||||
|
|
||||||
// add new event
|
|
||||||
_removeById(req.params.eventId);
|
_removeById(req.params.eventId);
|
||||||
|
|
||||||
// update timer
|
// update timer
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"id":"xxxxx0",
|
"id":"xxxxx0",
|
||||||
"order": 0,
|
|
||||||
"duration": 300000,
|
"duration": 300000,
|
||||||
"type": "delay"
|
"type": "delay"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id":"xxxxx1",
|
"id":"xxxxx1",
|
||||||
"order": 1,
|
|
||||||
"title": "Is the internet a fad?",
|
"title": "Is the internet a fad?",
|
||||||
"subtitle": "It is",
|
"subtitle": "It is",
|
||||||
"presenter": "Carlos Valente",
|
"presenter": "Carlos Valente",
|
||||||
@@ -17,13 +15,11 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id":"xxxxx2",
|
"id":"xxxxx2",
|
||||||
"order": 2,
|
|
||||||
"duration": 1500000,
|
"duration": 1500000,
|
||||||
"type": "delay"
|
"type": "delay"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id":"xxxxx3",
|
"id":"xxxxx3",
|
||||||
"order": 3,
|
|
||||||
"title": "Is reddit a dictatorship?",
|
"title": "Is reddit a dictatorship?",
|
||||||
"subtitle": "It is",
|
"subtitle": "It is",
|
||||||
"presenter": "Carlos Valente",
|
"presenter": "Carlos Valente",
|
||||||
@@ -33,7 +29,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id":"xxxxx4",
|
"id":"xxxxx4",
|
||||||
"order": 4,
|
|
||||||
"title": "Out of words",
|
"title": "Out of words",
|
||||||
"subtitle": "",
|
"subtitle": "",
|
||||||
"presenter": "Carlos Valente",
|
"presenter": "Carlos Valente",
|
||||||
@@ -43,7 +38,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id":"xxxxx5",
|
"id":"xxxxx5",
|
||||||
"order": 5,
|
|
||||||
"title": "Really",
|
"title": "Really",
|
||||||
"subtitle": "",
|
"subtitle": "",
|
||||||
"presenter": "Carlos Valente",
|
"presenter": "Carlos Valente",
|
||||||
@@ -53,7 +47,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id":"xxxxx6",
|
"id":"xxxxx6",
|
||||||
"order": 6,
|
|
||||||
"title": "...",
|
"title": "...",
|
||||||
"subtitle": "",
|
"subtitle": "",
|
||||||
"presenter": "Carlos Valente",
|
"presenter": "Carlos Valente",
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
const event = {
|
const event = {
|
||||||
order: 0,
|
|
||||||
title: '',
|
title: '',
|
||||||
subtitle: '',
|
subtitle: '',
|
||||||
presenter: '',
|
presenter: '',
|
||||||
|
note: '',
|
||||||
timeStart: 0,
|
timeStart: 0,
|
||||||
timeEnd: 0,
|
timeEnd: 0,
|
||||||
isPublic: false,
|
isPublic: false,
|
||||||
@@ -11,13 +11,11 @@ const event = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const delay = {
|
const delay = {
|
||||||
order: 0,
|
|
||||||
duration: 0,
|
duration: 0,
|
||||||
type: 'delay',
|
type: 'delay',
|
||||||
};
|
};
|
||||||
|
|
||||||
const block = {
|
const block = {
|
||||||
order: 0,
|
|
||||||
type: 'block',
|
type: 'block',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4,22 +4,25 @@ const router = express.Router();
|
|||||||
// import events controller
|
// import events controller
|
||||||
const eventsController = require('../controllers/eventsController');
|
const eventsController = require('../controllers/eventsController');
|
||||||
|
|
||||||
// create route between controller and '/events' endpoint
|
// create route between controller and '/events/' endpoint
|
||||||
router.get('/', eventsController.eventsGetAll);
|
router.get('/', eventsController.eventsGetAll);
|
||||||
|
|
||||||
// create route between controller and '/events/:id' endpoint
|
// create route between controller and '/events/:eventId' endpoint
|
||||||
router.get('/:eventId', eventsController.eventsGetById);
|
router.get('/:eventId', eventsController.eventsGetById);
|
||||||
|
|
||||||
// create route between controller and '/events/' endpoint
|
// create route between controller and '/events/' endpoint
|
||||||
router.post('/', eventsController.eventsPost);
|
router.post('/', eventsController.eventsPost);
|
||||||
|
|
||||||
// create route between controller and '/events/:id' endpoint
|
// create route between controller and '/events/' endpoint
|
||||||
router.put('/', eventsController.eventsPut);
|
router.put('/', eventsController.eventsPut);
|
||||||
|
|
||||||
// create route between controller and '/events/:id' endpoint
|
// create route between controller and '/events/' endpoint
|
||||||
router.patch('/', eventsController.eventsPatch);
|
router.patch('/', eventsController.eventsPatch);
|
||||||
|
|
||||||
// create route between controller and '/events/:id' endpoint
|
// create route between controller and '/events/reorder' endpoint
|
||||||
|
router.patch('/reorder/', eventsController.eventsReorder);
|
||||||
|
|
||||||
|
// create route between controller and '/events/:eventId' endpoint
|
||||||
router.delete('/:eventId', eventsController.eventsDelete);
|
router.delete('/:eventId', eventsController.eventsDelete);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
Reference in New Issue
Block a user