Merge pull request #2 from cpvalente/feat/cursor

Feat/cursor
This commit is contained in:
Carlos Valente
2021-05-20 13:48:52 +02:00
committed by GitHub
9 changed files with 167 additions and 129 deletions
+34
View File
@@ -0,0 +1,34 @@
const { atom } = require('jotai');
const PATH = 'option-gen';
const initialValue = {
cursor: 'locked',
};
export const settingsAtom = atom(
(get) => {
const storedOptions = localStorage.getItem(PATH);
if (storedOptions == null) return initialValue;
return JSON.parse(storedOptions);
},
(get, set, newValues) => {
set(settingsAtom, newValues);
localStorage.setItem(PATH, JSON.stringify(newValues));
}
);
// get a single option, if it exists
export const SelectSetting = (setting) => {
return atom((get) => get(settingsAtom)[setting]);
};
// change a single item in object
export const HandleOptions = atom(null, (get, set, payload) => {
const updatedVal = {
...get(settingsAtom),
...payload,
};
set(settingsAtom, updatedVal);
localStorage.setItem(PATH, JSON.stringify(updatedVal));
});
@@ -1,18 +1,20 @@
import { Button } from '@chakra-ui/button';
import { IconButton } from '@chakra-ui/button';
import { Tooltip } from '@chakra-ui/tooltip';
import { FiChevronsUp } from 'react-icons/fi';
export default function CollapseBtn(props) {
const { clickhandler } = props;
return (
<Button
size={props.size || 'xs'}
leftIcon={<FiChevronsUp />}
colorScheme='white'
variant='outline'
onClick={clickhandler}
_focus={{ boxShadow: 'none' }}
>
Collapse All
</Button>
<Tooltip label='Collapse all'>
<IconButton
size={props.size || 'xs'}
icon={<FiChevronsUp />}
colorScheme='white'
variant='outline'
background='#fff1'
onClick={clickhandler}
_focus={{ boxShadow: 'none' }}
/>
</Tooltip>
);
}
@@ -2,13 +2,13 @@ import { Button } from '@chakra-ui/button';
import { FiTarget } from 'react-icons/fi';
export default function CurrentBtn(props) {
const { clickhandler } = props;
const { clickhandler, active } = props;
return (
<Button
size={props.size || 'xs'}
leftIcon={<FiTarget />}
colorScheme='whiteAlpha'
variant='outline'
variant={active ? 'solid' : 'outline'}
onClick={clickhandler}
_focus={{ boxShadow: 'none' }}
>
@@ -1,18 +1,20 @@
import { Button } from '@chakra-ui/button';
import { IconButton } from '@chakra-ui/button';
import { Tooltip } from '@chakra-ui/tooltip';
import { FiChevronsDown } from 'react-icons/fi';
export default function ExpandBtn(props) {
const { clickhandler } = props;
return (
<Button
size={props.size || 'xs'}
leftIcon={<FiChevronsDown />}
colorScheme='white'
variant='outline'
onClick={clickhandler}
_focus={{ boxShadow: 'none' }}
>
Expand All
</Button>
<Tooltip label='Expand all'>
<IconButton
size={props.size || 'xs'}
icon={<FiChevronsDown />}
colorScheme='white'
variant='outline'
background='#fff1'
onClick={clickhandler}
_focus={{ boxShadow: 'none' }}
/>
</Tooltip>
);
}
@@ -0,0 +1,21 @@
import { IconButton } from '@chakra-ui/button';
import { Tooltip } from '@chakra-ui/tooltip';
import { FiTarget } from 'react-icons/fi';
export default function LockIconBtn(props) {
const { clickhandler, active, ref } = props;
return (
<Tooltip label='Lock cursor to current'>
<IconButton
ref={ref}
size={props.size || 'xs'}
icon={<FiTarget />}
colorScheme='pink'
color={'pink.300'}
variant={active ? 'solid' : 'outline'}
onClick={clickhandler}
_focus={{ boxShadow: 'none' }}
/>
</Tooltip>
);
}
+41 -47
View File
@@ -1,18 +1,22 @@
import style from './List.module.css';
import { Fragment, useEffect, useState } from 'react';
import { createRef, useEffect, useMemo, useState } from 'react';
import { useSocket } from 'app/context/socketContext';
import tinykeys from 'tinykeys';
import Empty from 'common/state/Empty';
import EventListItem from './EventListItem';
import { AnimatePresence, motion } from 'framer-motion';
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
import { useAtom } from 'jotai';
import { SelectSetting } from 'app/context/settingsAtom';
export default function EventList(props) {
const { events, eventsHandler } = props;
const socket = useSocket();
const [selected, setSelected] = useState(null);
const [next, setNext] = useState(null);
const [cursor, setCursor] = useState(null);
const [cursor, setCursor] = useState(0);
const [cursorSettings] = useAtom(useMemo(() => SelectSetting('cursor'), []));
const cursorRef = createRef();
// Handle keyboard shortcuts
useEffect(() => {
@@ -52,12 +56,13 @@ export default function EventList(props) {
if (socket == null) return;
// ask for playstate
socket.emit('get-selected-id');
socket.emit('get-selected');
socket.emit('get-next-id');
// Handle playstate
socket.on('selected-id', (data) => {
socket.on('selected', (data) => {
setSelected(data);
console.log('debug cursor setted', data);
});
socket.on('next-id', (data) => {
setNext(data);
@@ -65,31 +70,35 @@ export default function EventList(props) {
// Clear listener
return () => {
socket.off('selected-id');
socket.off('selected');
socket.off('next-id');
};
}, [socket]);
// attach cursor to selected
useEffect(() => {
if (cursorSettings !== 'locked' || selected == null) return;
if (selected.index == null) return;
setCursor(selected.index);
}, [selected, cursorSettings]);
// attach scroll to cursor
useEffect(() => {
if (cursor == null || cursorRef.current == null) return;
console.log('debug cursor scrolling');
cursorRef.current.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'start',
});
}, [cursor, cursorRef]);
if (events.length < 1) {
return <Empty text='No Events' />;
}
// motion
const cursorVariants = {
hidden: {
scale: 0,
},
visible: {
scale: 1,
transition: {
duration: 0.3,
},
},
exit: {
scale: 0,
},
};
// DND
const handleOnDragEnd = (result) => {
// drop outside of area
@@ -109,19 +118,10 @@ export default function EventList(props) {
console.log('EventList: events in event list', events);
let cumulativeDelay = 0;
console.log('debug selected', selected);
return (
<div className={style.eventContainer}>
<AnimatePresence>
{cursor === -1 && (
<motion.div
className={style.cursor}
variants={cursorVariants}
initial='hidden'
animate='visible'
exit='exit'
/>
)}
</AnimatePresence>
<DragDropContext onDragEnd={handleOnDragEnd}>
<Droppable droppableId='eventlist'>
{(provided) => (
@@ -131,33 +131,27 @@ export default function EventList(props) {
ref={provided.innerRef}
>
{events.map((e, index) => {
let isCursor = cursor === index;
if (index === 0) cumulativeDelay = 0;
if (e.type === 'delay' && e.duration != null) {
cumulativeDelay += e.duration;
} else if (e.type === 'block') cumulativeDelay = 0;
return (
<Fragment key={e.id}>
<div
ref={isCursor ? cursorRef : undefined}
key={e.id}
className={isCursor ? style.cursor : undefined}
>
<EventListItem
type={e.type}
index={index}
data={e}
selected={selected === e.id}
selected={selected?.id === e.id}
next={next === e.id}
eventsHandler={eventsHandler}
delay={cumulativeDelay}
/>
<AnimatePresence>
{cursor === index && (
<motion.div
className={style.cursor}
variants={cursorVariants}
initial='hidden'
animate='visible'
exit='exit'
/>
)}
</AnimatePresence>
</Fragment>
</div>
);
})}
{provided.placeholder}
@@ -21,11 +21,14 @@
}
.cursor {
width: 90%;
min-height: 2px;
background-color: #ff7597;
border-radius: 2px;
margin: 0 auto;
/* transition: 1s;
transition-property: all; */
width: 100%;
background: linear-gradient(
180deg,
#ff7597 2%,
#0001 3%,
#0001 97%,
#ff7597 98%
);
border-radius: 14px;
}
+22 -51
View File
@@ -1,34 +1,16 @@
import { memo } from 'react';
import { FiChevronDown } from 'react-icons/fi';
import {
Button,
ButtonGroup,
Menu,
MenuButton,
MenuItem,
MenuList,
} from '@chakra-ui/react';
import { memo, useMemo } from 'react';
import { Divider } from '@chakra-ui/react';
import style from './EventListMenu.module.css';
import MenuActionButtons from '../editors/list/MenuActionButtons';
import MenuActionButtons from './MenuActionButtons';
import CollapseBtn from 'common/components/buttons/CollapseBtn';
import ExpandBtn from 'common/components/buttons/ExpandBtn';
import CurrentBtn from 'common/components/buttons/CurrentBtn';
import { SelectSetting, HandleOptions } from 'app/context/settingsAtom';
import { useAtom } from 'jotai';
import LockIconBtn from 'common/components/buttons/LockIconBtn';
const EventListMenu = ({ eventsHandler }) => {
const buttonProps = {
size: 'sm',
variant: 'outline',
colorScheme: 'whiteAlpha',
backgroundColor: '#ffffff05',
_hover: { bg: 'blue.800' },
_expanded: { bg: 'blue.400' },
_focus: { boxShadow: 'none' },
};
const menuStyle = {
color: 'initial',
backgroundColor: 'rgba(255,255,255,0.67)',
};
const [cursorSettings] = useAtom(useMemo(() => SelectSetting('cursor'), []));
const [, SetOption] = useAtom(HandleOptions);
const actionHandler = (action) => {
switch (action) {
@@ -41,6 +23,13 @@ const EventListMenu = ({ eventsHandler }) => {
case 'block':
eventsHandler('add', { type: action, order: 0 });
break;
case 'togglelock':
let newSet = 'locked';
if (cursorSettings === 'locked') {
newSet = 'unlocked';
}
SetOption({ cursor: newSet });
break;
default:
break;
}
@@ -48,36 +37,18 @@ const EventListMenu = ({ eventsHandler }) => {
return (
<div className={style.headerButtons}>
<Menu className={style.menu} isLazy>
<ButtonGroup isAttached>
<Button {...buttonProps}>Upload</Button>
<MenuButton as={Button} {...buttonProps}>
<FiChevronDown />
</MenuButton>
</ButtonGroup>
<MenuList style={menuStyle}>
<MenuItem>Upload Excel</MenuItem>
<MenuItem>Upload CSV</MenuItem>
</MenuList>
</Menu>
<Menu>
<ButtonGroup isAttached>
<Button {...buttonProps}>Save</Button>
<MenuButton as={Button} {...buttonProps}>
<FiChevronDown />
</MenuButton>
</ButtonGroup>
<MenuList style={menuStyle}>
<MenuItem>Download Excel</MenuItem>
<MenuItem>Download CSV</MenuItem>
</MenuList>
</Menu>
<CurrentBtn size='sm' />
<CollapseBtn
size='sm'
clickhandler={() => eventsHandler('collapseall')}
/>
<ExpandBtn size='sm' clickhandler={() => eventsHandler('expandall')} />
<Divider orientation='vertical' />
<LockIconBtn
size='sm'
clickhandler={() => actionHandler('togglelock')}
active={cursorSettings === 'locked'}
/>
<Divider orientation='vertical' />
<MenuActionButtons actionHandler={actionHandler} size='sm' />
</div>
);
+11
View File
@@ -90,6 +90,10 @@ class EventTimer extends Timer {
broadcastState() {
this.io.emit('timer', this.getObject());
this.io.emit('playstate', this.state);
this.io.emit('selected', {
id: this.selectedEventId,
index: this.selectedEventIndex,
});
this.io.emit('selected-id', this.selectedEventId);
this.io.emit('next-id', this.nextEventId);
this.io.emit('publicselected-id', this.selectedPublicEventId);
@@ -259,6 +263,13 @@ class EventTimer extends Timer {
/*******************************************/
// selection data
socket.on('get-selected', () => {
socket.emit('selected', {
id: this.selectedEventId,
index: this.selectedEventIndex,
});
});
socket.on('get-selected-id', () => {
socket.emit('selected-id', this.selectedEventId);
});