mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-27 01:49:10 +00:00
feat: delay flow
This commit is contained in:
+17
-8
@@ -16,32 +16,41 @@ export const sampleData = {
|
|||||||
title: 'Is the internet a fad?',
|
title: 'Is the internet a fad?',
|
||||||
subtitle: 'It is',
|
subtitle: 'It is',
|
||||||
presenter: 'Carlos Valente',
|
presenter: 'Carlos Valente',
|
||||||
timeStart: new Date('March 24, 2021 03:25:00'),
|
timeStart: new Date('October 13, 2014 11:30:00'),
|
||||||
timeEnd: new Date('March 24, 2021 04:24:00'),
|
timeEnd: new Date('October 13, 2014 11:50:00'),
|
||||||
clockStarted: dummy,
|
clockStarted: dummy,
|
||||||
timerDuration: 60,
|
timerDuration: 60,
|
||||||
|
type: 'event',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 0.4849093424577693,
|
||||||
|
order: 2,
|
||||||
|
timerDuration: 25,
|
||||||
|
type: 'delay',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '2',
|
id: '2',
|
||||||
order: 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',
|
||||||
timeStart: new Date('March 24, 2021 04:25:00'),
|
timeStart: new Date('October 13, 2014 12:30:00'),
|
||||||
timeEnd: new Date('March 24, 2021 05:24:00'),
|
timeEnd: new Date('October 13, 2014 12:50:00'),
|
||||||
clockStarted: dummy,
|
clockStarted: dummy,
|
||||||
timerDuration: 60,
|
timerDuration: 60,
|
||||||
|
type: 'event',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '3',
|
id: '3',
|
||||||
order: 3,
|
order: 4,
|
||||||
title: 'Out of words',
|
title: 'Out of words',
|
||||||
subtitle: '',
|
subtitle: '',
|
||||||
presenter: 'Carlos Valente',
|
presenter: 'Carlos Valente',
|
||||||
timeStart: new Date('March 24, 2021 05:25:00'),
|
timeStart: new Date('October 13, 2014 13:30:00'),
|
||||||
timeEnd: new Date('March 24, 2021 05:55:00'),
|
timeEnd: new Date('October 13, 2014 13:50:00'),
|
||||||
clockStarted: dummy,
|
clockStarted: dummy,
|
||||||
timerDuration: 60,
|
timerDuration: 60,
|
||||||
|
type: 'event',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1 +1,7 @@
|
|||||||
export const timeFormat = 'HH:mm';
|
export const timeFormat = 'HH:mm';
|
||||||
|
|
||||||
|
// make date with string
|
||||||
|
export const timeToDate = (time) => {
|
||||||
|
const today = new Date();
|
||||||
|
return new Date(today.toDateString() + ' ' + time);
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,4 +1,24 @@
|
|||||||
|
import { IconButton } from '@chakra-ui/button';
|
||||||
|
import { AddIcon, MinusIcon } from '@chakra-ui/icons';
|
||||||
import style from './List.module.css';
|
import style from './List.module.css';
|
||||||
export default function BlockBlock() {
|
|
||||||
return <div className={style.blockContainer} />;
|
export default function BlockBlock(props) {
|
||||||
|
return (
|
||||||
|
<div className={style.blockContainer}>
|
||||||
|
<div className={style.actionOverlay}>
|
||||||
|
<IconButton
|
||||||
|
size='xs'
|
||||||
|
icon={<MinusIcon />}
|
||||||
|
colorScheme='red'
|
||||||
|
onClick={() => props.deleteEvent(props.index)}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
size='xs'
|
||||||
|
icon={<AddIcon />}
|
||||||
|
colorScheme='blue'
|
||||||
|
onClick={() => props.createEvent(props.index)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,35 +8,40 @@ import {
|
|||||||
SliderThumb,
|
SliderThumb,
|
||||||
SliderTrack,
|
SliderTrack,
|
||||||
} from '@chakra-ui/slider';
|
} from '@chakra-ui/slider';
|
||||||
import { useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import style from './List.module.css';
|
import style from './List.module.css';
|
||||||
|
|
||||||
export default function DelayBlock() {
|
export default function DelayBlock(props) {
|
||||||
const [delay, setDelay] = useState(0);
|
const [delay, setDelay] = useState(0);
|
||||||
const { getIncrementButtonProps, getDecrementButtonProps } = useNumberInput({
|
const { data, ...rest } = props;
|
||||||
step: 1,
|
|
||||||
defaultValue: 0,
|
|
||||||
min: -60,
|
|
||||||
max: 60,
|
|
||||||
});
|
|
||||||
|
|
||||||
const inc = getIncrementButtonProps();
|
// update delay value in parent
|
||||||
const dec = getDecrementButtonProps();
|
const populateDelay = (value) => {
|
||||||
|
// check if value has changed
|
||||||
|
if (data.timerDuration !== value) {
|
||||||
|
// create object with new field
|
||||||
|
const newData = { ...data, timerDuration: delay };
|
||||||
|
|
||||||
const populateDelay = (val) => {
|
// request update in parent
|
||||||
console.log('set delay in parent', val);
|
props.updateData(props.index, newData);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// update delay value in state
|
||||||
|
useEffect(() => {
|
||||||
|
setDelay(data.timerDuration);
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={style.delayContainer}>
|
<div className={style.delayContainer}>
|
||||||
<div className={style.delayValue}>{`${delay} min`}</div>
|
<div className={style.delayValue}>{`${delay} min`}</div>
|
||||||
<Slider
|
<Slider
|
||||||
defaultValue={0}
|
defaultValue={data.timerDuration}
|
||||||
min={-60}
|
min={0}
|
||||||
max={60}
|
max={60}
|
||||||
step={5}
|
step={5}
|
||||||
onChange={(val) => setDelay(val)}
|
onChange={(value) => setDelay(value)}
|
||||||
onChangeEnd={(val) => populateDelay(val)}
|
onChangeEnd={(value) => populateDelay(value)}
|
||||||
>
|
>
|
||||||
<SliderTrack bg='orange.100'>
|
<SliderTrack bg='orange.100'>
|
||||||
<Box position='relative' right={10} />
|
<Box position='relative' right={10} />
|
||||||
@@ -45,8 +50,18 @@ export default function DelayBlock() {
|
|||||||
<SliderThumb boxSize={4} />
|
<SliderThumb boxSize={4} />
|
||||||
</Slider>
|
</Slider>
|
||||||
<div className={style.actionOverlay}>
|
<div className={style.actionOverlay}>
|
||||||
<IconButton size='xs' icon={<MinusIcon />} colorScheme='red' />
|
<IconButton
|
||||||
<IconButton size='xs' icon={<AddIcon />} colorScheme='blue' />
|
size='xs'
|
||||||
|
icon={<MinusIcon />}
|
||||||
|
colorScheme='red'
|
||||||
|
onClick={() => props.deleteEvent(props.index)}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
size='xs'
|
||||||
|
icon={<AddIcon />}
|
||||||
|
colorScheme='blue'
|
||||||
|
onClick={() => props.createEvent(props.index)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,52 +8,87 @@ import { sortByOrderVal } from './listUtils';
|
|||||||
import style from './List.module.css';
|
import style from './List.module.css';
|
||||||
import DelayBlock from './DelayBlock';
|
import DelayBlock from './DelayBlock';
|
||||||
import BlockBlock from './BlockBlock';
|
import BlockBlock from './BlockBlock';
|
||||||
import { createEvent } from '@testing-library/dom';
|
|
||||||
|
|
||||||
export default function EventList(props) {
|
export default function EventList(props) {
|
||||||
const [events, setEvents] = useContext(EventListContext);
|
const [events, setEvents] = useContext(EventListContext);
|
||||||
const [event, setEvent] = useContext(EventContext);
|
const [event, setEvent] = useContext(EventContext);
|
||||||
console.log('in el', events);
|
|
||||||
|
|
||||||
const selected = event?.id || -1;
|
const selected = event?.id || -1;
|
||||||
|
|
||||||
const createEvent = (startPosition = 0) => {
|
const insertItemAt = (item, index) => {
|
||||||
|
// handle insert at beggining of array
|
||||||
|
if (index === -1) {
|
||||||
|
// move all items one element down, starting from new position
|
||||||
|
events.forEach((e) => {
|
||||||
|
e.order = e.order + 1;
|
||||||
|
});
|
||||||
|
return [item, ...events];
|
||||||
|
} else {
|
||||||
|
let before = events.slice(0, index + 1);
|
||||||
|
let after = events.slice(index + 1);
|
||||||
|
|
||||||
|
// move all items one element down, starting from new position
|
||||||
|
after.forEach((e) => {
|
||||||
|
e.order = e.order + 1;
|
||||||
|
});
|
||||||
|
return [...before, item, ...after];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const createEvent = (itemIndex = -1) => {
|
||||||
// make an event
|
// make an event
|
||||||
// TODO: Replace this with global def somewhere
|
// TODO: Replace this with global def somewhere
|
||||||
// TODO: handle random ids better
|
// TODO: handle random ids better
|
||||||
let newEvent = {
|
let newEvent = {
|
||||||
id: Math.floor(Math.random() * 10000000),
|
id: Math.random(),
|
||||||
order: startPosition + 1,
|
order: itemIndex + 1,
|
||||||
title: '',
|
title: '',
|
||||||
subtitle: '',
|
subtitle: '',
|
||||||
presenter: '',
|
presenter: '',
|
||||||
timerStart: '',
|
timeStart: new Date(),
|
||||||
timerEnd: '',
|
timeEnd: new Date(),
|
||||||
clockStarted: null,
|
clockStarted: null,
|
||||||
timerDuration: 0,
|
timerDuration: 0,
|
||||||
|
type: 'event',
|
||||||
};
|
};
|
||||||
|
|
||||||
// move all items one element down, starting from new position
|
|
||||||
let newEvents = events.map((e) => {
|
|
||||||
if (e.order > startPosition) e.order = e.order + 1;
|
|
||||||
return e;
|
|
||||||
});
|
|
||||||
|
|
||||||
// add an event at the beggining
|
|
||||||
newEvents = [newEvent, ...events];
|
|
||||||
|
|
||||||
// set to state
|
// set to state
|
||||||
setEvents(newEvents);
|
setEvents(insertItemAt(newEvent, itemIndex));
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteEvent = (position) => {
|
const createDelay = (itemIndex = 0) => {
|
||||||
// ?? Should i reorder after?
|
// make an event
|
||||||
|
// TODO: Replace this with global def somewhere
|
||||||
// remove event from array
|
// TODO: handle random ids better
|
||||||
let filtered = events.filter((e) => e.id !== position);
|
let newEvent = {
|
||||||
|
id: Math.random(),
|
||||||
|
order: itemIndex + 1,
|
||||||
|
timerDuration: 0,
|
||||||
|
type: 'delay',
|
||||||
|
};
|
||||||
|
|
||||||
// set to state
|
// set to state
|
||||||
setEvents(filtered);
|
setEvents(insertItemAt(newEvent, itemIndex));
|
||||||
|
};
|
||||||
|
|
||||||
|
const createBlock = (itemIndex = 0) => {
|
||||||
|
// make an event
|
||||||
|
// TODO: Replace this with global def somewhere
|
||||||
|
// TODO: handle random ids better
|
||||||
|
let newEvent = {
|
||||||
|
id: Math.random(),
|
||||||
|
order: itemIndex + 1,
|
||||||
|
type: 'block',
|
||||||
|
};
|
||||||
|
|
||||||
|
// set to state
|
||||||
|
setEvents(insertItemAt(newEvent, itemIndex));
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteEvent = (index) => {
|
||||||
|
// TODO: This feels weird?
|
||||||
|
let e = events.splice(index, 1);
|
||||||
|
setEvents([...events]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const replaceAt = (array, index, value) => {
|
const replaceAt = (array, index, value) => {
|
||||||
@@ -65,8 +100,10 @@ export default function EventList(props) {
|
|||||||
const updateData = (itemIndex, data) => {
|
const updateData = (itemIndex, data) => {
|
||||||
const newEvents = replaceAt(events, itemIndex, data);
|
const newEvents = replaceAt(events, itemIndex, data);
|
||||||
setEvents(newEvents);
|
setEvents(newEvents);
|
||||||
};
|
}
|
||||||
|
|
||||||
|
console.log('events in event list', events);
|
||||||
|
let cumulativeDelay = 0;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={style.headerButtons}>
|
<div className={style.headerButtons}>
|
||||||
@@ -84,17 +121,46 @@ export default function EventList(props) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={style.eventContainer}>
|
<div className={style.eventContainer}>
|
||||||
{sortByOrderVal(events).map((e, index) => (
|
{events.map((e, index) => {
|
||||||
<EventListItem
|
if (e.type === 'event') {
|
||||||
key={e.id}
|
return (
|
||||||
index={index}
|
<EventListItem
|
||||||
data={e}
|
key={e.id}
|
||||||
selected={e.id === selected}
|
index={index}
|
||||||
createEvent={createEvent}
|
data={e}
|
||||||
deleteEvent={deleteEvent}
|
selected={e.id === selected}
|
||||||
updateData={updateData}
|
createEvent={createEvent}
|
||||||
/>
|
deleteEvent={deleteEvent}
|
||||||
))}
|
createDelay={createDelay}
|
||||||
|
createBlock={createBlock}
|
||||||
|
updateData={updateData}
|
||||||
|
delay={cumulativeDelay}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (e.type === 'block') {
|
||||||
|
cumulativeDelay = 0;
|
||||||
|
return (
|
||||||
|
<BlockBlock
|
||||||
|
key={e.id}
|
||||||
|
index={index}
|
||||||
|
createEvent={createEvent}
|
||||||
|
deleteEvent={deleteEvent}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (e.type === 'delay') {
|
||||||
|
cumulativeDelay = cumulativeDelay + e.timerDuration;
|
||||||
|
return (
|
||||||
|
<DelayBlock
|
||||||
|
key={e.id}
|
||||||
|
index={index}
|
||||||
|
data={e}
|
||||||
|
createEvent={createEvent}
|
||||||
|
deleteEvent={deleteEvent}
|
||||||
|
updateData={updateData}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,14 +12,34 @@ import {
|
|||||||
EditablePreview,
|
EditablePreview,
|
||||||
EditableInput,
|
EditableInput,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
|
import { addMinutes, format } from 'date-fns';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
import { timeFormat, timeToDate } from '../../../common/dateConfig';
|
||||||
import style from './List.module.css';
|
import style from './List.module.css';
|
||||||
|
|
||||||
|
// small shorthand for adding delay and formatting date
|
||||||
|
const addAndFormat = (time, delay) => {
|
||||||
|
return format(addMinutes(time, delay), timeFormat);
|
||||||
|
};
|
||||||
|
|
||||||
export default function EventListItem(props) {
|
export default function EventListItem(props) {
|
||||||
const [more, setMore] = useState(false);
|
const [more, setMore] = useState(false);
|
||||||
const [armed, setArmed] = useState(false);
|
const [armed, setArmed] = useState(false);
|
||||||
|
// const [timeStart, setTimeStart] = useState(
|
||||||
|
// addAndFormat(props.data.timeStart, props.delay)
|
||||||
|
// );
|
||||||
|
const [timeStart, setTimeStart] = useState(0);
|
||||||
|
const [timeEnd, setTimeEnd] = useState(
|
||||||
|
addAndFormat(props.data.timeEnd, props.delay)
|
||||||
|
);
|
||||||
|
|
||||||
const { data, selected, ...rest } = props;
|
const { data, selected, delay, ...rest } = props;
|
||||||
|
|
||||||
|
// prepare time fields
|
||||||
|
useEffect(() => {
|
||||||
|
setTimeStart(addAndFormat(props.data.timeStart, props.delay));
|
||||||
|
setTimeEnd(addAndFormat(props.data.timeEnd, props.delay));
|
||||||
|
}, [props.data, props.delay]);
|
||||||
|
|
||||||
const updateValues = (field, value) => {
|
const updateValues = (field, value) => {
|
||||||
// validate field
|
// validate field
|
||||||
@@ -43,10 +63,12 @@ export default function EventListItem(props) {
|
|||||||
/>
|
/>
|
||||||
<div className={style.time}>
|
<div className={style.time}>
|
||||||
<Editable
|
<Editable
|
||||||
onSubmit={(v) => updateValues('timerStart', v)}
|
onSubmit={(v) => updateValues('timeStart', timeToDate(v))}
|
||||||
defaultValue={data.timerStart}
|
value={timeStart}
|
||||||
|
onChange={(val) => setTimeStart(val)}
|
||||||
placeholder='--:--'
|
placeholder='--:--'
|
||||||
style={{ textAlign: 'center' }}
|
style={{ textAlign: 'center' }}
|
||||||
|
className={delay > 0 && style.delayedEditable}
|
||||||
>
|
>
|
||||||
<EditablePreview />
|
<EditablePreview />
|
||||||
<EditableInput type='time' min='00:00' max='23:59' />
|
<EditableInput type='time' min='00:00' max='23:59' />
|
||||||
@@ -54,10 +76,12 @@ export default function EventListItem(props) {
|
|||||||
</div>
|
</div>
|
||||||
<div className={style.time}>
|
<div className={style.time}>
|
||||||
<Editable
|
<Editable
|
||||||
onSubmit={(v) => updateValues('timerEnd', v)}
|
onSubmit={(v) => updateValues('timeEnd', timeToDate(v))}
|
||||||
defaultValue={data.timerEnd}
|
value={timeEnd}
|
||||||
|
onChange={(val) => setTimeEnd(val)}
|
||||||
placeholder='--:--'
|
placeholder='--:--'
|
||||||
style={{ textAlign: 'center' }}
|
style={{ textAlign: 'center' }}
|
||||||
|
className={delay > 0 && style.delayedEditable}
|
||||||
>
|
>
|
||||||
<EditablePreview />
|
<EditablePreview />
|
||||||
<EditableInput type='time' min='00:00' max='23:59' />
|
<EditableInput type='time' min='00:00' max='23:59' />
|
||||||
@@ -129,19 +153,25 @@ export default function EventListItem(props) {
|
|||||||
size='xs'
|
size='xs'
|
||||||
icon={<MinusIcon />}
|
icon={<MinusIcon />}
|
||||||
colorScheme='red'
|
colorScheme='red'
|
||||||
onClick={() => props.deleteEvent(data.id)}
|
onClick={() => props.deleteEvent(props.index)}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
size='xs'
|
size='xs'
|
||||||
icon={<AddIcon />}
|
icon={<AddIcon />}
|
||||||
colorScheme='blue'
|
colorScheme='blue'
|
||||||
onClick={() => props.createEvent(data.order)}
|
onClick={() => props.createEvent(props.index)}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
size='xs'
|
||||||
|
icon={<TimeIcon />}
|
||||||
|
colorScheme='yellow'
|
||||||
|
onClick={() => props.createDelay(props.index)}
|
||||||
/>
|
/>
|
||||||
<IconButton size='xs' icon={<TimeIcon />} colorScheme='yellow' />
|
|
||||||
<IconButton
|
<IconButton
|
||||||
size='xs'
|
size='xs'
|
||||||
icon={<NotAllowedIcon />}
|
icon={<NotAllowedIcon />}
|
||||||
colorScheme='purple'
|
colorScheme='purple'
|
||||||
|
onClick={() => props.createBlock(props.index)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -160,6 +160,10 @@
|
|||||||
border: 1px solid #0001;
|
border: 1px solid #0001;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.delayedEditable {
|
||||||
|
background-color: #ecc94b55;
|
||||||
|
}
|
||||||
|
|
||||||
/* ============= BLOCK BLOCK ============= */
|
/* ============= BLOCK BLOCK ============= */
|
||||||
.blockContainer {
|
.blockContainer {
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -170,8 +174,14 @@
|
|||||||
background-color: #805ad5aa;
|
background-color: #805ad5aa;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
color: white;
|
color: white;
|
||||||
height: 2em;
|
height: 3em;
|
||||||
border: 1px solid #0001;
|
border: 1px solid #0001;
|
||||||
border-bottom: 8px solid #805ad5;
|
border-bottom: 8px solid #805ad5;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
margin: auto;
|
||||||
|
gap: 1em;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user