// @flow import React, { useState } from 'react'; import classnames from 'classnames'; import Icon from 'component/common/icon'; import * as ICONS from 'constants/icons'; import 'scss/component/homepage-sort.scss'; // prettier-ignore const Lazy = { // $FlowFixMe: cannot resolve dnd DragDropContext: React.lazy(() => import('react-beautiful-dnd' /* webpackChunkName: "dnd" */).then((module) => ({ default: module.DragDropContext }))), // $FlowFixMe: cannot resolve dnd Droppable: React.lazy(() => import('react-beautiful-dnd' /* webpackChunkName: "dnd" */).then((module) => ({ default: module.Droppable }))), // $FlowFixMe: cannot resolve dnd Draggable: React.lazy(() => import('react-beautiful-dnd' /* webpackChunkName: "dnd" */).then((module) => ({ default: module.Draggable }))), }; const NON_CATEGORY = Object.freeze({ FOLLOWING: { label: 'Following' }, FYP: { label: 'Recommended' }, }); // **************************************************************************** // Helpers // **************************************************************************** const reorder = (list, startIndex, endIndex) => { const result = Array.from(list); const [removed] = result.splice(startIndex, 1); result.splice(endIndex, 0, removed); return result; }; const move = (source, destination, droppableSource, droppableDestination) => { const sourceClone = Array.from(source); const destClone = Array.from(destination); const [removed] = sourceClone.splice(droppableSource.index, 1); destClone.splice(droppableDestination.index, 0, removed); return { [droppableSource.droppableId]: sourceClone, [droppableDestination.droppableId]: destClone, }; }; function getInitialList(listId, savedOrder, homepageSections) { const savedActiveOrder = savedOrder.active || []; const savedHiddenOrder = savedOrder.hidden || []; const sectionKeys = Object.keys(homepageSections); if (listId === 'ACTIVE') { // Start with saved order, excluding obsolete items (i.e. category removed or not available in non-English) const finalOrder = savedActiveOrder.filter((x) => sectionKeys.includes(x)); // Add new items (e.g. new categories) sectionKeys.forEach((x) => { if (!finalOrder.includes(x)) { finalOrder.push(x); } }); // Exclude items that were moved to Hidden return finalOrder.filter((x) => !savedHiddenOrder.includes(x)); } else { console.assert(listId === 'HIDDEN', `Unhandled listId: ${listId}`); return savedHiddenOrder.filter((x) => sectionKeys.includes(x)); } } // **************************************************************************** // HomepageSort // **************************************************************************** type HomepageOrder = { active: ?Array, hidden: ?Array }; type Props = { onUpdate: (newOrder: HomepageOrder) => void, // --- redux: homepageData: any, homepageOrder: HomepageOrder, }; export default function HomepageSort(props: Props) { const { onUpdate, homepageData, homepageOrder } = props; const SECTIONS = { ...NON_CATEGORY, ...homepageData }; const [listActive, setListActive] = useState(() => getInitialList('ACTIVE', homepageOrder, SECTIONS)); const [listHidden, setListHidden] = useState(() => getInitialList('HIDDEN', homepageOrder, SECTIONS)); const BINS = { ACTIVE: { id: 'ACTIVE', title: 'Active', list: listActive, setList: setListActive }, HIDDEN: { id: 'HIDDEN', title: 'Hidden', list: listHidden, setList: setListHidden }, }; function onDragEnd(result) { const { source, destination } = result; if (destination) { if (source.droppableId === destination.droppableId) { const newList = reorder(BINS[source.droppableId].list, source.index, destination.index); BINS[source.droppableId].setList(newList); } else { const result = move(BINS[source.droppableId].list, BINS[destination.droppableId].list, source, destination); BINS[source.droppableId].setList(result[source.droppableId]); BINS[destination.droppableId].setList(result[destination.droppableId]); } } } const draggedItemRef = React.useRef(); const DraggableItem = ({ item, index }: any) => { return ( {(draggableProvided, snapshot) => { if (snapshot.isDragging) { // https://github.com/atlassian/react-beautiful-dnd/issues/1881#issuecomment-691237307 // $FlowFixMe draggableProvided.draggableProps.style.left = draggedItemRef.offsetLeft; // $FlowFixMe draggableProvided.draggableProps.style.top = draggedItemRef.offsetTop; } return (
{__(SECTIONS[item].label)}
); }}
); }; const DroppableBin = ({ bin, className }: any) => { return ( {(provided, snapshot) => (
{__(bin.title)}
{bin.list.map((item, index) => ( ))} {provided.placeholder}
)}
); }; React.useEffect(() => { if (onUpdate) { return onUpdate({ active: listActive, hidden: listHidden }); } }, [listActive, listHidden, onUpdate]); return (
); }