mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-21 15:09:10 +00:00
refactor: event editor color picker (#1400)
* refactor: event editor color picker * refactor: throttle and debounce function signature
This commit is contained in:
@@ -0,0 +1,54 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
import { IoEyedrop } from '@react-icons/all-files/io5/IoEyedrop';
|
||||||
|
import Color from 'color';
|
||||||
|
|
||||||
|
import PopoverPicker from '../../../../common/components/input/popover-picker/PopoverPicker';
|
||||||
|
import { debounce } from '../../../../common/utils/debounce';
|
||||||
|
import { cx } from '../../../utils/styleUtils';
|
||||||
|
|
||||||
|
import style from './SwatchSelect.module.scss';
|
||||||
|
|
||||||
|
interface SwatchPickerProps {
|
||||||
|
color: string;
|
||||||
|
isSelected?: boolean;
|
||||||
|
onChange: (name: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getIconColor = (color: string, isSelected: boolean) => {
|
||||||
|
if (isSelected) {
|
||||||
|
try {
|
||||||
|
const isLight = Color(color).isLight();
|
||||||
|
return isLight ? '#000000' : '#ffffff';
|
||||||
|
} catch (_error) {
|
||||||
|
/* we are not handling the error here */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '#ffffff';
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function SwatchPicker(props: SwatchPickerProps) {
|
||||||
|
const { color, onChange, isSelected } = props;
|
||||||
|
|
||||||
|
const classes = cx([style.swatch, isSelected ? style.selected : null, style.selectable]);
|
||||||
|
|
||||||
|
const iconColor = getIconColor(color, isSelected ?? false);
|
||||||
|
|
||||||
|
const debouncedOnChange = useCallback(
|
||||||
|
debounce((newValue: string) => {
|
||||||
|
onChange(newValue);
|
||||||
|
}, 500),
|
||||||
|
[onChange],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes}>
|
||||||
|
<PopoverPicker
|
||||||
|
color={isSelected ? color : ''}
|
||||||
|
onChange={debouncedOnChange}
|
||||||
|
icon={<IoEyedrop color={iconColor} />}
|
||||||
|
hasInput
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import Swatch from './Swatch';
|
import Swatch from './Swatch';
|
||||||
|
import SwatchPicker from './SwatchPicker';
|
||||||
|
|
||||||
import style from './SwatchSelect.module.scss';
|
import style from './SwatchSelect.module.scss';
|
||||||
|
|
||||||
@@ -43,6 +44,7 @@ export default function SwatchSelect(props: ColourInputProps) {
|
|||||||
{colours.map((colour) => (
|
{colours.map((colour) => (
|
||||||
<Swatch key={colour} color={colour} onClick={setColour} isSelected={value === colour} />
|
<Swatch key={colour} color={colour} onClick={setColour} isSelected={value === colour} />
|
||||||
))}
|
))}
|
||||||
|
<SwatchPicker color={value} onChange={setColour} isSelected={!colours.includes(value)} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
.swatch {
|
.swatch {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
@@ -14,3 +17,23 @@
|
|||||||
box-shadow: 0 0 0 1px $blue-300;
|
box-shadow: 0 0 0 1px $blue-300;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
color: $gray-200;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 0 3px;
|
||||||
|
background-color: $gray-1200;
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
height: 2rem;
|
||||||
|
outline: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $gray-1100;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
background-color: $gray-1000;
|
||||||
|
color: $gray-50;
|
||||||
|
border: 1px solid $blue-500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { HexAlphaColorPicker } from 'react-colorful';
|
import { ReactNode } from 'react';
|
||||||
|
import { HexAlphaColorPicker, HexColorInput } from 'react-colorful';
|
||||||
import { useController, UseControllerProps } from 'react-hook-form';
|
import { useController, UseControllerProps } from 'react-hook-form';
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from '@chakra-ui/react';
|
import { Popover, PopoverContent, PopoverTrigger } from '@chakra-ui/react';
|
||||||
import { ViewSettings } from 'ontime-types';
|
import { ViewSettings } from 'ontime-types';
|
||||||
@@ -16,19 +17,24 @@ export function PopoverPickerRHF(props: UseControllerProps<ViewSettings>) {
|
|||||||
|
|
||||||
interface PopoverPickerProps {
|
interface PopoverPickerProps {
|
||||||
color: string;
|
color: string;
|
||||||
|
icon?: ReactNode;
|
||||||
|
hasInput?: boolean;
|
||||||
onChange: (color: string) => void;
|
onChange: (color: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PopoverPicker(props: PopoverPickerProps) {
|
export default function PopoverPicker(props: PopoverPickerProps) {
|
||||||
const { color, onChange } = props;
|
const { color, icon, hasInput, onChange } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger>
|
<PopoverTrigger>
|
||||||
<div className={style.swatch} style={{ backgroundColor: color }} />
|
<div className={style.swatch} style={{ backgroundColor: color }}>
|
||||||
|
{icon ?? null}
|
||||||
|
</div>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent style={{ width: 'auto' }}>
|
<PopoverContent style={{ width: 'auto' }}>
|
||||||
<HexAlphaColorPicker color={color} onChange={onChange} />
|
<HexAlphaColorPicker color={color} onChange={onChange} />
|
||||||
|
{hasInput && <HexColorInput color={color} onChange={onChange} className={style.input} prefixed />}
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { debounce } from '../../common/utils/debounce';
|
import { throttle } from '../../common/utils/throttle';
|
||||||
|
|
||||||
export const useFadeOutOnInactivity = () => {
|
export const useFadeOutOnInactivity = () => {
|
||||||
const [isMouseMoved, setIsMouseMoved] = useState(false);
|
const [isMouseMoved, setIsMouseMoved] = useState(false);
|
||||||
@@ -16,11 +16,11 @@ export const useFadeOutOnInactivity = () => {
|
|||||||
fadeOut = setTimeout(() => setIsMouseMoved(false), 3000);
|
fadeOut = setTimeout(() => setIsMouseMoved(false), 3000);
|
||||||
};
|
};
|
||||||
|
|
||||||
const debouncedShowMenu = debounce(setShowMenuTrue, 1000);
|
const throttledShowMenu = throttle(setShowMenuTrue, 1000);
|
||||||
|
|
||||||
document.addEventListener('mousemove', debouncedShowMenu);
|
document.addEventListener('mousemove', throttledShowMenu);
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('mousemove', debouncedShowMenu);
|
document.removeEventListener('mousemove', throttledShowMenu);
|
||||||
if (fadeOut) {
|
if (fadeOut) {
|
||||||
clearTimeout(fadeOut);
|
clearTimeout(fadeOut);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
export function debounce(callback: () => void, wait: number) {
|
export function debounce<T extends any[]>(callback: (...args: T) => void, wait: number) {
|
||||||
let timeout: NodeJS.Timeout | null;
|
let timeout: NodeJS.Timeout | null;
|
||||||
return () => {
|
return (...args: T) => {
|
||||||
if (timeout) {
|
if (timeout) {
|
||||||
return;
|
clearTimeout(timeout);
|
||||||
}
|
}
|
||||||
timeout = setTimeout(() => {
|
timeout = setTimeout(() => {
|
||||||
timeout = null;
|
timeout = null;
|
||||||
callback();
|
callback(...args);
|
||||||
}, wait);
|
}, wait);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
export function throttle<T extends any[]>(callback: (...args: T) => void, wait: number) {
|
||||||
|
let timeout: NodeJS.Timeout | null;
|
||||||
|
return (...args: T) => {
|
||||||
|
if (timeout) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
timeout = null;
|
||||||
|
callback(...args);
|
||||||
|
}, wait);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ import useCustomFields from '../../common/hooks-query/useCustomFields';
|
|||||||
import useProjectData from '../../common/hooks-query/useProjectData';
|
import useProjectData from '../../common/hooks-query/useProjectData';
|
||||||
import useRundown from '../../common/hooks-query/useRundown';
|
import useRundown from '../../common/hooks-query/useRundown';
|
||||||
import useSettings from '../../common/hooks-query/useSettings';
|
import useSettings from '../../common/hooks-query/useSettings';
|
||||||
import { debounce } from '../../common/utils/debounce';
|
import { throttle } from '../../common/utils/throttle';
|
||||||
import { getDefaultFormat } from '../../common/utils/time';
|
import { getDefaultFormat } from '../../common/utils/time';
|
||||||
import { getPropertyValue, isStringBoolean } from '../viewers/common/viewUtils';
|
import { getPropertyValue, isStringBoolean } from '../viewers/common/viewUtils';
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ export default function Operator() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const debouncedHandleScroll = debounce(handleUserScroll, 1000);
|
const throttledHandleScroll = throttle(handleUserScroll, 1000);
|
||||||
|
|
||||||
const handleScroll = () => {
|
const handleScroll = () => {
|
||||||
if (timeoutId.current) {
|
if (timeoutId.current) {
|
||||||
@@ -97,7 +97,7 @@ export default function Operator() {
|
|||||||
|
|
||||||
setShowEditPrompt(true);
|
setShowEditPrompt(true);
|
||||||
|
|
||||||
debouncedHandleScroll();
|
throttledHandleScroll();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEdit = useCallback((event: EditEvent) => {
|
const handleEdit = useCallback((event: EditEvent) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user