react-weekly-table
Version:
React weekly scheduler <br/> By default build time ranges for a week, supports up to 31 days <br/> Can work with different timezones, data always return to UTC+0
194 lines (193 loc) • 7.73 kB
JavaScript
import { jsx as _jsx } from "react/jsx-runtime";
import { createContext, useCallback, useContext, useEffect, useMemo, useState, } from 'react';
import { ACTION, DIR, HOUR_24, } from '../common';
import { useCells } from './CellsProvider';
import { usePointerLock } from './PointerLockProvider';
import { useScheduler } from './Scheduler';
import { useTimeBlock } from './TimeBlockProvider';
const PopupContext = createContext({});
/**
* Popup provider context hook
* @returns [PopupProps]{@link PopupProps}
*/
export const usePopup = () => useContext(PopupContext);
/**
* Popup provider, operates most timeblock actions
*/
const PopupProvider = ({ children }) => {
const { blocks, setBlocks, handleHistory, undoHistory } = useTimeBlock();
const { millisPerPixel, msTime } = useCells();
const { isLocking, movement, action } = usePointerLock();
const { mouseSpeed } = useScheduler();
/**
* Popup state, component have only one popup
*/
const [popup, setPopup] = useState(undefined);
/**
* Hotkeys state
*/
const [keyRequest, setKeyRequest] = useState({ code: '', ctrl: false });
/**
* Hotkeys event listeners
*/
useEffect(() => {
document.addEventListener('keydown', handleKey);
return () => {
document.removeEventListener('keydown', handleKey);
};
//eslint-disable-next-line
}, []);
/**
* Hotkeys actions handler
*/
const handleKey = useCallback((event) => {
if (event.code) {
setKeyRequest({ code: event.code, ctrl: event.ctrlKey });
}
}, []);
/**
* Hotkeys actions
*/
useEffect(() => {
if (keyRequest.code === 'Delete' || keyRequest.code === 'Backspace') {
if (popup === null || popup === void 0 ? void 0 : popup.id) {
deleteBlock(popup.id);
}
setKeyRequest({ code: '', ctrl: false });
return;
}
if (keyRequest.code === 'KeyZ' && keyRequest.ctrl) {
undoHistory();
setKeyRequest({ code: '', ctrl: false });
}
//eslint-disable-next-line
}, [keyRequest.code, keyRequest.ctrl, popup === null || popup === void 0 ? void 0 : popup.id]);
/**
* Popup rendering over timeblock
*/
const showPopup = useCallback((blockId) => {
const block = blocks.find((b) => b.id === blockId);
if (!block)
return;
setPopup({
id: block.id,
block: block,
});
}, [blocks]);
/**
* Popup hiding eq removing
*/
const hidePopup = useCallback(() => {
setPopup(undefined);
}, []);
/**
* isAllowing timerange state
* Timeblock will stopping actions if it less than timeframe
* Don't affecting to custom time setting
*/
const isAllowing = useMemo(() => {
if (!(popup === null || popup === void 0 ? void 0 : popup.block))
return true;
return popup.block.endTime - popup.block.startTime >= msTime;
}, [msTime, popup === null || popup === void 0 ? void 0 : popup.block]);
/**
* Block remover
*/
const deleteBlock = useCallback((blockId) => {
setBlocks([...blocks].filter((b) => b.id !== blockId));
handleHistory([...blocks].filter((b) => b.id !== blockId));
setPopup(undefined);
},
//eslint-disable-next-line
[blocks]);
/**
* Move handler - moving block with mouse pressed
* Hidden realTime field used
* Rounding visible time every timeframe
*/
const handleMove = useCallback((block, movementY) => {
const oldTop = block.top;
const newTop = oldTop + movementY * mouseSpeed;
const newBlocks = [...blocks].filter((b) => b.id !== block.id);
const dateDiff = (newTop - oldTop) * millisPerPixel;
const newStart = block.realStartTime + dateDiff;
const newEnd = block.realEndTime + dateDiff;
if (newStart < -msTime / 3 ||
newEnd > HOUR_24 + msTime / 3 ||
newEnd - newStart < msTime) {
return;
}
const roundedStart = Math.abs(Math.round(block.realStartTime / msTime) * msTime);
const roundedEnd = Math.round(block.realEndTime / msTime) * msTime;
const movedBlock = Object.assign(Object.assign({}, block), { startTime: roundedStart, endTime: roundedEnd, realStartTime: newStart, realEndTime: newEnd, top: newTop });
setBlocks(newBlocks.concat(movedBlock));
}, [mouseSpeed, blocks, millisPerPixel, msTime]);
/**
* Resize handler - resizing block with mouse pressed
* Hidden realTime field used
* Rounding visible time every timeframe
*/
const handleResize = useCallback((block, movementY, dir) => {
const newBlocks = blocks.filter((b) => b.id !== block.id);
if (dir === DIR.TOP) {
const oldTop = block.top;
const newTop = oldTop + movementY * mouseSpeed;
const dateDiff = (newTop - oldTop) * millisPerPixel;
const newStart = block.realStartTime + dateDiff;
if (newStart < -msTime / 3)
return;
if (block.realEndTime - newStart < msTime)
return;
const roundedStart = Math.abs(Math.round(block.realStartTime / msTime) * msTime);
const movedBlock = Object.assign(Object.assign({}, block), { startTime: roundedStart, realStartTime: newStart, top: newTop, height: block.height + oldTop - newTop });
setBlocks(newBlocks.concat(movedBlock));
return;
}
if (dir === DIR.BOTTOM) {
const oldBot = block.height;
const newBot = oldBot + movementY * mouseSpeed;
const dateDiff = (newBot - oldBot) * millisPerPixel;
const newEnd = block.realEndTime + dateDiff;
if (newEnd > HOUR_24 + msTime / 3)
return;
if (newEnd - block.realStartTime < msTime)
return;
let roundedEnd = Math.round(block.realEndTime / msTime) * msTime;
if (roundedEnd > HOUR_24)
roundedEnd = HOUR_24;
const movedBlock = Object.assign(Object.assign({}, block), { endTime: roundedEnd, realEndTime: newEnd, height: newBot });
setBlocks(newBlocks.concat(movedBlock));
}
}, [blocks, millisPerPixel, mouseSpeed, msTime]);
/**
* mouseevents router when timeblock actions
*/
useEffect(() => {
if (isLocking && (popup === null || popup === void 0 ? void 0 : popup.block) && movement !== 0) {
switch (action) {
case ACTION.MOVE: {
handleMove(popup.block, movement);
return;
}
case ACTION.RESIZE_TOP: {
handleResize(popup.block, movement, DIR.TOP);
return;
}
case ACTION.RESIZE_BOT: {
handleResize(popup.block, movement, DIR.BOTTOM);
return;
}
default:
return;
}
}
}, [action, isLocking, movement, popup === null || popup === void 0 ? void 0 : popup.block]);
return (_jsx(PopupContext.Provider, Object.assign({ value: {
isAllowing,
popup,
hidePopup,
deleteBlock,
showPopup,
} }, { children: children })));
};
export default PopupProvider;