From d24fb406afa0d684ae795334e9899542f1278a15 Mon Sep 17 00:00:00 2001
From: cv <34649812+cpvalente@users.noreply.github.com>
Date: Mon, 10 May 2021 21:31:16 +0200
Subject: [PATCH] major: event block redesign
---
.../components/buttons/DeleteIconBtn.jsx | 4 +-
.../components/buttons/TrashIconBtn.jsx | 16 +
.../components/buttons/VisibleIconBtn.jsx | 6 +-
.../components/eventTimes/EventTimes.jsx | 6 +-
.../eventTimes/EventTimesVertical.jsx | 129 ++++++++
client/src/common/dateConfig.js | 3 +-
.../input/{TimeInput.jsx => DelayInput.jsx} | 7 +-
client/src/common/input/DelayValue.jsx | 12 -
client/src/common/input/EditableText.jsx | 14 +-
.../src/common/input/EditableText.module.css | 14 +-
client/src/common/input/EditableTimer.jsx | 30 +-
.../src/common/input/EditableTimer.module.css | 8 +-
.../src/features/control/MessageControl.jsx | 6 +-
.../features/editors/list/ActionButtons.jsx | 8 +-
.../features/editors/list/Block.module.css | 186 -----------
.../src/features/editors/list/BlockBlock.jsx | 23 +-
.../editors/list/BlockBlock.module.css | 58 ++++
.../src/features/editors/list/DelayBlock.jsx | 30 +-
.../editors/list/DelayBlock.module.css | 66 ++++
.../features/editors/list/EventBlock copy.jsx | 188 +++++++++++
.../src/features/editors/list/EventBlock.jsx | 298 ++++++++++--------
.../editors/list/EventBlock.module.css | 185 +++++++++++
.../features/editors/list/EventListItem.jsx | 52 ++-
23 files changed, 921 insertions(+), 428 deletions(-)
create mode 100644 client/src/common/components/buttons/TrashIconBtn.jsx
create mode 100644 client/src/common/components/eventTimes/EventTimesVertical.jsx
rename client/src/common/input/{TimeInput.jsx => DelayInput.jsx} (83%)
delete mode 100644 client/src/common/input/DelayValue.jsx
delete mode 100644 client/src/features/editors/list/Block.module.css
create mode 100644 client/src/features/editors/list/BlockBlock.module.css
create mode 100644 client/src/features/editors/list/DelayBlock.module.css
create mode 100644 client/src/features/editors/list/EventBlock copy.jsx
create mode 100644 client/src/features/editors/list/EventBlock.module.css
diff --git a/client/src/common/components/buttons/DeleteIconBtn.jsx b/client/src/common/components/buttons/DeleteIconBtn.jsx
index 0e6f18132..eb4fea6b4 100644
--- a/client/src/common/components/buttons/DeleteIconBtn.jsx
+++ b/client/src/common/components/buttons/DeleteIconBtn.jsx
@@ -2,13 +2,13 @@ import { IconButton } from '@chakra-ui/button';
import { FiMinus } from 'react-icons/fi';
export default function DeleteIconBtn(props) {
- const { clickhandler, ...rest } = props;
+ const { actionHandler, ...rest } = props;
return (
}
colorScheme='red'
- onClick={clickhandler}
+ onClick={() => actionHandler('delete')}
_focus={{ boxShadow: 'none' }}
{...rest}
/>
diff --git a/client/src/common/components/buttons/TrashIconBtn.jsx b/client/src/common/components/buttons/TrashIconBtn.jsx
new file mode 100644
index 000000000..20b9aef38
--- /dev/null
+++ b/client/src/common/components/buttons/TrashIconBtn.jsx
@@ -0,0 +1,16 @@
+import { IconButton } from '@chakra-ui/button';
+import { FiTrash2 } from 'react-icons/fi';
+
+export default function TrashIconBtn(props) {
+ const { clickhandler, ...rest } = props;
+ return (
+ }
+ colorScheme='red'
+ onClick={clickhandler}
+ _focus={{ boxShadow: 'none' }}
+ {...rest}
+ />
+ );
+}
diff --git a/client/src/common/components/buttons/VisibleIconBtn.jsx b/client/src/common/components/buttons/VisibleIconBtn.jsx
index db06c8e34..ba2f4ec0f 100644
--- a/client/src/common/components/buttons/VisibleIconBtn.jsx
+++ b/client/src/common/components/buttons/VisibleIconBtn.jsx
@@ -2,14 +2,16 @@ import { IconButton } from '@chakra-ui/button';
import { FiSun } from 'react-icons/fi';
export default function VisibleIconBtn(props) {
- const { clickhandler, active, ...rest } = props;
+ const { actionHandler, active, ...rest } = props;
return (
}
colorScheme='blue'
variant={active ? 'solid' : 'outline'}
- onClick={clickhandler}
+ onClick={() =>
+ actionHandler('update', { field: 'isPublic', value: !active })
+ }
_focus={{ boxShadow: 'none' }}
{...rest}
/>
diff --git a/client/src/common/components/eventTimes/EventTimes.jsx b/client/src/common/components/eventTimes/EventTimes.jsx
index c680cce07..568b40b2e 100644
--- a/client/src/common/components/eventTimes/EventTimes.jsx
+++ b/client/src/common/components/eventTimes/EventTimes.jsx
@@ -2,7 +2,7 @@ import EditableTimer from '../../input/EditableTimer';
import { showWarningToast } from '../../helpers/toastManager';
export default function EventTimes(props) {
- const { updateValues, delay, timeStart, timeEnd } = props;
+ const { actionHandler, delay, timeStart, timeEnd } = props;
const handleValidate = (entry, v) => {
// we dont inforce validation here
@@ -26,14 +26,14 @@ export default function EventTimes(props) {
diff --git a/client/src/common/components/eventTimes/EventTimesVertical.jsx b/client/src/common/components/eventTimes/EventTimesVertical.jsx
new file mode 100644
index 000000000..b9517db1b
--- /dev/null
+++ b/client/src/common/components/eventTimes/EventTimesVertical.jsx
@@ -0,0 +1,129 @@
+import EditableTimer from '../../input/EditableTimer';
+import { showWarningToast } from '../../helpers/toastManager';
+import { useEffect, useState } from 'react';
+import { millisToMinutes, stringFromMillis } from '../../dateConfig';
+
+const label = {
+ fontSize: '0.75em',
+ color: '#aaa',
+};
+
+const TimesDelayed = (props) => {
+ const { handleValidate, actionHandler, delay, timeStart, timeEnd } = props;
+
+ const scheduledStart = stringFromMillis(timeStart, false);
+
+ const scheduledEnd = stringFromMillis(timeEnd, false);
+
+ return (
+ <>
+
+ Start {scheduledStart}
+
+
+
+ End {scheduledEnd}
+
+
+ Duration
+
+ >
+ );
+};
+
+const Times = (props) => {
+ const { handleValidate, actionHandler, timeStart, timeEnd, duration } = props;
+
+ return (
+ <>
+ Start
+
+ End
+
+ Duration (min)
+
+ >
+ );
+};
+
+export default function EventTimesVertical(props) {
+ const { delay, timeStart, timeEnd } = props;
+ const [duration, setDuration] = useState(
+ millisToMinutes(timeEnd - timeStart) || 0
+ );
+ useEffect(() => {
+ setDuration(millisToMinutes(timeEnd - timeStart));
+ }, [timeStart, timeEnd]);
+
+ const handleValidate = (entry, v) => {
+ // we dont enforce validation here
+
+ if (v == null || timeStart == null || timeEnd == null) return true;
+ if (timeStart === 0) return true;
+
+ let validate = { value: true, catch: '' };
+ if (entry === 'timeStart' && v > timeEnd)
+ validate.catch = 'Start time later than end time';
+ else if (entry === 'timeEnd' && v < timeStart)
+ validate.catch = 'End time earlier than start time';
+
+ if (validate.catch !== '')
+ showWarningToast('Time Input Warning', validate.catch);
+ return validate.value;
+ };
+
+ return (delay != null) & (delay > 0) ? (
+
+ ) : (
+
+ );
+}
diff --git a/client/src/common/dateConfig.js b/client/src/common/dateConfig.js
index a89197bc0..857812c4f 100644
--- a/client/src/common/dateConfig.js
+++ b/client/src/common/dateConfig.js
@@ -8,11 +8,12 @@ export const stringFromMillis = (
delim = ':',
ifNull = '...'
) => {
- if (ms === null) return ifNull;
+ if (ms === null || isNaN(ms)) return ifNull;
const showWith0 = (value) => (value < 10 ? `0${value}` : value);
const hours = showWith0(Math.floor(((ms / (1000 * 60 * 60)) % 60) % 24));
const minutes = showWith0(Math.floor((ms / (1000 * 60)) % 60));
const seconds = showWith0(Math.floor((ms / 1000) % 60));
+
return showSeconds
? `${
parseInt(hours) ? `${hours}${delim}` : `00${delim}`
diff --git a/client/src/common/input/TimeInput.jsx b/client/src/common/input/DelayInput.jsx
similarity index 83%
rename from client/src/common/input/TimeInput.jsx
rename to client/src/common/input/DelayInput.jsx
index 9ac84a6ef..8a1fba8e9 100644
--- a/client/src/common/input/TimeInput.jsx
+++ b/client/src/common/input/DelayInput.jsx
@@ -13,7 +13,7 @@ const inputProps = {
textAlign: 'center',
};
-export default function TimeInput(props) {
+export default function DelayInput(props) {
const [value, setValue] = useState(props.value);
useEffect(() => {
@@ -29,9 +29,10 @@ export default function TimeInput(props) {
if (val === props.value) return;
if (val === '') setValue(0);
if (val >= 0 && val <= 60) {
- props.submitHandler(val);
+ // convert to ms and updates
+ props.actionHandler('update', { field: 'duration', value: val * 60000 });
} else {
- setValue(60);
+ props.actionHandler('update', { field: 'duration', value: 3600000 });
}
};
diff --git a/client/src/common/input/DelayValue.jsx b/client/src/common/input/DelayValue.jsx
deleted file mode 100644
index 0d6879712..000000000
--- a/client/src/common/input/DelayValue.jsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import { millisToMinutes } from '../dateConfig';
-import style from '../../features/editors/list/Block.module.css';
-
-export default function DelayValue(props) {
- const { delay } = props;
- const delayed = delay > 0;
- return (
-
- {delayed && {`+ ${millisToMinutes(delay)}`}}
-
- );
-}
diff --git a/client/src/common/input/EditableText.jsx b/client/src/common/input/EditableText.jsx
index 0e13627ad..73739d6c2 100644
--- a/client/src/common/input/EditableText.jsx
+++ b/client/src/common/input/EditableText.jsx
@@ -4,7 +4,7 @@ import style from './EditableText.module.css';
export default function EditableText(props) {
const { label, defaultValue, placeholder, submitHandler } = props;
- const [text, setText] = useState('');
+ const [text, setText] = useState(defaultValue || '');
useEffect(() => {
if (defaultValue == null) setText('');
@@ -19,14 +19,11 @@ export default function EditableText(props) {
const handleChange = (val) => {
if (val.length < 40) setText(val);
- console.log(val.length);
};
return (
-
- {label}
-
+ {label}
handleChange(v)}
onSubmit={(v) => handleSubmit(v)}
@@ -34,8 +31,11 @@ export default function EditableText(props) {
placeholder={placeholder}
className={style.inline}
>
-
-
+
+
);
diff --git a/client/src/common/input/EditableText.module.css b/client/src/common/input/EditableText.module.css
index 735435bca..d1edacc92 100644
--- a/client/src/common/input/EditableText.module.css
+++ b/client/src/common/input/EditableText.module.css
@@ -1,24 +1,16 @@
-.title,
-.titleUnderlined {
+.title {
padding-left: 1em;
- font-size: 0.8em;
+ font-size: 0.75em;
color: #aaa;
display: inline-block;
width: 6em;
- cursor: default;
-}
-
-.titleUnderlined {
- border-bottom: 1px solid rgba(255, 255, 255, 0.15);
}
.block {
display: 'block';
- overflow: hidden;
- max-width: 100%;
+ overflow-x: hidden;
}
.inline {
display: inline;
- width: max-content;
}
diff --git a/client/src/common/input/EditableTimer.jsx b/client/src/common/input/EditableTimer.jsx
index 26fd1e835..2c771dbbf 100644
--- a/client/src/common/input/EditableTimer.jsx
+++ b/client/src/common/input/EditableTimer.jsx
@@ -9,7 +9,7 @@ import { showErrorToast } from '../helpers/toastManager';
import style from './EditableTimer.module.css';
export default function EditableTimer(props) {
- const { name, updateValues, time, delay, validate } = props;
+ const { name, actionHandler, time, delay, validate } = props;
const [value, setValue] = useState('');
// prepare time fields
@@ -46,7 +46,7 @@ export default function EditableTimer(props) {
if (!validate(name, millis)) return false;
// update entry
- updateValues(name, millis);
+ actionHandler('update', { field: name, value: millis });
return true;
};
@@ -56,19 +56,17 @@ export default function EditableTimer(props) {
};
return (
-
- showOriginal()}
- onEdit={() => showOriginal}
- onChange={(v) => setValue(v)}
- onSubmit={(v) => validateValue(v)}
- value={value}
- placeholder='--:--'
- className={delay > 0 ? style.delayedEditable : style.editable}
- >
-
-
-
-
+ showOriginal}
+ onEdit={() => showOriginal}
+ onChange={(v) => setValue(v)}
+ onSubmit={(v) => validateValue(v)}
+ value={value}
+ placeholder='--:--'
+ className={delay > 0 ? style.delayedEditable : style.editable}
+ >
+
+
+
);
}
diff --git a/client/src/common/input/EditableTimer.module.css b/client/src/common/input/EditableTimer.module.css
index 4cd23a677..d10c24948 100644
--- a/client/src/common/input/EditableTimer.module.css
+++ b/client/src/common/input/EditableTimer.module.css
@@ -1,15 +1,13 @@
-.time {
+.editable,
+.delayedEditable {
background-color: rgba(255, 255, 255, 0.05);
+ border: 1px solid rgba(255, 255, 255, 0.05);
border-radius: 4px;
width: 5em;
height: fit-content;
-}
-.editable,
-.delayedEditable {
text-align: center;
border-radius: 4px;
- border: 1px solid rgba(255, 255, 255, 0.05);
}
.delayedEditable {
diff --git a/client/src/features/control/MessageControl.jsx b/client/src/features/control/MessageControl.jsx
index 4a95d6b12..2e549df34 100644
--- a/client/src/features/control/MessageControl.jsx
+++ b/client/src/features/control/MessageControl.jsx
@@ -94,7 +94,7 @@ export default function MessageControl() {
messageControl('toggle-pres-visible')}
+ actionHandler={() => messageControl('toggle-pres-visible')}
{...inputProps}
/>
@@ -114,7 +114,7 @@ export default function MessageControl() {
messageControl('toggle-publ-visible')}
+ actionHandler={() => messageControl('toggle-publ-visible')}
{...inputProps}
/>
@@ -135,7 +135,7 @@ export default function MessageControl() {
messageControl('toggle-lower-visible')}
+ actionHandler={() => messageControl('toggle-lower-visible')}
{...inputProps}
/>
diff --git a/client/src/features/editors/list/ActionButtons.jsx b/client/src/features/editors/list/ActionButtons.jsx
index 316c059de..8c1d2bff4 100644
--- a/client/src/features/editors/list/ActionButtons.jsx
+++ b/client/src/features/editors/list/ActionButtons.jsx
@@ -3,7 +3,7 @@ import { IconButton } from '@chakra-ui/button';
import { FiZap, FiPlus, FiMinusCircle, FiClock } from 'react-icons/fi';
export default function ActionButtons(props) {
- const { showAdd, showDelay, showBlock } = props;
+ const { showAdd, showDelay, showBlock, actionHandler } = props;
const menuStyle = {
color: '#000000',
@@ -25,7 +25,7 @@ export default function ActionButtons(props) {
}
- onClick={props.addHandler}
+ onClick={() => actionHandler('event')}
isDisabled={!showAdd}
>
Event next
@@ -33,14 +33,14 @@ export default function ActionButtons(props) {
}
- onClick={props.delayHandler}
+ onClick={() => actionHandler('delay')}
isDisabled={!showDelay}
>
Delay next
}
- onClick={props.blockHandler}
+ onClick={() => actionHandler('block')}
isDisabled={!showBlock}
>
Block next
diff --git a/client/src/features/editors/list/Block.module.css b/client/src/features/editors/list/Block.module.css
deleted file mode 100644
index 3161dad70..000000000
--- a/client/src/features/editors/list/Block.module.css
+++ /dev/null
@@ -1,186 +0,0 @@
-/* ============= COMMON ============= */
-
-.eventRow,
-.eventRowActive,
-.block,
-.delay {
- position: relative;
- margin: 0.2em 0;
- padding: 0.2em 0.5em;
-
- width: 100%;
- border-radius: 8px;
- font-size: 15px;
- border: 1px solid rgba(255, 255, 255, 0.05);
-}
-
-.block,
-.delay {
- box-sizing: border-box;
- display: flex;
- gap: 1em;
-}
-
-.actionOverlay {
- display: flex;
- flex-direction: row;
- padding-top: 0.2em;
- gap: 0.5em;
- align-content: center;
- align-self: flex-start;
-
- opacity: 0.8;
- transition: linear 0.1s;
-}
-
-.eventRow:hover > .actionOverlay,
-.eventRowActive:hover > .actionOverlay,
-.block:hover > .actionOverlay,
-.delay:hover > .actionOverlay {
- opacity: 1;
- transition: linear 0.1s;
-}
-
-/* NA */
-.actionOverlay:hover {
- opacity: 1;
-}
-/* ============= EVENT ITEM ============= */
-
-.eventRow,
-.eventRowActive {
- display: grid;
- grid-template-columns: 2em 2em auto auto 1fr auto;
-
- justify-content: center;
- align-content: center;
- align-items: baseline;
- gap: 0.5em;
-}
-
-.eventRow {
- background-color: rgba(255, 255, 255, 0.02);
-}
-
-.eventRowActive {
- background-color: rgba(255, 255, 255, 0.36);
- background: linear-gradient(
- 180deg,
- #00000000 10%,
- #4bffab10 89%,
- #227c52aa 90%,
- #4bffabcc 100%
- );
-}
-
-.drag {
- color: rgba(255, 255, 255, 0.37);
- align-self: center;
-}
-
-.indicators {
- display: flex;
- flex-direction: column;
- align-self: flex-start;
- font-size: 0.75em;
- overflow: hidden;
- min-width: 0;
- justify-content: center;
-}
-
-.next,
-.nextDisabled {
- width: max-content;
- padding: 0 0.2em;
- color: #4bffab;
- transition: 0.3s;
-}
-
-.next {
- opacity: 0.8;
-}
-
-.nextDisabled {
- opacity: 0.05;
-}
-
-.delayValue {
- color: #d69e2eaa;
- padding: 0 0.2em;
- text-align: center;
- align-self: flex-start;
- width: max-content;
-}
-
-.next:after,
-.delayValue:after {
- content: '\200b';
-}
-
-.titleContainer,
-.detailedContainer {
- background-color: rgba(255, 255, 255, 0.05);
- border: 1px solid rgba(255, 255, 255, 0.05);
- border-radius: 4px;
-}
-
-.rowDetailed {
- display: inline-flex;
- position: relative;
- align-self: flex-start;
-}
-
-.titleContainer,
-.detailedContainer {
- width: 90%;
- display: block;
-}
-
-.more {
- position: absolute;
- right: 0.25em;
- top: 0.5em;
-}
-
-/* ============= BLOCK BLOCK ============= */
-.block {
- background-color: #805ad5;
- background: linear-gradient(
- 0deg,
- rgba(107, 70, 193, 0.4) 0%,
- rgba(128, 90, 213, 0.4) 20%,
- rgba(107, 70, 193, 0.15) 21%
- );
-
- display: grid;
- grid-template-columns: 2em auto;
- align-content: center;
- align-items: baseline;
- gap: 0.5em;
-}
-
-.block > .drag {
- justify-self: start;
-}
-
-.block > .actionOverlay {
- justify-self: end;
-}
-
-/* ============= DELAY BLOCK ============= */
-.delay {
- background-color: #ecc94b;
- background: linear-gradient(
- 180deg,
- rgba(214, 158, 46, 0.4) 0%,
- rgba(236, 201, 75, 0.4) 20%,
- rgba(214, 158, 46, 0.07) 21%
- );
-
- display: grid;
- grid-template-columns: 2em 1fr auto;
- justify-content: center;
- align-content: center;
- align-items: baseline;
- gap: 0.5em;
-}
diff --git a/client/src/features/editors/list/BlockBlock.jsx b/client/src/features/editors/list/BlockBlock.jsx
index ee856ea77..83851ba2e 100644
--- a/client/src/features/editors/list/BlockBlock.jsx
+++ b/client/src/features/editors/list/BlockBlock.jsx
@@ -2,20 +2,10 @@ import { Draggable } from 'react-beautiful-dnd';
import { FiMoreVertical } from 'react-icons/fi';
import DeleteIconBtn from '../../../common/components/buttons/DeleteIconBtn';
import ActionButtons from './ActionButtons';
-import style from './Block.module.css';
+import style from './BlockBlock.module.css';
export default function BlockBlock(props) {
- const { eventsHandler, index, data } = props;
-
- const addHandler = () => {
- eventsHandler('add', { type: 'event', order: index + 1 });
- };
- const deleteHandler = () => {
- eventsHandler('delete', data.id);
- };
- const delayHandler = () => {
- eventsHandler('add', { type: 'delay', order: index + 1 });
- };
+ const { index, data, actionHandler } = props;
return (
@@ -29,13 +19,8 @@ export default function BlockBlock(props) {
)}
diff --git a/client/src/features/editors/list/BlockBlock.module.css b/client/src/features/editors/list/BlockBlock.module.css
new file mode 100644
index 000000000..eadc8bcae
--- /dev/null
+++ b/client/src/features/editors/list/BlockBlock.module.css
@@ -0,0 +1,58 @@
+/* ============= COMMON ============= */
+
+.block {
+ margin: 0.2em 0;
+ padding: 0.2em 0.5em;
+ box-sizing: border-box;
+
+ width: 100%;
+ border-radius: 8px;
+ font-size: 15px;
+ border: 1px solid rgba(255, 255, 255, 0.05);
+
+ display: grid;
+ grid-template-columns: 2em auto;
+ align-content: center;
+ align-items: baseline;
+ gap: 0.5em;
+
+ background-color: #805ad5;
+ background: linear-gradient(
+ 0deg,
+ rgba(107, 70, 193, 0.4) 0%,
+ rgba(128, 90, 213, 0.4) 20%,
+ rgba(107, 70, 193, 0.15) 21%
+ );
+}
+
+/* ================ DRAG ================ */
+.drag {
+ color: rgba(255, 255, 255, 0.67);
+ align-self: center;
+ justify-self: start;
+}
+
+/* ============== ACTION ================ */
+.actionOverlay {
+ display: flex;
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ align-content: center;
+ align-self: flex-start;
+ padding-top: 0.2em;
+ gap: 0.5em;
+ opacity: 0.8;
+ transition: linear 0.1s;
+ justify-self: end;
+}
+
+.block:hover > .actionOverlay {
+ opacity: 1;
+ transition: linear 0.1s;
+}
+
+/* NA */
+.actionOverlay:hover {
+ opacity: 1;
+}
diff --git a/client/src/features/editors/list/DelayBlock.jsx b/client/src/features/editors/list/DelayBlock.jsx
index 4ee8e1ac3..cfb865729 100644
--- a/client/src/features/editors/list/DelayBlock.jsx
+++ b/client/src/features/editors/list/DelayBlock.jsx
@@ -1,27 +1,14 @@
import { Draggable } from 'react-beautiful-dnd';
import { FiMoreVertical } from 'react-icons/fi';
import { millisToMinutes } from '../../../common/dateConfig';
-import TimeInput from '../../../common/input/TimeInput';
-import style from './Block.module.css';
import ActionButtons from './ActionButtons';
import DeleteIconBtn from '../../../common/components/buttons/DeleteIconBtn';
import ApplyIconBtn from '../../../common/components/buttons/ApplyIconBtn';
+import DelayInput from '../../../common/input/DelayInput';
+import style from './DelayBlock.module.css';
export default function DelayBlock(props) {
- const { data, eventsHandler, index } = props;
-
- const addHandler = () => {
- eventsHandler('add', { type: 'event', order: index + 1 });
- };
-
- const submitHandler = (value) => {
- // convert to ms and patch
- eventsHandler('patch', { id: data.id, duration: value * 1000 * 60 });
- };
-
- const deleteHandler = () => {
- eventsHandler('delete', data.id);
- };
+ const { eventsHandler, data, index, actionHandler } = props;
const applyDelayHandler = () => {
eventsHandler('applyDelay', { id: data.id, duration: data.duration });
@@ -29,6 +16,7 @@ export default function DelayBlock(props) {
let delayValue =
data.duration != null ? millisToMinutes(data.duration) : undefined;
+
return (
{(provided) => (
@@ -40,11 +28,15 @@ export default function DelayBlock(props) {
-
+
)}
diff --git a/client/src/features/editors/list/DelayBlock.module.css b/client/src/features/editors/list/DelayBlock.module.css
new file mode 100644
index 000000000..a1b226c98
--- /dev/null
+++ b/client/src/features/editors/list/DelayBlock.module.css
@@ -0,0 +1,66 @@
+/* ============= COMMON ============= */
+
+.delay {
+ margin: 0.2em 0;
+ padding: 0.2em 0.5em;
+
+ width: 100%;
+ border-radius: 8px;
+ font-size: 15px;
+ border: 1px solid rgba(255, 255, 255, 0.05);
+
+ display: grid;
+ grid-template-columns: 2em 1fr auto;
+ grid-template-areas: 'drag inpt btns';
+ justify-content: center;
+ align-content: center;
+ align-items: baseline;
+ gap: 0.5em;
+
+ background-color: #ecc94b;
+ background: linear-gradient(
+ 180deg,
+ rgba(214, 158, 46, 0.4) 0%,
+ rgba(236, 201, 75, 0.4) 20%,
+ rgba(214, 158, 46, 0.07) 21%
+ );
+}
+
+/* ================ DRAG ================ */
+
+.drag {
+ grid-area: drag;
+ color: rgba(255, 255, 255, 0.67);
+ align-self: center;
+}
+
+/* =============== INPUT ================ */
+
+.input {
+ grid-area: inpt;
+}
+
+/* ============== ACTION ================ */
+
+.actionOverlay {
+ grid-area: btns;
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ align-content: center;
+ align-self: flex-start;
+ padding-top: 0.2em;
+ gap: 0.5em;
+ opacity: 0.8;
+ transition: linear 0.1s;
+}
+
+.delay:hover > .actionOverlay {
+ opacity: 1;
+ transition: linear 0.1s;
+}
+
+/* NA */
+.actionOverlay:hover {
+ opacity: 1;
+}
diff --git a/client/src/features/editors/list/EventBlock copy.jsx b/client/src/features/editors/list/EventBlock copy.jsx
new file mode 100644
index 000000000..e5b9926d8
--- /dev/null
+++ b/client/src/features/editors/list/EventBlock copy.jsx
@@ -0,0 +1,188 @@
+import { FiChevronDown, FiChevronUp, FiMoreVertical } from 'react-icons/fi';
+import { memo, useCallback, useEffect, useState } from 'react';
+import { showErrorToast } from '../../../common/helpers/toastManager';
+import { Draggable } from 'react-beautiful-dnd';
+import EventTimes from '../../../common/components/eventTimes/EventTimes';
+// import common from './Block.module.css';
+import style from './EventBlock.module.css';
+import EditableText from '../../../common/input/EditableText';
+import DelayValue from '../../../common/input/DelayValue';
+import ActionButtons from './ActionButtons';
+import VisibleIconBtn from '../../../common/components/buttons/VisibleIconBtn';
+import DeleteIconBtn from '../../../common/components/buttons/DeleteIconBtn';
+
+const areEqual = (prevProps, nextProps) => {
+ return (
+ prevProps.data.revision === nextProps.data.revision &&
+ prevProps.selected === nextProps.selected &&
+ prevProps.next === nextProps.next &&
+ prevProps.index === nextProps.index &&
+ prevProps.delay === nextProps.delay
+ );
+};
+
+const EventBlock = (props) => {
+ const { data, selected, next, delay, index, eventsHandler } = props;
+
+ const [more, setMore] = useState(true);
+ const [visible, setVisible] = useState(data.isPublic || false);
+
+ // Set visibility indicator
+ useEffect(() => {
+ setVisible(data.isPublic);
+ }, [data.isPublic]);
+
+ // Create / delete new events
+ const actionHandler = (action) => {
+ switch (action) {
+ case 'event':
+ eventsHandler('add', { type: 'event', order: index + 1 });
+ break;
+ case 'delay':
+ eventsHandler('add', { type: 'delay', order: index + 1 });
+ break;
+ case 'block':
+ eventsHandler('add', { type: 'block', order: index + 1 });
+ break;
+ case 'delete':
+ eventsHandler('delete', data.id);
+ break;
+ default:
+ break;
+ }
+ };
+
+ // Update data in fields
+ const updateValues = (field, value) => {
+ // validate field
+ if (field in data) {
+ // create object with new field
+ const newData = { id: data.id, [field]: value };
+
+ // request update in parent
+ eventsHandler('patch', newData);
+ } else {
+ showErrorToast('Field Error: ' + field);
+ }
+ };
+
+ const handleTitleSubmit = (v) => {
+ updateValues('title', v);
+ };
+
+ const handleSubtitleSubmit = (v) => {
+ updateValues('subtitle', v);
+ };
+
+ const handlePresenterSubmit = (v) => {
+ updateValues('presenter', v);
+ };
+
+ const handleNoteSubmit = (v) => {
+ updateValues('note', v);
+ };
+
+ const handleVisibleToggle = useCallback(() => {
+ const viz = !data.isPublic || !visible;
+ updateValues('isPublic', viz);
+ }, [data.isPublic, visible, updateValues]);
+
+ // TODO: inside useEffect()
+ const isSelected = selected ? style.active : '';
+ const isExpanded = more ? style.expanded : style.collapsed;
+ const classSelect = ` ${style.event} ${isExpanded} ${isSelected}`;
+
+ return (
+
+ {(provided) => (
+
+
+
+
+
+
+ {more &&
Start Time
}
+ {more &&
End Time
}
+ {more &&
Duration
}
+
+
+ {more ? (
+
+
+
+
+
+
+ ) : (
+
+
+
+ )}
+
setMore(!more)}>
+ {more ? : }
+
+
+
+
+ )}
+
+ );
+};
+
+export default memo(EventBlock, areEqual);
+// export default EventBlock;
diff --git a/client/src/features/editors/list/EventBlock.jsx b/client/src/features/editors/list/EventBlock.jsx
index 833bb85f4..6e9d045c3 100644
--- a/client/src/features/editors/list/EventBlock.jsx
+++ b/client/src/features/editors/list/EventBlock.jsx
@@ -1,14 +1,15 @@
+import Icon from '@chakra-ui/icon';
import { FiChevronDown, FiChevronUp, FiMoreVertical } from 'react-icons/fi';
-import { memo, useEffect, useState } from 'react';
-import { showErrorToast } from '../../../common/helpers/toastManager';
+import { memo, useState } from 'react';
import { Draggable } from 'react-beautiful-dnd';
import EventTimes from '../../../common/components/eventTimes/EventTimes';
-import style from './Block.module.css';
+import EventTimesVertical from '../../../common/components/eventTimes/EventTimesVertical';
import EditableText from '../../../common/input/EditableText';
-import DelayValue from '../../../common/input/DelayValue';
import ActionButtons from './ActionButtons';
import VisibleIconBtn from '../../../common/components/buttons/VisibleIconBtn';
import DeleteIconBtn from '../../../common/components/buttons/DeleteIconBtn';
+import { millisToMinutes } from '../../../common/dateConfig';
+import style from './EventBlock.module.css';
const areEqual = (prevProps, nextProps) => {
return (
@@ -20,147 +21,181 @@ const areEqual = (prevProps, nextProps) => {
);
};
+const ExpandedBlock = (props) => {
+ const { provided, data, next, delay, delayValue, actionHandler } = props;
+
+ return (
+ <>
+
+
+
+
+
+ Next
+ {delayValue != null && (
+ + {delayValue}
+ )}
+
+
+
+
+
+
+
+ actionHandler('update', { field: 'title', value: v })
+ }
+ />
+
+ actionHandler('update', { field: 'presenter', value: v })
+ }
+ />
+
+ actionHandler('update', { field: 'subtitle', value: v })
+ }
+ />
+
+ actionHandler('update', { field: 'note', value: v })
+ }
+ />
+
+ props.setExpanded(false)}
+ />
+
+ >
+ );
+};
+
+const CollapsedBlock = (props) => {
+ const { provided, data, next, delay, delayValue, actionHandler } = props;
+
+ return (
+ <>
+
+
+
+
+
+ Next
+ {delayValue != null && (
+ + {delayValue}
+ )}
+
+
+
+
+ actionHandler('update', { field: 'title', value: v })
+ }
+ />
+
+ props.setExpanded(true)}
+ />
+
+ >
+ );
+};
+
const EventBlock = (props) => {
- const { data, selected, next, delay, index, eventsHandler } = props;
+ const { data, selected, delay, index, actionHandler } = props;
- const [more, setMore] = useState(true);
- const [visible, setVisible] = useState(data.isPublic || false);
+ const [expanded, setExpanded] = useState(true);
- // Set visibility indicator
- useEffect(() => {
- setVisible(data.isPublic);
- }, [data.isPublic]);
+ // TODO: should this go inside useEffect()
+ // Would I then need to add this to state?
+ const isSelected = selected ? style.active : '';
+ const isExpanded = expanded ? style.expanded : style.collapsed;
+ const classSelect = `${style.event} ${isExpanded} ${isSelected}`;
- const updateValues = (field, value) => {
- // validate field
- if (field in data) {
- // create object with new field
- const newData = { id: data.id, [field]: value };
-
- // request update in parent
- eventsHandler('patch', newData);
- } else {
- showErrorToast('Field Error: ' + field);
- }
- };
-
- const addHandler = () => {
- eventsHandler('add', { type: 'event', order: index + 1 });
- };
- const delayHandler = () => {
- eventsHandler('add', { type: 'delay', order: index + 1 });
- };
- const blockHandler = () => {
- eventsHandler('add', { type: 'block', order: index + 1 });
- };
- const deleteHandler = () => {
- eventsHandler('delete', data.id);
- };
-
- const handleTitleSubmit = (v) => {
- updateValues('title', v);
- };
-
- const handleSubtitleSubmit = (v) => {
- updateValues('subtitle', v);
- };
-
- const handlePresenterSubmit = (v) => {
- updateValues('presenter', v);
- };
-
- const handleNoteSubmit = (v) => {
- updateValues('note', v);
- };
-
- const handleVisibleToggle = () => {
- let viz = !data.isPublic || !visible;
- updateValues('isPublic', viz);
- };
+ // Calculate delay in min
+ const delayValue = delay > 0 ? millisToMinutes(delay) : null;
return (
{(provided) => (
-
-
-
-
-
-
- {more ? (
-
-
-
-
-
-
- ) : (
-
-
-
- )}
-
setMore(!more)}>
- {more ? : }
-
-
-
+ )}
)}
@@ -168,4 +203,3 @@ const EventBlock = (props) => {
};
export default memo(EventBlock, areEqual);
-// export default EventBlock;
diff --git a/client/src/features/editors/list/EventBlock.module.css b/client/src/features/editors/list/EventBlock.module.css
new file mode 100644
index 000000000..afe089a21
--- /dev/null
+++ b/client/src/features/editors/list/EventBlock.module.css
@@ -0,0 +1,185 @@
+/* ============= COMMON ============= */
+.event {
+ margin: 0.2em 0;
+ padding: 0.2em 0.5em;
+ box-sizing: border-box;
+
+ width: 100%;
+ border-radius: 8px;
+ font-size: 15px;
+ border: 1px solid rgba(255, 255, 255, 0.05);
+
+ display: grid;
+ gap: 0.5em;
+
+ background-color: rgba(255, 255, 255, 0.02);
+}
+
+.active {
+ background-color: rgba(255, 255, 255, 0.36);
+ background: linear-gradient(
+ 180deg,
+ #00000000 10%,
+ #4bffab10 97%,
+ #227c52aa 98%,
+ #4bffabcc 100%
+ );
+}
+
+.collapsed {
+ grid-template-columns: 2em 2em auto auto 1fr auto 3.7em;
+ grid-template-areas: 'drag indi time time text more btns';
+ justify-content: center;
+ align-items: baseline;
+}
+
+.expanded {
+ grid-template-columns: 2em 2em 2em auto 1fr auto 3.7em;
+ grid-template-rows: repeat(4, 1fr);
+ grid-template-areas:
+ 'drag indi time time text more btns'
+ '.... indi time time text .... btns'
+ '.... indi time time text .... btns'
+ '.... indi time time text .... btns';
+ justify-content: center;
+}
+
+/* ================ DRAG ================ */
+
+.drag {
+ grid-area: drag;
+ color: rgba(255, 255, 255, 0.67);
+ align-self: center;
+}
+
+/* ============= INDICATORS ============= */
+.indicators {
+ grid-area: indi;
+ display: flex;
+ flex-direction: column;
+ align-self: flex-start;
+ font-size: 0.75em;
+ overflow: hidden;
+ min-width: 0;
+ justify-content: center;
+}
+
+.next,
+.nextDisabled {
+ width: max-content;
+ padding: 0 0.2em;
+ color: #4bffab;
+ transition: 0.3s;
+}
+
+.next {
+ opacity: 0.8;
+}
+
+.nextDisabled {
+ opacity: 0.05;
+}
+
+.delayValue {
+ color: #d69e2eaa;
+ padding: 0 0.2em;
+ text-align: center;
+ align-self: flex-start;
+ width: max-content;
+}
+
+.next:after,
+.delayValue:after {
+ content: '\200b';
+}
+/* =============== TIMES ================ */
+
+.time {
+ grid-area: time;
+}
+
+.start {
+ grid-area: star;
+}
+
+.end {
+ grid-area: end;
+}
+
+.duration {
+ grid-area: dura;
+}
+
+.timeExpanded {
+ grid-area: time;
+ display: flex;
+ flex-direction: column;
+ flex-wrap: wrap;
+ align-items: flex-start;
+ justify-items: flex-start;
+
+ background-color: rgba(255, 255, 255, 0.03);
+ border: 1px solid rgba(255, 255, 255, 0.05);
+ border-radius: 4px;
+ padding: 0 0.5em 0.5em 0.5em;
+}
+
+.label {
+ font-size: 0.75em;
+ color: #aaa;
+}
+
+.calculatedTime {
+ grid-area: time;
+ font-size: 0.75em;
+}
+
+/* =============== TITLES =============== */
+
+.titleContainer {
+ grid-area: text;
+ display: inline-flex;
+ position: relative;
+ align-self: flex-start;
+ height: 100%;
+
+ background-color: rgba(255, 255, 255, 0.03);
+ border: 1px solid rgba(255, 255, 255, 0.05);
+ border-radius: 4px;
+ width: 100%;
+ display: block;
+}
+/* ================ MORE ================ */
+
+.more {
+ cursor: pointer;
+}
+/* ============== ACTION ================ */
+
+.actionOverlay {
+ grid-area: btns;
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ align-content: center;
+ align-self: flex-start;
+ padding-top: 0.2em;
+ gap: 0.5em;
+ opacity: 0.8;
+ transition: linear 0.1s;
+}
+
+.expanded > .actionOverlay {
+ flex-direction: column;
+ justify-self: flex-end;
+}
+
+.event:hover > .actionOverlay {
+ opacity: 1;
+ transition: linear 0.1s;
+}
+
+/* NA */
+.actionOverlay:hover {
+ opacity: 1;
+}
diff --git a/client/src/features/editors/list/EventListItem.jsx b/client/src/features/editors/list/EventListItem.jsx
index caec07a6f..0a6bd8b4a 100644
--- a/client/src/features/editors/list/EventListItem.jsx
+++ b/client/src/features/editors/list/EventListItem.jsx
@@ -1,6 +1,7 @@
import DelayBlock from './DelayBlock';
import BlockBlock from './BlockBlock';
import EventBlock from './EventBlock';
+import { showErrorToast } from '../../../common/helpers/toastManager';
export default function EventListItem(props) {
const {
@@ -14,6 +15,46 @@ export default function EventListItem(props) {
...rest
} = props;
+ // Create / delete new events
+ const actionHandler = (action, payload) => {
+ switch (action) {
+ case 'event':
+ eventsHandler('add', { type: 'event', order: index + 1 });
+ break;
+ case 'delay':
+ eventsHandler('add', { type: 'delay', order: index + 1 });
+ break;
+ case 'block':
+ eventsHandler('add', { type: 'block', order: index + 1 });
+ break;
+ case 'delete':
+ eventsHandler('delete', data.id);
+ break;
+ case 'update':
+ // Handles and filters update requests
+ const { field, value } = payload;
+ if (field === 'durationOverride') {
+ // duration defines timeEnd
+ let end = (data.timeStart += value);
+ const newData = { id: data.id, timeEnd: end };
+
+ // request update in parent
+ eventsHandler('patch', newData);
+ } else if (field in data) {
+ // create object with new field
+ const newData = { id: data.id, [field]: value };
+
+ // request update in parent
+ eventsHandler('patch', newData);
+ } else {
+ showErrorToast('Field Error: ' + field);
+ }
+ break;
+ default:
+ break;
+ }
+ };
+
switch (type) {
case 'event':
return (
@@ -22,17 +63,22 @@ export default function EventListItem(props) {
data={data}
selected={selected}
next={next}
- eventsHandler={eventsHandler}
+ actionHandler={actionHandler}
delay={delay}
/>
);
case 'block':
return (
-
+
);
case 'delay':
return (
-
+
);
default:
break;