From fbd8e27078967dbe8c42feb8a3b9e79bd2247eb8 Mon Sep 17 00:00:00 2001 From: lxieyang Date: Mon, 18 Oct 2021 17:53:35 -0400 Subject: [PATCH] 1.10.0 --- CHANGELOG.md | 7 ++ package-lock.json | 4 +- package.json | 2 +- src/pages/Sidebar/Sidebar.jsx | 41 ++++--- .../Sidebar/containers/TabsList/Tab/Tab.css | 3 +- .../Sidebar/containers/TabsList/Tab/Tab.jsx | 80 +++++++------- .../Sidebar/containers/TabsList/TabsList.jsx | 104 +++++++++++------- src/shared/utils.ts | 10 ++ 8 files changed, 146 insertions(+), 105 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5a2f46..f05f8f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,13 @@ Please provide valuable feedback by: - Creating a [new issue](https://github.com/lxieyang/vertical-tabs-chrome-extension/issues/new) - Email: xieyangl@cs.cmu.edu +### [1.10.0](https://github.com/lxieyang/vertical-tabs-chrome-extension/releases/tag/v1.10.0) (2021-10-18) + +#### New Features + +- Bug fixes +- Smoother drag experience + ### [1.9.1](https://github.com/lxieyang/vertical-tabs-chrome-extension/releases/tag/v1.9.1) (2021-10-17) #### New Features diff --git a/package-lock.json b/package-lock.json index 7bf5f73..2231275 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { "name": "vertical-tabs-chrome-extension", - "version": "1.9.2", + "version": "1.10.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "1.9.2", + "version": "1.10.0", "license": "MIT", "dependencies": { "@hot-loader/react-dom": "^17.0.1", diff --git a/package.json b/package.json index 597eab6..6c41bd9 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vertical-tabs-chrome-extension", - "version": "1.9.2", + "version": "1.10.0", "description": "A chrome extension that presents your tabs vertically.", "license": "MIT", "repository": { diff --git a/src/pages/Sidebar/Sidebar.jsx b/src/pages/Sidebar/Sidebar.jsx index 00bb710..5f417b3 100644 --- a/src/pages/Sidebar/Sidebar.jsx +++ b/src/pages/Sidebar/Sidebar.jsx @@ -30,14 +30,14 @@ class Sidebar extends Component { this.tabUpdatedHandler = this.handleTabUpdated.bind(this); this.tabMovedHandler = this.handleTabMoved.bind(this); this.tabActivatedHandler = this.handleTabActivated.bind(this); - this.tabHighlightedHandler = this.handleTabHighlighted.bind(this); + // this.tabHighlightedHandler = this.handleTabHighlighted.bind(this); chrome.tabs.onCreated.addListener(this.tabCreatedHandler); chrome.tabs.onRemoved.addListener(this.tabRemovedHandler); chrome.tabs.onUpdated.addListener(this.tabUpdatedHandler); chrome.tabs.onMoved.addListener(this.tabMovedHandler); chrome.tabs.onActivated.addListener(this.tabActivatedHandler); - chrome.tabs.onHighlighted.addListener(this.tabHighlightedHandler); + // chrome.tabs.onHighlighted.addListener(this.tabHighlightedHandler); } componentDidMount() { @@ -85,7 +85,7 @@ class Sidebar extends Component { chrome.tabs.onUpdated.removeListener(this.tabUpdatedHandler); chrome.tabs.onMoved.removeListener(this.tabMovedHandler); chrome.tabs.onActivated.removeListener(this.tabActivatedHandler); - chrome.tabs.onHighlighted.removeListener(this.tabHighlightedHandler); + // chrome.tabs.onHighlighted.removeListener(this.tabHighlightedHandler); window.removeEventListener('scroll', this.handleScroll); } @@ -140,7 +140,7 @@ class Sidebar extends Component { window.clearTimeout(this.isScrolling); // Set a timeout to run after scrolling ends - this.isScrolling = setTimeout(function() { + this.isScrolling = setTimeout(function () { chrome.runtime.sendMessage({ from: 'sidebar', msg: 'SIDEBAR_SCROLL_POSITION_CHANGED', @@ -244,16 +244,20 @@ class Sidebar extends Component { this.updateTabOrders(); }; - handleTabHighlighted = (highlightInfo) => {}; - - moveTab = (dragIndex, hoverIndex) => { - const dragTab = this.state.tabOrders[dragIndex]; - this.setState({ - tabOrders: update(this.state.tabOrders, { - $splice: [[dragIndex, 1], [hoverIndex, 0, dragTab]], - }), - }); - }; + // handleTabHighlighted = (highlightInfo) => { + // }; + + // moveTab = (dragIndex, hoverIndex) => { + // const dragTab = this.state.tabOrders[dragIndex]; + // this.setState({ + // tabOrders: update(this.state.tabOrders, { + // $splice: [ + // [dragIndex, 1], + // [hoverIndex, 0, dragTab], + // ], + // }), + // }); + // }; setDisplayTabInFull = (toStatus) => { this.setState({ displayTabInFull: toStatus }); @@ -275,10 +279,13 @@ class Sidebar extends Component { <TabsList displayTabInFull={displayTabInFull} - tabOrders={tabOrders} + tabOrders={tabOrders + .filter(({ id }) => tabsDict[id] !== undefined) + .map((tabOrder) => ({ + ...tabOrder, + ...tabsDict[tabOrder.id], + }))} activeTab={activeTab} - tabsDict={tabsDict} - moveTab={this.moveTab} setTabAsLoading={this.setTabAsLoading} /> {/* <UpdateNotice /> */} diff --git a/src/pages/Sidebar/containers/TabsList/Tab/Tab.css b/src/pages/Sidebar/containers/TabsList/Tab/Tab.css index 3dbed1a..cefbc83 100644 --- a/src/pages/Sidebar/containers/TabsList/Tab/Tab.css +++ b/src/pages/Sidebar/containers/TabsList/Tab/Tab.css @@ -169,7 +169,8 @@ } .blink { - animation: blinker 1.2s linear infinite; + opacity: 0; + /* animation: blinker 1.2s linear infinite; */ } @keyframes blinker { diff --git a/src/pages/Sidebar/containers/TabsList/Tab/Tab.jsx b/src/pages/Sidebar/containers/TabsList/Tab/Tab.jsx index b53a7ec..add282d 100644 --- a/src/pages/Sidebar/containers/TabsList/Tab/Tab.jsx +++ b/src/pages/Sidebar/containers/TabsList/Tab/Tab.jsx @@ -1,4 +1,4 @@ -import React, { useRef, useContext } from 'react'; +import React, { useRef, useContext, memo } from 'react'; import classNames from 'classnames'; import DarkModeContext from '../../../context/dark-mode-context'; import Loader from 'react-loader-spinner'; @@ -40,6 +40,7 @@ const Tab = ({ displayTabInFull, contextMenuShow, contextMenuShowPrev, + findTab, moveTab, setTabAsLoading, isSearching, @@ -58,47 +59,41 @@ const Tab = ({ const darkModeContext = useContext(DarkModeContext); const { isDark } = darkModeContext; - const [, drop] = useDrop(() => ({ - accept: ItemTypes.TABCARD, - hover(item, monitor) { - if (!ref.current) { - return; - } - - const dragIndex = item.idx; - const hoverIndex = idx; - if (dragIndex === hoverIndex) { - return; - } - const hoverBoundingRect = ref.current.getBoundingClientRect(); - const hoverMiddleY = - (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2; - const clientOffset = monitor.getClientOffset(); - const hoverClientY = clientOffset.y - hoverBoundingRect.top; - if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) { - return; - } - if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) { - return; - } - moveTab(dragIndex, hoverIndex); - item.idx = hoverIndex; - }, - })); + const originalIndex = findTab(id).index; + const [{ isDragging }, drag] = useDrag( + () => ({ + type: ItemTypes.TABCARD, + item: { id, originalIndex }, + collect: (monitor) => ({ + isDragging: monitor.isDragging(), + }), + end: (item, monitor) => { + const { id: droppedId, originalIndex } = item; + const { index: overIndex } = findTab(droppedId); + const didDrop = monitor.didDrop(); + if (!didDrop) { + moveTab(droppedId, originalIndex); + } else { + moveTabToIndex(id, overIndex); + } + }, + }), + [id, originalIndex, moveTab] + ); - const [{ isDragging }, drag] = useDrag(() => ({ - type: ItemTypes.TABCARD, - item: { id, idx }, - end: (item, monitor) => { - moveTabToIndex(id, item.idx); - }, - canDrag(monitor) { - return !isSearching; - }, - collect: (monitor) => ({ - isDragging: monitor.isDragging(), + const [, drop] = useDrop( + () => ({ + accept: ItemTypes.TABCARD, + canDrop: () => false, + hover({ id: draggedId, originalIndex: draggedIndex }) { + if (draggedId !== id) { + const { index: overIndex } = findTab(id); + moveTab(draggedId, overIndex); + } + }, }), - })); + [findTab, moveTab] + ); drag(drop(ref)); /* End of --> Drag and Drop support */ @@ -158,7 +153,7 @@ const Tab = ({ <ContextMenuTrigger id={id.toString()} holdToDisplay={-1}> <li // style={{ opacity: isDragging ? 0 : 1 }} - ref={ref} + title={`${title}\n\n${url}`} className={classNames({ TabItem: true, @@ -192,6 +187,7 @@ const Tab = ({ }} > <div + ref={ref} className={classNames({ TabContainer: true, isPinned: pinned, @@ -381,4 +377,4 @@ const Tab = ({ ); }; -export default Tab; +export default memo(Tab); diff --git a/src/pages/Sidebar/containers/TabsList/TabsList.jsx b/src/pages/Sidebar/containers/TabsList/TabsList.jsx index 526337b..aeb7f8f 100644 --- a/src/pages/Sidebar/containers/TabsList/TabsList.jsx +++ b/src/pages/Sidebar/containers/TabsList/TabsList.jsx @@ -1,6 +1,11 @@ -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useState, useEffect, useCallback, memo } from 'react'; import classNames from 'classnames'; +import { useDrop } from 'react-dnd'; +import ItemTypes from './ItemTypes'; +import update from 'immutability-helper'; +import { isEqual } from 'lodash'; import DarkModeContext from '../../context/dark-mode-context'; +import { usePrevious } from '../../../../shared/utils'; import { MdAdd } from 'react-icons/md'; @@ -12,19 +17,39 @@ import './TabsList.css'; const TabsList = ({ tabOrders, - tabsDict, activeTab, displayTabInFull, - moveTab, setTabAsLoading, }) => { - let firstTab = null; + const [, drop] = useDrop(() => ({ accept: ItemTypes.TABCARD })); const [searchBarInputText, _setSearchBarInputText] = useState(''); const [contextMenuShow, _setContextMenuShow] = useState(false); const [contextMenuShowPrev, _setContextMenuShowPrev] = useState(null); const [platformInfo, _setPlatformInfo] = useState(null); + const [tabOrdersCopy, _setTabOrdersCopy] = useState([]); + + const prevTabOrders = usePrevious(tabOrders); + const prevSearchBarInputText = usePrevious(searchBarInputText); + useEffect(() => { + if ( + !isEqual(prevTabOrders, tabOrders) || + !isEqual(prevSearchBarInputText, searchBarInputText) + ) { + let ordersCopy = []; + tabOrders.forEach((tabOrder) => { + const { combinedText } = tabOrder; + if (combinedText.includes(searchBarInputText.toLowerCase())) { + ordersCopy.push({ + ...tabOrder, + }); + } + }); + _setTabOrdersCopy(ordersCopy); + } + }, [tabOrders, searchBarInputText]); + const setContextMenuShow = (toStatus) => { if (toStatus === false) { _setContextMenuShow(false); @@ -42,14 +67,14 @@ const TabsList = ({ if (event.key === 'Enter') { // enter key if (searchBarInputText.length > 0) { - if (firstTab) { - chrome.tabs.update(firstTab.id, { active: true }); + if (tabOrdersCopy && tabOrdersCopy.length > 0) { + chrome.tabs.update(tabOrdersCopy[0].id, { active: true }); clearSearchBoxInputText(); } } } }, - [searchBarInputText, firstTab] + [searchBarInputText, tabOrdersCopy] ); useEffect(() => { @@ -77,6 +102,32 @@ const TabsList = ({ chrome.tabs.create({}); }; + const findTab = useCallback( + (id) => { + const tab = tabOrdersCopy.filter((tb) => tb.id === id)[0]; + return { + tab, + index: tabOrdersCopy.indexOf(tab), + }; + }, + [tabOrdersCopy] + ); + + const moveTabCard = useCallback( + (id, atIndex) => { + const { tab, index } = findTab(id); + _setTabOrdersCopy( + update(tabOrdersCopy, { + $splice: [ + [index, 1], + [atIndex, 0, tab], + ], + }) + ); + }, + [findTab, tabOrdersCopy, _setTabOrdersCopy] + ); + const renderTab = (tabOrder, idx) => { return ( <Tab @@ -97,7 +148,8 @@ const TabsList = ({ displayTabInFull={displayTabInFull} contextMenuShow={contextMenuShow} contextMenuShowPrev={contextMenuShowPrev} - moveTab={moveTab} + findTab={findTab} + moveTab={moveTabCard} setTabAsLoading={setTabAsLoading} clearSearchBoxInputText={clearSearchBoxInputText} isSearching={searchBarInputText.length > 0} @@ -108,28 +160,6 @@ const TabsList = ({ ); }; - const inputText = searchBarInputText.toLowerCase(); - - const tabOrdersCopy = []; - tabOrders.forEach((tabOrder) => { - const { id } = tabOrder; - if (tabsDict[id] !== undefined) { - const { combinedText } = tabsDict[id]; - if (combinedText.includes(inputText)) { - tabOrdersCopy.push({ - ...tabOrder, - ...tabsDict[id], - }); - } - } - }); - - if (tabOrdersCopy.length > 0) { - firstTab = tabOrdersCopy[0]; - } else { - firstTab = null; - } - const pinnedTabs = tabOrdersCopy.filter((item) => item.pinned); const unpinnedTabs = tabOrdersCopy.filter((item) => !item.pinned); @@ -144,15 +174,10 @@ const TabsList = ({ searchCount={tabOrdersCopy.length} /> - <div style={{ margin: 0, padding: 0 }}> + <div style={{ margin: 0, padding: 0 }} ref={drop}> {pinnedTabs.length > 0 && ( <div className="PinnedTabsContainer"> {pinnedTabs.map((tabOrder, idx) => { - if (tabsDict[tabOrder.id] === undefined) { - return null; - } - - // let tab = { ...tabsDict[tabOrder.id] }; return ( <React.Fragment key={tabOrder.id}> {renderTab(tabOrder, idx)} @@ -167,11 +192,6 @@ const TabsList = ({ )} {unpinnedTabs.map((tabOrder, idx) => { - if (tabsDict[tabOrder.id] === undefined) { - return null; - } - - // let tab = { ...tabsDict[tabOrder.id] }; return ( <React.Fragment key={tabOrder.id}> {renderTab(tabOrder, idx + pinnedTabs.length)} @@ -213,4 +233,4 @@ const TabsList = ({ ); }; -export default TabsList; +export default memo(TabsList); diff --git a/src/shared/utils.ts b/src/shared/utils.ts index 9408d49..cffc6be 100644 --- a/src/shared/utils.ts +++ b/src/shared/utils.ts @@ -1,3 +1,13 @@ +import { useEffect, useRef } from 'react'; + export const getFavicon = (url: string) => { return `chrome://favicon/size/16@2x/${url}`; }; + +export const usePrevious = <T extends unknown>(value: T): T | undefined => { + const ref = useRef<T>(); + useEffect(() => { + ref.current = value; + }); + return ref.current; +};