mirror of
https://github.com/cpvalente/ontime.git
synced 2026-09-08 15:59:16 +00:00
Feat/block improvements (#95)
* feat/89-block-improvements style: replace reload icon * feat/block-feat/block-improvements add cursor buttons * feat/block-improvements hover to create * feat/block-improvements change settings in modal * feat/block-improvements version bump
This commit is contained in:
@@ -26,17 +26,17 @@ and extend with using the URL aliases feature
|
|||||||
```
|
```
|
||||||
For the presentation views...
|
For the presentation views...
|
||||||
-------------------------------------------------------------
|
-------------------------------------------------------------
|
||||||
IP.ADDRESS:4001 > Web server default to presenter timer view
|
IP.ADDRESS:4001 > Web server default to presenter timer view
|
||||||
IP.ADDRESS:4001/preseter > Presenter / Stage timer view
|
IP.ADDRESS:4001/presenter > Presenter / Stage timer view
|
||||||
IP.ADDRESS:4001/sm > Stage Manager / Backstage view
|
IP.ADDRESS:4001/sm > Stage Manager / Backstage view
|
||||||
IP.ADDRESS:4001/public > Public / Foyer view
|
IP.ADDRESS:4001/public > Public / Foyer view
|
||||||
IP.ADDRESS:4001/pip > Picture in Picture view
|
IP.ADDRESS:4001/pip > Picture in Picture view
|
||||||
IP.ADDRESS:4001/lower > Lower Thirds
|
IP.ADDRESS:4001/lower > Lower Thirds
|
||||||
IP.ADDRESS:4001/studio > Studio Clock
|
IP.ADDRESS:4001/studio > Studio Clock
|
||||||
|
|
||||||
...and for the editor (the control interface, same as the app)
|
...and for the editor (the control interface, same as the app)
|
||||||
-------------------------------------------------------------
|
-------------------------------------------------------------
|
||||||
IP.ADDRESS:4001/studio > Studio Clock
|
IP.ADDRESS:4001/studio > Studio Clock
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -20,18 +20,18 @@ export const requestPatch = async (data) => {
|
|||||||
|
|
||||||
export const requestReorder = async (data) => {
|
export const requestReorder = async (data) => {
|
||||||
const action = 'reorder';
|
const action = 'reorder';
|
||||||
return await axios.patch(eventsURL + '/' + action, data);
|
return await axios.patch(`${eventsURL}/${action}`, data);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const requestApplyDelay = async (eventId) => {
|
export const requestApplyDelay = async (eventId) => {
|
||||||
const action = 'applydelay';
|
const action = 'applydelay';
|
||||||
return await axios.patch(eventsURL + '/' + action + '/' + eventId);
|
return await axios.patch(`${eventsURL}/${action}/${eventId}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const requestDelete = async (eventId) => {
|
export const requestDelete = async (eventId) => {
|
||||||
return await axios.delete(eventsURL + '/' + eventId);
|
return await axios.delete(`${eventsURL}/${eventId}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const requestDeleteAll = async () => {
|
export const requestDeleteAll = async () => {
|
||||||
return await axios.delete(eventsURL + '/all');
|
return await axios.delete(`${eventsURL}/all`);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import { createContext, useCallback, useMemo, useState } from 'react';
|
||||||
|
import { useLocalStorage } from '../hooks/useLocalStorage';
|
||||||
|
|
||||||
|
export const CursorContext = createContext({
|
||||||
|
cursor: 0,
|
||||||
|
isCursorLocked: false,
|
||||||
|
|
||||||
|
setCursor: () => undefined,
|
||||||
|
moveCursorUp: () => undefined,
|
||||||
|
moveCursorDown: () => undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CursorProvider = (props) => {
|
||||||
|
const [cursor, setCursor] = useState(0);
|
||||||
|
const [_cursorLocked, _setCursorLocked] = useLocalStorage('isCursorLocked', 'locked');
|
||||||
|
const isCursorLocked = useMemo(() => _cursorLocked === 'locked', [_cursorLocked]);
|
||||||
|
|
||||||
|
const cursorLockedOff = useCallback(() => _setCursorLocked('unlocked'), [_setCursorLocked]);
|
||||||
|
const cursorLockedOn = useCallback(() => _setCursorLocked('locked'), [_setCursorLocked]);
|
||||||
|
|
||||||
|
const moveCursorUp = useCallback(() => {
|
||||||
|
setCursor((prev) => Math.max(prev - 1, 0));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const moveCursorDown = useCallback(() => {
|
||||||
|
setCursor((prev) => prev + 1);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {boolean | undefined} newValue
|
||||||
|
*/
|
||||||
|
const toggleCursorLocked = useCallback(
|
||||||
|
(newValue = undefined) => {
|
||||||
|
if (newValue === undefined) {
|
||||||
|
if (isCursorLocked) {
|
||||||
|
cursorLockedOff();
|
||||||
|
} else {
|
||||||
|
cursorLockedOn();
|
||||||
|
}
|
||||||
|
} else if (!newValue) {
|
||||||
|
cursorLockedOff();
|
||||||
|
} else if (newValue) {
|
||||||
|
cursorLockedOn();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[cursorLockedOff, cursorLockedOn, isCursorLocked]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CursorContext.Provider
|
||||||
|
value={{
|
||||||
|
cursor,
|
||||||
|
isCursorLocked,
|
||||||
|
toggleCursorLocked,
|
||||||
|
setCursor,
|
||||||
|
moveCursorUp,
|
||||||
|
moveCursorDown,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</CursorContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { createContext, useState } from 'react';
|
||||||
|
|
||||||
|
export const LocalEventSettingsContext = createContext({
|
||||||
|
showQuickEntry: false,
|
||||||
|
starTimeIsLastEnd: true,
|
||||||
|
defaultPublic: true,
|
||||||
|
|
||||||
|
setShowQuickEntry: () => undefined,
|
||||||
|
setStarTimeIsLastEnd: () => undefined,
|
||||||
|
setDefaultPublic: () => undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const LocalEventSettingsProvider = (props) => {
|
||||||
|
const [showQuickEntry, setShowQuickEntry] = useState(false);
|
||||||
|
const [starTimeIsLastEnd, setStarTimeIsLastEnd] = useState(true);
|
||||||
|
const [defaultPublic, setDefaultPublic] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LocalEventSettingsContext.Provider
|
||||||
|
value={{
|
||||||
|
showQuickEntry,
|
||||||
|
setShowQuickEntry,
|
||||||
|
starTimeIsLastEnd,
|
||||||
|
setStarTimeIsLastEnd,
|
||||||
|
defaultPublic,
|
||||||
|
setDefaultPublic,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</LocalEventSettingsContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
// Roughly from useHooks - useLocalStorage
|
||||||
|
|
||||||
|
export const useLocalStorage = (key, initialValue) => {
|
||||||
|
const [storedValue, setStoredValue] = useState(() => {
|
||||||
|
try {
|
||||||
|
const item = window.localStorage.getItem(key);
|
||||||
|
return item ? JSON.parse(item) : initialValue;
|
||||||
|
} catch (error) {
|
||||||
|
return initialValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const setValue = (value) => {
|
||||||
|
try {
|
||||||
|
setStoredValue(value);
|
||||||
|
window.localStorage.setItem(key, JSON.stringify(value));
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return [storedValue, setValue];
|
||||||
|
}
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
//
|
|
||||||
// useTimeout React Hook
|
|
||||||
//
|
|
||||||
// React hook for delaying calls with time
|
|
||||||
export const useTimeout = (
|
|
||||||
callback, // function to call. No args passed.
|
|
||||||
timeout = 0, // delay, ms (default: immediately put into JS Event Queue)
|
|
||||||
{
|
|
||||||
// manage re-render behavior.
|
|
||||||
// by default, a re-render in your component will re-define the callback,
|
|
||||||
// which will cause this timeout to cancel itself.
|
|
||||||
// to avoid cancelling on re-renders (but still cancel on unmounts),
|
|
||||||
// set `persistRenders: true,`.
|
|
||||||
persistRenders = false,
|
|
||||||
} = {},
|
|
||||||
// These dependencies are injected for testing purposes.
|
|
||||||
// (pure functions - where all dependencies are arguments - is often easier to test)
|
|
||||||
_setTimeout = setTimeout,
|
|
||||||
_clearTimeout = clearTimeout,
|
|
||||||
_useEffect = useEffect
|
|
||||||
) => {
|
|
||||||
let timeoutId;
|
|
||||||
const cancel = () => timeoutId && _clearTimeout(timeoutId);
|
|
||||||
|
|
||||||
_useEffect(
|
|
||||||
() => {
|
|
||||||
timeoutId = _setTimeout(callback, timeout);
|
|
||||||
return cancel;
|
|
||||||
},
|
|
||||||
persistRenders
|
|
||||||
? [_setTimeout, _clearTimeout]
|
|
||||||
: [callback, timeout, _setTimeout, _clearTimeout]
|
|
||||||
);
|
|
||||||
|
|
||||||
return cancel;
|
|
||||||
};
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { IconButton } from '@chakra-ui/button';
|
import { IconButton } from '@chakra-ui/button';
|
||||||
import { Tooltip } from '@chakra-ui/tooltip';
|
import { Tooltip } from '@chakra-ui/tooltip';
|
||||||
import { FiChevronsDown } from 'react-icons/fi';
|
import { FiCheck } from 'react-icons/fi';
|
||||||
|
|
||||||
export default function ApplyIconBtn(props) {
|
export default function ApplyIconBtn(props) {
|
||||||
const { clickhandler, ...rest } = props;
|
const { clickhandler, ...rest } = props;
|
||||||
@@ -8,7 +8,7 @@ export default function ApplyIconBtn(props) {
|
|||||||
<Tooltip label='Apply delays'>
|
<Tooltip label='Apply delays'>
|
||||||
<IconButton
|
<IconButton
|
||||||
size={props.size || 'xs'}
|
size={props.size || 'xs'}
|
||||||
icon={<FiChevronsDown />}
|
icon={<FiCheck />}
|
||||||
colorScheme='orange'
|
colorScheme='orange'
|
||||||
onClick={clickhandler}
|
onClick={clickhandler}
|
||||||
_focus={{ boxShadow: 'none' }}
|
_focus={{ boxShadow: 'none' }}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { IconButton } from '@chakra-ui/button';
|
||||||
|
import { Tooltip } from '@chakra-ui/tooltip';
|
||||||
|
import { IoCaretDown } from 'react-icons/io5';
|
||||||
|
|
||||||
|
export default function CursorDownBtn(props) {
|
||||||
|
const { clickhandler, active, ref } = props;
|
||||||
|
return (
|
||||||
|
<Tooltip label='Move cursor down Alt + ↓'>
|
||||||
|
<IconButton
|
||||||
|
ref={ref}
|
||||||
|
size={props.size || 'xs'}
|
||||||
|
icon={<IoCaretDown />}
|
||||||
|
color={active ? 'pink.100' : 'pink.300'}
|
||||||
|
borderColor={active ? undefined : 'pink.300'}
|
||||||
|
backgroundColor={active ? 'pink.400' : undefined}
|
||||||
|
variant={active ? 'solid' : 'outline'}
|
||||||
|
onClick={clickhandler}
|
||||||
|
_focus={{ boxShadow: 'none' }}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
+1
-1
@@ -2,7 +2,7 @@ import { IconButton } from '@chakra-ui/button';
|
|||||||
import { Tooltip } from '@chakra-ui/tooltip';
|
import { Tooltip } from '@chakra-ui/tooltip';
|
||||||
import { FiTarget } from 'react-icons/fi';
|
import { FiTarget } from 'react-icons/fi';
|
||||||
|
|
||||||
export default function LockIconBtn(props) {
|
export default function CursorLockedBtn(props) {
|
||||||
const { clickhandler, active, ref } = props;
|
const { clickhandler, active, ref } = props;
|
||||||
return (
|
return (
|
||||||
<Tooltip label='Lock cursor to current'>
|
<Tooltip label='Lock cursor to current'>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { IconButton } from '@chakra-ui/button';
|
||||||
|
import { Tooltip } from '@chakra-ui/tooltip';
|
||||||
|
import { IoCaretUp } from 'react-icons/io5';
|
||||||
|
|
||||||
|
export default function CursorUpBtn(props) {
|
||||||
|
const { clickhandler, active, ref } = props;
|
||||||
|
return (
|
||||||
|
<Tooltip label='Move cursor up Alt + ↑'>
|
||||||
|
<IconButton
|
||||||
|
ref={ref}
|
||||||
|
size={props.size || 'xs'}
|
||||||
|
icon={<IoCaretUp />}
|
||||||
|
color={active ? 'pink.100' : 'pink.300'}
|
||||||
|
borderColor={active ? undefined : 'pink.300'}
|
||||||
|
backgroundColor={active ? 'pink.400' : undefined}
|
||||||
|
variant={active ? 'solid' : 'outline'}
|
||||||
|
onClick={clickhandler}
|
||||||
|
_focus={{ boxShadow: 'none' }}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { IconButton } from '@chakra-ui/button';
|
import { IconButton } from '@chakra-ui/button';
|
||||||
import { IoReload } from 'react-icons/io5';
|
import { IoArrowUndo } from 'react-icons/io5';
|
||||||
import { Tooltip } from '@chakra-ui/tooltip';
|
import { Tooltip } from '@chakra-ui/tooltip';
|
||||||
|
|
||||||
export default function ReloadIconButton(props) {
|
export default function ReloadIconButton(props) {
|
||||||
@@ -7,7 +7,7 @@ export default function ReloadIconButton(props) {
|
|||||||
return (
|
return (
|
||||||
<Tooltip label='Reload event' openDelay={500} shouldWrapChildren={props.disabled}>
|
<Tooltip label='Reload event' openDelay={500} shouldWrapChildren={props.disabled}>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={<IoReload size='22px' />}
|
icon={<IoArrowUndo size='22px' />}
|
||||||
colorScheme='whiteAlpha'
|
colorScheme='whiteAlpha'
|
||||||
backgroundColor='#ffffff05'
|
backgroundColor='#ffffff05'
|
||||||
variant='outline'
|
variant='outline'
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const Countdown = ({ time, small, isNegative, hideZeroHours }) => {
|
|||||||
export default memo(Countdown);
|
export default memo(Countdown);
|
||||||
|
|
||||||
Countdown.propTypes = {
|
Countdown.propTypes = {
|
||||||
time: PropTypes.number.isRequired,
|
time: PropTypes.number,
|
||||||
small: PropTypes.bool,
|
small: PropTypes.bool,
|
||||||
isNegative: PropTypes.bool,
|
isNegative: PropTypes.bool,
|
||||||
hideZeroHour: PropTypes.bool,
|
hideZeroHour: PropTypes.bool,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const inputProps = {
|
|||||||
backgroundColor: 'rgba(0,0,0,0.05)',
|
backgroundColor: 'rgba(0,0,0,0.05)',
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
border: '1px solid #ecc94b55',
|
border: '1px solid #ecc94b55',
|
||||||
borderRadius: '4px',
|
borderRadius: '8px',
|
||||||
placeholder: '-',
|
placeholder: '-',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Editable, EditableInput, EditablePreview } from '@chakra-ui/editable';
|
import { Editable, EditableInput, EditablePreview } from '@chakra-ui/editable';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import style from './EditableText.module.css';
|
import style from './EditableText.module.scss';
|
||||||
|
|
||||||
export default function EditableText(props) {
|
export default function EditableText(props) {
|
||||||
const { label, defaultValue, placeholder, submitHandler, ...rest } = props;
|
const { label, defaultValue, placeholder, submitHandler, ...rest } = props;
|
||||||
@@ -33,10 +33,7 @@ export default function EditableText(props) {
|
|||||||
className={style.inline}
|
className={style.inline}
|
||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
<EditablePreview
|
<EditablePreview color={text === '' ? '#666' : 'inherit'} maxWidth='75%' />
|
||||||
color={text === '' ? '#666' : 'inherit'}
|
|
||||||
maxWidth='75%'
|
|
||||||
/>
|
|
||||||
<EditableInput overflowX='hidden' maxWidth='75%' />
|
<EditableInput overflowX='hidden' maxWidth='75%' />
|
||||||
</Editable>
|
</Editable>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+10
-1
@@ -7,8 +7,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.block {
|
.block {
|
||||||
display: 'block';
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
*:nth-of-type(2) {
|
||||||
|
flex: 1;
|
||||||
|
width: 20em;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.inline {
|
.inline {
|
||||||
@@ -2,13 +2,12 @@
|
|||||||
.delayedEditable {
|
.delayedEditable {
|
||||||
background-color: rgba(255, 255, 255, 0.03);
|
background-color: rgba(255, 255, 255, 0.03);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
border-radius: 4px;
|
|
||||||
width: 6.5em;
|
width: 6.5em;
|
||||||
letter-spacing: 1px;
|
letter-spacing: 1px;
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border-radius: 4px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.delayedEditable {
|
.delayedEditable {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Draggable } from 'react-beautiful-dnd';
|
|||||||
import { FiMoreVertical } from 'react-icons/fi';
|
import { FiMoreVertical } from 'react-icons/fi';
|
||||||
import DeleteIconBtn from 'common/components/buttons/DeleteIconBtn';
|
import DeleteIconBtn from 'common/components/buttons/DeleteIconBtn';
|
||||||
import ActionButtons from '../list/ActionButtons';
|
import ActionButtons from '../list/ActionButtons';
|
||||||
import style from './BlockBlock.module.css';
|
import style from './BlockBlock.module.scss';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
export default function BlockBlock(props) {
|
export default function BlockBlock(props) {
|
||||||
|
|||||||
+3
-3
@@ -1,3 +1,4 @@
|
|||||||
|
@use '../../../styles/main' as *;
|
||||||
/* ============= COMMON ============= */
|
/* ============= COMMON ============= */
|
||||||
|
|
||||||
.block {
|
.block {
|
||||||
@@ -8,7 +9,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
border: $block-border;
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 2em auto;
|
grid-template-columns: 2em auto;
|
||||||
@@ -16,7 +17,7 @@
|
|||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
|
|
||||||
background-color: #805ad5;
|
background-color: $block-block-color;
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
0deg,
|
0deg,
|
||||||
rgba(107, 70, 193, 0.4) 0%,
|
rgba(107, 70, 193, 0.4) 0%,
|
||||||
@@ -34,7 +35,6 @@
|
|||||||
|
|
||||||
/* ============== ACTION ================ */
|
/* ============== ACTION ================ */
|
||||||
.actionOverlay {
|
.actionOverlay {
|
||||||
display: flex;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -5,7 +5,7 @@ import ActionButtons from '../list/ActionButtons';
|
|||||||
import DeleteIconBtn from 'common/components/buttons/DeleteIconBtn';
|
import DeleteIconBtn from 'common/components/buttons/DeleteIconBtn';
|
||||||
import ApplyIconBtn from 'common/components/buttons/ApplyIconBtn';
|
import ApplyIconBtn from 'common/components/buttons/ApplyIconBtn';
|
||||||
import DelayInput from 'common/input/DelayInput';
|
import DelayInput from 'common/input/DelayInput';
|
||||||
import style from './DelayBlock.module.css';
|
import style from './DelayBlock.module.scss';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
export default function DelayBlock(props) {
|
export default function DelayBlock(props) {
|
||||||
|
|||||||
+5
-3
@@ -1,3 +1,5 @@
|
|||||||
|
@use '../../../styles/main' as *;
|
||||||
|
|
||||||
/* ============= COMMON ============= */
|
/* ============= COMMON ============= */
|
||||||
|
|
||||||
.delay {
|
.delay {
|
||||||
@@ -7,7 +9,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
border: $block-border;
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 2em 1fr auto;
|
grid-template-columns: 2em 1fr auto;
|
||||||
@@ -17,7 +19,7 @@
|
|||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
|
|
||||||
background-color: #ecc94b;
|
background-color: $block-delay-color;
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
180deg,
|
180deg,
|
||||||
rgba(214, 158, 46, 0.4) 0%,
|
rgba(214, 158, 46, 0.4) 0%,
|
||||||
@@ -30,7 +32,7 @@
|
|||||||
|
|
||||||
.drag {
|
.drag {
|
||||||
grid-area: drag;
|
grid-area: drag;
|
||||||
color: rgba(255, 255, 255, 0.67);
|
color: $block-icon-drag;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6,10 +6,10 @@ import MenuBar from 'features/menu/MenuBar';
|
|||||||
import ModalManager from 'features/modals/ModalManager';
|
import ModalManager from 'features/modals/ModalManager';
|
||||||
import ErrorBoundary from 'common/components/errorBoundary/ErrorBoundary';
|
import ErrorBoundary from 'common/components/errorBoundary/ErrorBoundary';
|
||||||
import { LoggingProvider } from '../../app/context/LoggingContext';
|
import { LoggingProvider } from '../../app/context/LoggingContext';
|
||||||
|
import { LocalEventSettingsProvider } from '../../app/context/LocalEventSettingsContext';
|
||||||
|
import { CursorProvider } from '../../app/context/CursorContext';
|
||||||
|
|
||||||
const EventListWrapper = lazy(() =>
|
const EventListWrapper = lazy(() => import('features/editors/list/EventListWrapper'));
|
||||||
import('features/editors/list/EventListWrapper')
|
|
||||||
);
|
|
||||||
const PlaybackControl = lazy(() => import('features/control/PlaybackControl'));
|
const PlaybackControl = lazy(() => import('features/control/PlaybackControl'));
|
||||||
const MessageControl = lazy(() => import('features/control/MessageControl'));
|
const MessageControl = lazy(() => import('features/control/MessageControl'));
|
||||||
const Info = lazy(() => import('features/info/Info'));
|
const Info = lazy(() => import('features/info/Info'));
|
||||||
@@ -24,53 +24,57 @@ export default function Editor() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<LoggingProvider>
|
<LoggingProvider>
|
||||||
<ErrorBoundary>
|
<LocalEventSettingsProvider>
|
||||||
<ModalManager isOpen={isOpen} onClose={onClose} />
|
<ErrorBoundary>
|
||||||
</ErrorBoundary>
|
<ModalManager isOpen={isOpen} onClose={onClose} />
|
||||||
|
</ErrorBoundary>
|
||||||
|
|
||||||
<div className={styles.mainContainer}>
|
<div className={styles.mainContainer}>
|
||||||
<Box id='settings' className={styles.settings}>
|
<CursorProvider>
|
||||||
<ErrorBoundary>
|
<Box id='settings' className={styles.settings}>
|
||||||
<MenuBar onOpen={onOpen} isOpen={isOpen} />
|
<ErrorBoundary>
|
||||||
</ErrorBoundary>
|
<MenuBar onOpen={onOpen} isOpen={isOpen} />
|
||||||
</Box>
|
</ErrorBoundary>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Box className={styles.editor}>
|
<Box className={styles.editor}>
|
||||||
<h1>Event List</h1>
|
<h1>Event List</h1>
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<EventListWrapper />
|
<EventListWrapper />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</div>
|
</div>
|
||||||
</Box>
|
</Box>
|
||||||
|
</CursorProvider>
|
||||||
|
|
||||||
<Box className={styles.messages}>
|
<Box className={styles.messages}>
|
||||||
<h1>Display Messages</h1>
|
<h1>Display Messages</h1>
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<MessageControl />
|
<MessageControl />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</div>
|
</div>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box className={styles.playback}>
|
<Box className={styles.playback}>
|
||||||
<h1>Timer Control</h1>
|
<h1>Timer Control</h1>
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<PlaybackControl />
|
<PlaybackControl />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</div>
|
</div>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box className={styles.info}>
|
<Box className={styles.info}>
|
||||||
<h1>Info</h1>
|
<h1>Info</h1>
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<Info />
|
<Info />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</div>
|
</div>
|
||||||
</Box>
|
</Box>
|
||||||
</div>
|
</div>
|
||||||
|
</LocalEventSettingsProvider>
|
||||||
</LoggingProvider>
|
</LoggingProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import { Tooltip } from '@chakra-ui/tooltip';
|
||||||
|
import { Checkbox } from '@chakra-ui/react';
|
||||||
|
import style from './EntryBlock.module.scss';
|
||||||
|
import { useContext, useEffect, useState } from 'react';
|
||||||
|
import { LocalEventSettingsContext } from '../../../app/context/LocalEventSettingsContext';
|
||||||
|
|
||||||
|
export default function EntryBlock(props) {
|
||||||
|
const { showKbd, index, eventsHandler } = props;
|
||||||
|
const { starTimeIsLastEnd, defaultPublic } = useContext(LocalEventSettingsContext);
|
||||||
|
const [doStartTime, setStartTime] = useState(starTimeIsLastEnd);
|
||||||
|
const [doPublic, setPublic] = useState(defaultPublic);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setStartTime(starTimeIsLastEnd);
|
||||||
|
}, [starTimeIsLastEnd]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setPublic(defaultPublic);
|
||||||
|
}, [defaultPublic]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={style.create}>
|
||||||
|
<Tooltip label='Add Event' openDelay={300}>
|
||||||
|
<span
|
||||||
|
className={style.createEvent}
|
||||||
|
onClick={() =>
|
||||||
|
eventsHandler(
|
||||||
|
'add',
|
||||||
|
{ type: 'event', order: index + 1, isPublic: doPublic },
|
||||||
|
{ startIsLastEnd: doStartTime ? index : undefined }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
E{showKbd && <span className={style.keyboard}>Alt + E</span>}
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip label='Add Delay' openDelay={300}>
|
||||||
|
<span
|
||||||
|
className={style.createDelay}
|
||||||
|
onClick={() => eventsHandler('add', { type: 'delay', order: index + 1 })}
|
||||||
|
>
|
||||||
|
D{showKbd && <span className={style.keyboard}>Alt + D</span>}
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip label='Add Block' openDelay={300}>
|
||||||
|
<span
|
||||||
|
className={style.createBlock}
|
||||||
|
onClick={() => eventsHandler('add', { type: 'block', order: index + 1 })}
|
||||||
|
>
|
||||||
|
B{showKbd && <span className={style.keyboard}>Alt + B</span>}
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
<div className={style.options}>
|
||||||
|
<Checkbox
|
||||||
|
size='sm'
|
||||||
|
colorScheme='blue'
|
||||||
|
isChecked={doStartTime}
|
||||||
|
onChange={(e) => setStartTime(e.target.checked)}
|
||||||
|
>
|
||||||
|
Start time is last end
|
||||||
|
</Checkbox>
|
||||||
|
<Checkbox
|
||||||
|
size='sm'
|
||||||
|
colorScheme='blue'
|
||||||
|
isChecked={doPublic}
|
||||||
|
onChange={(e) => setPublic(e.target.checked)}
|
||||||
|
>
|
||||||
|
Default public
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
@use '../../../styles/main' as *;
|
||||||
|
|
||||||
|
.create {
|
||||||
|
padding: 0 0.5em;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
height: 20px;
|
||||||
|
margin: -8px 0;
|
||||||
|
transition: height 0.1s ease;
|
||||||
|
|
||||||
|
* {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
height: calc(2.5em + 20px);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 5%;
|
||||||
|
transition: height 0.15s ease;
|
||||||
|
|
||||||
|
* {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.createEvent,
|
||||||
|
.createDelay,
|
||||||
|
.createBlock {
|
||||||
|
width: auto;
|
||||||
|
padding: 0 16px;
|
||||||
|
height: 24px;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: center;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 21px;
|
||||||
|
border-radius: 4px;
|
||||||
|
opacity: 0.6;
|
||||||
|
|
||||||
|
.keyboard {
|
||||||
|
margin-left: 4px;
|
||||||
|
padding: 0 4px;
|
||||||
|
color: #ccc;
|
||||||
|
border-radius: 2px;
|
||||||
|
font-family: Monospaced, sans-serif;
|
||||||
|
background-color: rgba(0, 0, 0, 0.24);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.createEvent {
|
||||||
|
border: 1px solid #2b6cb0;
|
||||||
|
color: lighten(#2b6cb0, 30%);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #2b6cb0;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.createDelay {
|
||||||
|
border: 1px solid #ecc94b;;
|
||||||
|
color: lighten(#ecc94b, 30%);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #ecc94b;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.createBlock {
|
||||||
|
border: 1px solid #805ad5;
|
||||||
|
color: lighten(#805ad5, 30%);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #805ad5;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.options {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
opacity: 0.65;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -142,7 +142,7 @@
|
|||||||
|
|
||||||
background-color: rgba(255, 255, 255, 0.03);
|
background-color: rgba(255, 255, 255, 0.03);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
border-radius: 4px;
|
border-radius: 8px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: block;
|
display: block;
|
||||||
|
|
||||||
@@ -150,11 +150,13 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.collapsed .titleContasiner {
|
.collapsed .titleContainer {
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
.expanded .titleContainer {
|
.expanded .titleContainer {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.oscLabel {
|
.oscLabel {
|
||||||
|
|||||||
@@ -1,21 +1,22 @@
|
|||||||
import style from './List.module.scss';
|
import style from './List.module.scss';
|
||||||
import { createRef, useCallback, useEffect, useMemo, useState } from 'react';
|
import { createRef, useCallback, useContext, useEffect, useState } from 'react';
|
||||||
import { useSocket } from 'app/context/socketContext';
|
import { useSocket } from 'app/context/socketContext';
|
||||||
import Empty from 'common/state/Empty';
|
import Empty from 'common/state/Empty';
|
||||||
import EventListItem from './EventListItem';
|
import EventListItem from './EventListItem';
|
||||||
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
|
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
|
||||||
import { useAtom } from 'jotai';
|
import EntryBlock from '../EntryBlock/EntryBlock';
|
||||||
import { SelectSetting } from 'app/context/settingsAtom';
|
import { CursorContext } from '../../../app/context/CursorContext';
|
||||||
|
import { LocalEventSettingsContext } from '../../../app/context/LocalEventSettingsContext';
|
||||||
|
|
||||||
export default function EventList(props) {
|
export default function EventList(props) {
|
||||||
const { events, eventsHandler } = props;
|
const { events, eventsHandler } = props;
|
||||||
|
const { cursor, moveCursorUp, moveCursorDown, setCursor, isCursorLocked } =
|
||||||
|
useContext(CursorContext);
|
||||||
const socket = useSocket();
|
const socket = useSocket();
|
||||||
const [selectedId, setSelectedId] = useState(null);
|
const [selectedId, setSelectedId] = useState(null);
|
||||||
const [nextId, setNextId] = useState(null);
|
const [nextId, setNextId] = useState(null);
|
||||||
const [cursor, setCursor] = useState(0);
|
|
||||||
const [cursorSettings] = useAtom(useMemo(() => SelectSetting('cursor'), []));
|
|
||||||
|
|
||||||
const cursorRef = createRef();
|
const cursorRef = createRef();
|
||||||
|
const { showQuickEntry } = useContext(LocalEventSettingsContext);
|
||||||
|
|
||||||
// Handle keyboard shortcuts
|
// Handle keyboard shortcuts
|
||||||
const handleKeyPress = useCallback(
|
const handleKeyPress = useCallback(
|
||||||
@@ -26,13 +27,11 @@ export default function EventList(props) {
|
|||||||
if (e.altKey && (!e.ctrlKey || !e.shiftKey)) {
|
if (e.altKey && (!e.ctrlKey || !e.shiftKey)) {
|
||||||
// Arrow down
|
// Arrow down
|
||||||
if (e.keyCode === 40) {
|
if (e.keyCode === 40) {
|
||||||
if (cursor == null) setCursor(0);
|
if (cursor < events.length - 1) moveCursorDown();
|
||||||
else if (cursor < events.length - 1) setCursor(cursor + 1);
|
|
||||||
}
|
}
|
||||||
// Arrow up
|
// Arrow up
|
||||||
if (e.keyCode === 38) {
|
if (e.keyCode === 38) {
|
||||||
if (cursor == null) setCursor(0);
|
if (cursor > 0) moveCursorUp();
|
||||||
else if (cursor > 0) setCursor(cursor - 1);
|
|
||||||
}
|
}
|
||||||
// E
|
// E
|
||||||
if (e.key === 'e' || e.key === 'E') {
|
if (e.key === 'e' || e.key === 'E') {
|
||||||
@@ -54,7 +53,7 @@ export default function EventList(props) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[cursor, events, eventsHandler]
|
[cursor, events.length, eventsHandler, moveCursorDown, moveCursorUp]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -67,7 +66,7 @@ export default function EventList(props) {
|
|||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('keydown', handleKeyPress);
|
document.removeEventListener('keydown', handleKeyPress);
|
||||||
};
|
};
|
||||||
}, [handleKeyPress, cursor, events]);
|
}, [handleKeyPress, cursor, events, setCursor]);
|
||||||
|
|
||||||
// handle incoming messages
|
// handle incoming messages
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -101,13 +100,14 @@ export default function EventList(props) {
|
|||||||
block: 'nearest',
|
block: 'nearest',
|
||||||
inline: 'start',
|
inline: 'start',
|
||||||
});
|
});
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [cursor]);
|
}, [cursor]);
|
||||||
|
|
||||||
// if selected event
|
// if selected event
|
||||||
// or cursor settings changed
|
// or cursor settings changed
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// and if we are locked
|
// and if we are locked
|
||||||
if (cursorSettings !== 'locked' || selectedId == null) return;
|
if (!isCursorLocked || selectedId == null) return;
|
||||||
|
|
||||||
// move cursor
|
// move cursor
|
||||||
let gotoIndex = -1;
|
let gotoIndex = -1;
|
||||||
@@ -123,7 +123,8 @@ export default function EventList(props) {
|
|||||||
// move cursor
|
// move cursor
|
||||||
setCursor(gotoIndex);
|
setCursor(gotoIndex);
|
||||||
}
|
}
|
||||||
}, [selectedId, cursorSettings]);
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [selectedId, isCursorLocked]);
|
||||||
|
|
||||||
if (events.length < 1) {
|
if (events.length < 1) {
|
||||||
return <Empty text='No Events' />;
|
return <Empty text='No Events' />;
|
||||||
@@ -155,11 +156,7 @@ export default function EventList(props) {
|
|||||||
<DragDropContext onDragEnd={handleOnDragEnd}>
|
<DragDropContext onDragEnd={handleOnDragEnd}>
|
||||||
<Droppable droppableId='eventlist'>
|
<Droppable droppableId='eventlist'>
|
||||||
{(provided) => (
|
{(provided) => (
|
||||||
<div
|
<div className={style.list} {...provided.droppableProps} ref={provided.innerRef}>
|
||||||
className={style.list}
|
|
||||||
{...provided.droppableProps}
|
|
||||||
ref={provided.innerRef}
|
|
||||||
>
|
|
||||||
{events.map((e, index) => {
|
{events.map((e, index) => {
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
cumulativeDelay = 0;
|
cumulativeDelay = 0;
|
||||||
@@ -174,24 +171,34 @@ export default function EventList(props) {
|
|||||||
previousEnd = thisEnd;
|
previousEnd = thisEnd;
|
||||||
thisEnd = e.timeEnd;
|
thisEnd = e.timeEnd;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div key={e.id}>
|
||||||
ref={cursor === index ? cursorRef : undefined}
|
{index === 0 && showQuickEntry && (
|
||||||
key={e.id}
|
<EntryBlock index={-1} eventsHandler={eventsHandler} />
|
||||||
className={cursor === index ? style.cursor : undefined}
|
)}
|
||||||
>
|
<div
|
||||||
<EventListItem
|
ref={cursor === index ? cursorRef : undefined}
|
||||||
type={e.type}
|
className={cursor === index ? style.cursor : undefined}
|
||||||
index={index}
|
>
|
||||||
eventIndex={eventIndex}
|
<EventListItem
|
||||||
data={e}
|
type={e.type}
|
||||||
selected={selectedId === e.id}
|
index={index}
|
||||||
next={nextId === e.id}
|
eventIndex={eventIndex}
|
||||||
eventsHandler={eventsHandler}
|
data={e}
|
||||||
delay={cumulativeDelay}
|
selected={selectedId === e.id}
|
||||||
previousEnd={previousEnd}
|
next={nextId === e.id}
|
||||||
/>
|
eventsHandler={eventsHandler}
|
||||||
|
delay={cumulativeDelay}
|
||||||
|
previousEnd={previousEnd}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{showQuickEntry && (
|
||||||
|
<EntryBlock
|
||||||
|
showKbd={index === cursor}
|
||||||
|
index={index}
|
||||||
|
eventsHandler={eventsHandler}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import BlockBlock from '../BlockBlock/BlockBlock';
|
|||||||
import EventBlock from '../EventBlock/EventBlock';
|
import EventBlock from '../EventBlock/EventBlock';
|
||||||
import { memo, useContext } from 'react';
|
import { memo, useContext } from 'react';
|
||||||
import { LoggingContext } from '../../../app/context/LoggingContext';
|
import { LoggingContext } from '../../../app/context/LoggingContext';
|
||||||
|
import { LocalEventSettingsContext } from '../../../app/context/LocalEventSettingsContext';
|
||||||
|
|
||||||
const areEqual = (prevProps, nextProps) => {
|
const areEqual = (prevProps, nextProps) => {
|
||||||
return (
|
return (
|
||||||
@@ -29,12 +30,21 @@ const EventListItem = (props) => {
|
|||||||
...rest
|
...rest
|
||||||
} = props;
|
} = props;
|
||||||
const { emitError } = useContext(LoggingContext);
|
const { emitError } = useContext(LoggingContext);
|
||||||
|
const { starTimeIsLastEnd, defaultPublic } = useContext(LocalEventSettingsContext);
|
||||||
|
|
||||||
// Create / delete new events
|
// Create / delete new events
|
||||||
const actionHandler = (action, payload) => {
|
const actionHandler = (action, payload) => {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case 'event':
|
case 'event':
|
||||||
eventsHandler('add', { type: 'event', order: index + 1 });
|
eventsHandler(
|
||||||
|
'add',
|
||||||
|
{
|
||||||
|
type: 'event',
|
||||||
|
order: index + 1,
|
||||||
|
isPublic: defaultPublic,
|
||||||
|
},
|
||||||
|
{ startIsLastEnd: starTimeIsLastEnd ? index : undefined }
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case 'delay':
|
case 'delay':
|
||||||
eventsHandler('add', { type: 'delay', order: index + 1 });
|
eventsHandler('add', { type: 'delay', order: index + 1 });
|
||||||
@@ -85,9 +95,7 @@ const EventListItem = (props) => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case 'block':
|
case 'block':
|
||||||
return (
|
return <BlockBlock index={index} data={data} actionHandler={actionHandler} />;
|
||||||
<BlockBlock index={index} data={data} actionHandler={actionHandler} />
|
|
||||||
);
|
|
||||||
case 'delay':
|
case 'delay':
|
||||||
return (
|
return (
|
||||||
<DelayBlock
|
<DelayBlock
|
||||||
|
|||||||
@@ -1,32 +1,29 @@
|
|||||||
import { useMutation, useQueryClient } from 'react-query';
|
|
||||||
import { useCallback, useContext, useEffect, useState } from 'react';
|
import { useCallback, useContext, useEffect, useState } from 'react';
|
||||||
import {
|
import { useMutation, useQueryClient } from 'react-query';
|
||||||
fetchAllEvents,
|
|
||||||
requestPatch,
|
|
||||||
requestPost,
|
|
||||||
requestPut,
|
|
||||||
requestDelete,
|
|
||||||
requestDeleteAll,
|
|
||||||
requestReorder,
|
|
||||||
requestApplyDelay,
|
|
||||||
} from 'app/api/eventsApi.js';
|
|
||||||
import EventList from './EventList';
|
|
||||||
import EventListMenu from 'features/menu/EventListMenu.jsx';
|
|
||||||
import { useFetch } from 'app/hooks/useFetch.js';
|
|
||||||
import Empty from 'common/state/Empty';
|
|
||||||
import { EVENTS_TABLE } from 'app/api/apiConstants';
|
import { EVENTS_TABLE } from 'app/api/apiConstants';
|
||||||
import { BatchOperation } from 'app/context/collapseAtom';
|
import { BatchOperation } from 'app/context/collapseAtom';
|
||||||
import { useAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
import { LoggingContext } from '../../../app/context/LoggingContext';
|
import { LoggingContext } from '../../../app/context/LoggingContext';
|
||||||
|
import {
|
||||||
|
fetchAllEvents,
|
||||||
|
requestApplyDelay,
|
||||||
|
requestDelete,
|
||||||
|
requestDeleteAll,
|
||||||
|
requestPatch,
|
||||||
|
requestPost,
|
||||||
|
requestPut,
|
||||||
|
requestReorder,
|
||||||
|
} from 'app/api/eventsApi.js';
|
||||||
|
import { useFetch } from 'app/hooks/useFetch.js';
|
||||||
|
import EventList from './EventList';
|
||||||
|
import EventListMenu from 'features/menu/EventListMenu.jsx';
|
||||||
|
import Empty from 'common/state/Empty';
|
||||||
|
|
||||||
export default function EventListWrapper() {
|
export default function EventListWrapper() {
|
||||||
const [, setCollapsed] = useAtom(BatchOperation);
|
const [, setCollapsed] = useAtom(BatchOperation);
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { emitError } = useContext(LoggingContext);
|
const { emitError } = useContext(LoggingContext);
|
||||||
const { data, status, isError, refetch } = useFetch(
|
const { data, status, isError, refetch } = useFetch(EVENTS_TABLE, fetchAllEvents);
|
||||||
EVENTS_TABLE,
|
|
||||||
fetchAllEvents
|
|
||||||
);
|
|
||||||
const [events, setEvents] = useState(null);
|
const [events, setEvents] = useState(null);
|
||||||
|
|
||||||
const addEvent = useMutation(requestPost, {
|
const addEvent = useMutation(requestPost, {
|
||||||
@@ -72,10 +69,7 @@ export default function EventListWrapper() {
|
|||||||
queryClient.cancelQueries([EVENTS_TABLE, newEvent.id]);
|
queryClient.cancelQueries([EVENTS_TABLE, newEvent.id]);
|
||||||
|
|
||||||
// Snapshot the previous value
|
// Snapshot the previous value
|
||||||
const previousEvent = queryClient.getQueryData([
|
const previousEvent = queryClient.getQueryData([EVENTS_TABLE, newEvent.id]);
|
||||||
EVENTS_TABLE,
|
|
||||||
newEvent.id,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// optimistically update object
|
// optimistically update object
|
||||||
queryClient.setQueryData([EVENTS_TABLE, newEvent.id], newEvent);
|
queryClient.setQueryData([EVENTS_TABLE, newEvent.id], newEvent);
|
||||||
@@ -86,10 +80,7 @@ export default function EventListWrapper() {
|
|||||||
|
|
||||||
// Mutation fails, rollback undos optimist update
|
// Mutation fails, rollback undos optimist update
|
||||||
onError: (error, newEvent, context) => {
|
onError: (error, newEvent, context) => {
|
||||||
queryClient.setQueryData(
|
queryClient.setQueryData([EVENTS_TABLE, context.newEvent.id], context.previousEvent);
|
||||||
[EVENTS_TABLE, context.newEvent.id],
|
|
||||||
context.previousEvent
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
// Mutation finished, failed or successful
|
// Mutation finished, failed or successful
|
||||||
// Fetch anyway, just to be sure
|
// Fetch anyway, just to be sure
|
||||||
@@ -105,10 +96,7 @@ export default function EventListWrapper() {
|
|||||||
queryClient.cancelQueries([EVENTS_TABLE, newEvent.id]);
|
queryClient.cancelQueries([EVENTS_TABLE, newEvent.id]);
|
||||||
|
|
||||||
// Snapshot the previous value
|
// Snapshot the previous value
|
||||||
const previousEvent = queryClient.getQueryData([
|
const previousEvent = queryClient.getQueryData([EVENTS_TABLE, newEvent.id]);
|
||||||
EVENTS_TABLE,
|
|
||||||
newEvent.id,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// optimistically update object
|
// optimistically update object
|
||||||
queryClient.setQueryData([EVENTS_TABLE, newEvent.id], newEvent);
|
queryClient.setQueryData([EVENTS_TABLE, newEvent.id], newEvent);
|
||||||
@@ -119,10 +107,7 @@ export default function EventListWrapper() {
|
|||||||
|
|
||||||
// Mutation fails, rollback undos optimist update
|
// Mutation fails, rollback undos optimist update
|
||||||
onError: (error, newEvent, context) => {
|
onError: (error, newEvent, context) => {
|
||||||
queryClient.setQueryData(
|
queryClient.setQueryData([EVENTS_TABLE, context.newEvent.id], context.previousEvent);
|
||||||
[EVENTS_TABLE, context.newEvent.id],
|
|
||||||
context.previousEvent
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
// Mutation finished, failed or successful
|
// Mutation finished, failed or successful
|
||||||
// Fetch anyway, just to be sure
|
// Fetch anyway, just to be sure
|
||||||
@@ -237,11 +222,16 @@ export default function EventListWrapper() {
|
|||||||
|
|
||||||
// Events API
|
// Events API
|
||||||
const eventsHandler = useCallback(
|
const eventsHandler = useCallback(
|
||||||
async (action, payload) => {
|
async (action, payload, options = undefined) => {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case 'add':
|
case 'add':
|
||||||
try {
|
try {
|
||||||
await addEvent.mutateAsync(payload);
|
let newEvent = { ...payload };
|
||||||
|
// there is an option to pass an index of an array to use as start time
|
||||||
|
if (options?.startIsLastEnd !== undefined) {
|
||||||
|
newEvent.timeStart = data[options.startIsLastEnd].timeEnd || 0;
|
||||||
|
}
|
||||||
|
await addEvent.mutateAsync(newEvent);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
emitError(`Error fetching data: ${error.message}`);
|
emitError(`Error fetching data: ${error.message}`);
|
||||||
}
|
}
|
||||||
@@ -326,6 +316,7 @@ export default function EventListWrapper() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
[data]
|
[data]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -23,11 +23,11 @@
|
|||||||
.cursor {
|
.cursor {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
180deg,
|
180deg,
|
||||||
#ff7597 2%,
|
#ff7597 2%,
|
||||||
#0001 3%,
|
#0001 3%,
|
||||||
#0001 97%,
|
#0001 97%,
|
||||||
#ff7597 98%
|
#ff7597 98%
|
||||||
);
|
);
|
||||||
|
|
||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { memo, useMemo } from 'react';
|
import { memo, useContext } from 'react';
|
||||||
import { Divider } from '@chakra-ui/react';
|
import { Divider } from '@chakra-ui/react';
|
||||||
import style from './EventListMenu.module.css';
|
import { CursorContext } from '../../app/context/CursorContext';
|
||||||
import MenuActionButtons from './MenuActionButtons';
|
import MenuActionButtons from './MenuActionButtons';
|
||||||
import CollapseBtn from 'common/components/buttons/CollapseBtn';
|
import CollapseBtn from 'common/components/buttons/CollapseBtn';
|
||||||
import ExpandBtn from 'common/components/buttons/ExpandBtn';
|
import CursorUpBtn from '../../common/components/buttons/CursorUpBtn';
|
||||||
import { SelectSetting, HandleOptions } from 'app/context/settingsAtom';
|
import CursorDownBtn from '../../common/components/buttons/CursorDownBtn';
|
||||||
import { useAtom } from 'jotai';
|
import CursorLockedBtn from 'common/components/buttons/CursorLockedBtn';
|
||||||
import LockIconBtn from 'common/components/buttons/LockIconBtn';
|
import style from './EventListMenu.module.css';
|
||||||
|
|
||||||
const EventListMenu = ({ eventsHandler }) => {
|
const EventListMenu = ({ eventsHandler }) => {
|
||||||
const [cursorSettings] = useAtom(useMemo(() => SelectSetting('cursor'), []));
|
const { isCursorLocked, toggleCursorLocked, moveCursorUp, moveCursorDown } =
|
||||||
const [, SetOption] = useAtom(HandleOptions);
|
useContext(CursorContext);
|
||||||
|
|
||||||
const actionHandler = (action) => {
|
const actionHandler = (action) => {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
@@ -23,12 +23,14 @@ const EventListMenu = ({ eventsHandler }) => {
|
|||||||
case 'block':
|
case 'block':
|
||||||
eventsHandler('add', { type: action, order: 0 });
|
eventsHandler('add', { type: action, order: 0 });
|
||||||
break;
|
break;
|
||||||
|
case 'cursorUp':
|
||||||
|
moveCursorUp();
|
||||||
|
break;
|
||||||
|
case 'cursorDown':
|
||||||
|
moveCursorDown();
|
||||||
|
break;
|
||||||
case 'togglelock':
|
case 'togglelock':
|
||||||
let newSet = 'locked';
|
toggleCursorLocked();
|
||||||
if (cursorSettings === 'locked') {
|
|
||||||
newSet = 'unlocked';
|
|
||||||
}
|
|
||||||
SetOption({ cursor: newSet });
|
|
||||||
break;
|
break;
|
||||||
case 'deleteall':
|
case 'deleteall':
|
||||||
eventsHandler('deleteall');
|
eventsHandler('deleteall');
|
||||||
@@ -40,16 +42,14 @@ const EventListMenu = ({ eventsHandler }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={style.headerButtons}>
|
<div className={style.headerButtons}>
|
||||||
<CollapseBtn
|
<CollapseBtn size='sm' clickhandler={() => eventsHandler('collapseall')} />
|
||||||
size='sm'
|
|
||||||
clickhandler={() => eventsHandler('collapseall')}
|
|
||||||
/>
|
|
||||||
<ExpandBtn size='sm' clickhandler={() => eventsHandler('expandall')} />
|
|
||||||
<Divider orientation='vertical' />
|
<Divider orientation='vertical' />
|
||||||
<LockIconBtn
|
<CursorUpBtn size='sm' clickhandler={() => actionHandler('cursorUp')} />
|
||||||
|
<CursorDownBtn size='sm' clickhandler={() => actionHandler('cursorDown')} />
|
||||||
|
<CursorLockedBtn
|
||||||
size='sm'
|
size='sm'
|
||||||
clickhandler={() => actionHandler('togglelock')}
|
clickhandler={() => actionHandler('togglelock')}
|
||||||
active={cursorSettings === 'locked'}
|
active={isCursorLocked}
|
||||||
/>
|
/>
|
||||||
<Divider orientation='vertical' />
|
<Divider orientation='vertical' />
|
||||||
<MenuActionButtons actionHandler={actionHandler} size='sm' />
|
<MenuActionButtons actionHandler={actionHandler} size='sm' />
|
||||||
|
|||||||
@@ -1,16 +1,6 @@
|
|||||||
import { ModalBody } from '@chakra-ui/modal';
|
import { ModalBody } from '@chakra-ui/modal';
|
||||||
import {
|
import { Checkbox, FormControl, FormLabel, Input, PinInput, PinInputField } from '@chakra-ui/react';
|
||||||
FormControl,
|
import { getSettings, ontimePlaceholderSettings, postSettings } from 'app/api/ontimeApi';
|
||||||
FormLabel,
|
|
||||||
Input,
|
|
||||||
PinInput,
|
|
||||||
PinInputField,
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
import {
|
|
||||||
getSettings,
|
|
||||||
ontimePlaceholderSettings,
|
|
||||||
postSettings,
|
|
||||||
} from 'app/api/ontimeApi';
|
|
||||||
import { useContext, useEffect, useState } from 'react';
|
import { useContext, useEffect, useState } from 'react';
|
||||||
import { useFetch } from 'app/hooks/useFetch';
|
import { useFetch } from 'app/hooks/useFetch';
|
||||||
import { APP_SETTINGS } from 'app/api/apiConstants';
|
import { APP_SETTINGS } from 'app/api/apiConstants';
|
||||||
@@ -20,6 +10,7 @@ import { IconButton } from '@chakra-ui/button';
|
|||||||
import { FiEye } from 'react-icons/fi';
|
import { FiEye } from 'react-icons/fi';
|
||||||
import SubmitContainer from './SubmitContainer';
|
import SubmitContainer from './SubmitContainer';
|
||||||
import { inputProps } from './modalHelper';
|
import { inputProps } from './modalHelper';
|
||||||
|
import { LocalEventSettingsContext } from '../../app/context/LocalEventSettingsContext';
|
||||||
|
|
||||||
export default function AppSettingsModal() {
|
export default function AppSettingsModal() {
|
||||||
const { data, status, refetch } = useFetch(APP_SETTINGS, getSettings);
|
const { data, status, refetch } = useFetch(APP_SETTINGS, getSettings);
|
||||||
@@ -29,6 +20,19 @@ export default function AppSettingsModal() {
|
|||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
const [hidePin, setHidePin] = useState(true);
|
const [hidePin, setHidePin] = useState(true);
|
||||||
|
|
||||||
|
const {
|
||||||
|
showQuickEntry,
|
||||||
|
setShowQuickEntry,
|
||||||
|
starTimeIsLastEnd,
|
||||||
|
setStarTimeIsLastEnd,
|
||||||
|
defaultPublic,
|
||||||
|
setDefaultPublic,
|
||||||
|
} = useContext(LocalEventSettingsContext);
|
||||||
|
|
||||||
|
const [doShowQuickEntry, setDoShowQuickEntry] = useState(showQuickEntry);
|
||||||
|
const [doStarTimeIsLastEnd, setDoStarTimeIsLastEnd] = useState(starTimeIsLastEnd);
|
||||||
|
const [doDefaultPublic, setDoDefaultPublic] = useState(defaultPublic);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set formdata from server state
|
* Set formdata from server state
|
||||||
*/
|
*/
|
||||||
@@ -40,6 +44,24 @@ export default function AppSettingsModal() {
|
|||||||
});
|
});
|
||||||
}, [changed, data]);
|
}, [changed, data]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set formdata from context
|
||||||
|
*/
|
||||||
|
useEffect(() => {
|
||||||
|
if (showQuickEntry == null) return;
|
||||||
|
setDoShowQuickEntry(showQuickEntry);
|
||||||
|
}, [showQuickEntry]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (starTimeIsLastEnd == null) return;
|
||||||
|
setDoStarTimeIsLastEnd(starTimeIsLastEnd);
|
||||||
|
}, [starTimeIsLastEnd]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (defaultPublic == null) return;
|
||||||
|
setDoDefaultPublic(defaultPublic);
|
||||||
|
}, [defaultPublic]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate and submit data
|
* Validate and submit data
|
||||||
*/
|
*/
|
||||||
@@ -47,29 +69,38 @@ export default function AppSettingsModal() {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
|
|
||||||
|
// set context
|
||||||
|
setShowQuickEntry(doShowQuickEntry);
|
||||||
|
setStarTimeIsLastEnd(doStarTimeIsLastEnd);
|
||||||
|
setDefaultPublic(doDefaultPublic);
|
||||||
|
|
||||||
const f = formData;
|
const f = formData;
|
||||||
let e = { status: false, message: '' };
|
|
||||||
|
|
||||||
// Validate fields
|
// we might not have changed this
|
||||||
if (f.pinCode === '' || f.pinCode == null) {
|
if (f.pinCode !== data.pinCode) {
|
||||||
e.status = true;
|
let e = { status: false, message: '' };
|
||||||
e.message += 'App pin code removed';
|
|
||||||
} else {
|
// Validate fields
|
||||||
e.status = true;
|
if (f.pinCode === '' || f.pinCode == null) {
|
||||||
e.message += 'App pin code added';
|
e.status = true;
|
||||||
|
e.message += 'App pin code removed';
|
||||||
|
} else {
|
||||||
|
e.status = true;
|
||||||
|
e.message += 'App pin code added';
|
||||||
|
}
|
||||||
|
|
||||||
|
// set fields with error
|
||||||
|
if (!e.status) {
|
||||||
|
emitError(`Invalid Input: ${e.message}`);
|
||||||
|
} else {
|
||||||
|
await postSettings(formData);
|
||||||
|
await refetch();
|
||||||
|
emitWarning(e.message);
|
||||||
|
setChanged(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// set fields with error
|
|
||||||
if (!e.status) {
|
|
||||||
emitError(`Invalid Input: ${e.message}`);
|
|
||||||
} else {
|
|
||||||
await postSettings(formData);
|
|
||||||
await refetch();
|
|
||||||
emitWarning(e.message);
|
|
||||||
setChanged(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
|
setChanged(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -78,6 +109,11 @@ export default function AppSettingsModal() {
|
|||||||
const revert = async () => {
|
const revert = async () => {
|
||||||
setChanged(false);
|
setChanged(false);
|
||||||
await refetch();
|
await refetch();
|
||||||
|
|
||||||
|
// set from context
|
||||||
|
setDoShowQuickEntry(showQuickEntry);
|
||||||
|
setDoStarTimeIsLastEnd(starTimeIsLastEnd);
|
||||||
|
setDoDefaultPublic(defaultPublic);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -92,6 +128,13 @@ export default function AppSettingsModal() {
|
|||||||
setChanged(true);
|
setChanged(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets changed flag to true
|
||||||
|
*/
|
||||||
|
const handleContextChange = () => {
|
||||||
|
setChanged(true);
|
||||||
|
};
|
||||||
|
|
||||||
const disableModal = status !== 'success';
|
const disableModal = status !== 'success';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -157,6 +200,36 @@ export default function AppSettingsModal() {
|
|||||||
</div>
|
</div>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</div>
|
</div>
|
||||||
|
<div className={style.hSeparator}>Create Event Default Settings</div>
|
||||||
|
<div className={style.modalColumn}>
|
||||||
|
<Checkbox
|
||||||
|
isChecked={doShowQuickEntry}
|
||||||
|
onChange={(e) => {
|
||||||
|
setDoShowQuickEntry(e.target.checked);
|
||||||
|
handleContextChange();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Show quick entry on hover
|
||||||
|
</Checkbox>
|
||||||
|
<Checkbox
|
||||||
|
isChecked={doStarTimeIsLastEnd}
|
||||||
|
onChange={(e) => {
|
||||||
|
setDoStarTimeIsLastEnd(e.target.checked);
|
||||||
|
handleContextChange();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Start time is last end
|
||||||
|
</Checkbox>
|
||||||
|
<Checkbox
|
||||||
|
isChecked={doDefaultPublic}
|
||||||
|
onChange={(e) => {
|
||||||
|
setDoDefaultPublic(e.target.checked);
|
||||||
|
handleContextChange();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Event default public
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<SubmitContainer
|
<SubmitContainer
|
||||||
revert={revert}
|
revert={revert}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
color: $error-red;
|
color: $error-red;
|
||||||
@@ -72,6 +73,13 @@
|
|||||||
padding: 0 0.5em 0.5em 0.5em;
|
padding: 0 0.5em 0.5em 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modalColumn {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 0 0.5em 0.5em 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
.spacedEntry {
|
.spacedEntry {
|
||||||
padding: 0 0.5em 0.5em 0.5em;
|
padding: 0 0.5em 0.5em 0.5em;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ export default function SubmitContainer(props) {
|
|||||||
return (
|
return (
|
||||||
<div className={style.submitContainer}>
|
<div className={style.submitContainer}>
|
||||||
<Button
|
<Button
|
||||||
type='submit'
|
|
||||||
isDisabled={submitting || !changed}
|
isDisabled={submitting || !changed}
|
||||||
variant='ghosted'
|
variant='ghosted'
|
||||||
onClick={() => revert()}
|
onClick={() => revert()}
|
||||||
|
|||||||
@@ -22,6 +22,14 @@ $bg-black: #121212;
|
|||||||
$bg-black-gradient: #202020;
|
$bg-black-gradient: #202020;
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////////// block elements
|
||||||
|
$block-delay-color: #ecc94b;
|
||||||
|
$block-block-color: #805ad5;
|
||||||
|
$block-icon-drag: rgba(255, 255, 255, 0.67);
|
||||||
|
$block-border: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//////////////////////////////////// general app element overriders
|
//////////////////////////////////// general app element overriders
|
||||||
|
|
||||||
// no decoration on lists
|
// no decoration on lists
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ontime",
|
"name": "ontime",
|
||||||
"version": "0.6.3",
|
"version": "0.7.0",
|
||||||
"author": "Carlos Valente",
|
"author": "Carlos Valente",
|
||||||
"description": "Time keeping for live events",
|
"description": "Time keeping for live events",
|
||||||
"repository": "https://github.com/cpvalente/ontime",
|
"repository": "https://github.com/cpvalente/ontime",
|
||||||
|
|||||||
Reference in New Issue
Block a user