Skip to content

Commit 8619d6f

Browse files
rynobaxhuv1k
authored andcommitted
Make tabs sortable (#848)
* Make tabs sortable * Change tab selection from onClick to onMouseDown * Allow scrolling of tabbar while sorting tabs
1 parent 3f457e5 commit 8619d6f

File tree

6 files changed

+113
-37
lines changed

6 files changed

+113
-37
lines changed

packages/graphql-playground-react/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@
156156
"react-modal": "^3.1.11",
157157
"react-redux": "^5.0.6",
158158
"react-router-dom": "^4.2.2",
159+
"react-sortable-hoc": "^0.8.3",
159160
"react-transition-group": "^2.2.1",
160161
"react-virtualized": "^9.12.0",
161162
"redux": "^3.7.2",

packages/graphql-playground-react/src/components/Playground/Tab.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class Tab extends React.PureComponent<Props & ReduxProps, State> {
4545
'New Tab'
4646

4747
return (
48-
<TabItem active={active} onClick={this.handleSelectSession}>
48+
<TabItem active={active} onMouseDown={this.handleSelectSession}>
4949
<Icons active={active}>
5050
{session.subscriptionActive && <RedDot />}
5151
<QueryTypes>
@@ -154,17 +154,15 @@ const TabItem = withProps<TabItemProps>()(styled.div)`
154154
height: 43px;
155155
padding: 10px;
156156
padding-top: 9px;
157-
margin-left: 10px;
157+
margin-right: 10px;
158158
font-size: 14px;
159159
border-radius: 2px;
160160
border-bottom: 2px solid ${p => p.theme.editorColours.navigationBar};
161161
box-sizing: border-box;
162162
cursor: pointer;
163+
user-select: none;
163164
background: ${p =>
164165
p.active ? p.theme.editorColours.tab : p.theme.editorColours.tabInactive};
165-
&:first-child {
166-
margin-left: 0;
167-
}
168166
&:hover {
169167
background: ${p => p.theme.editorColours.tab};
170168
.close {
@@ -202,6 +200,7 @@ const Icons = withProps<TabItemProps>()(styled.div)`
202200

203201
const QueryTypes = styled.div`
204202
display: flex;
203+
color: white;
205204
`
206205

207206
const QueryType = styled.div`

packages/graphql-playground-react/src/components/Playground/TabBar.tsx

Lines changed: 81 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
11
import * as React from 'react'
22
import { styled, withProps } from '../../styled'
33
import { Icon } from 'graphcool-styles'
4-
import Tab from './Tab'
4+
import Tab, { Props as TabProps } from './Tab'
55
import { connect } from 'react-redux'
66
import { createStructuredSelector } from 'reselect'
77
import {
88
getSessionsArray,
99
getSelectedSessionIdFromRoot,
1010
} from '../../state/sessions/selectors'
1111
import { Session } from '../../state/sessions/reducers'
12+
import { reorderTabs } from '../../state/sessions/actions'
13+
import {
14+
SortableContainer,
15+
SortableElement,
16+
SortStart,
17+
SortEnd,
18+
} from 'react-sortable-hoc'
1219

1320
export interface Props {
1421
onNewSession: any
@@ -18,42 +25,79 @@ export interface Props {
1825
export interface ReduxProps {
1926
sessions: Session[]
2027
selectedSessionId: string
28+
reorderTabs: (src: number, dest: number) => void
29+
}
30+
31+
interface State {
32+
sorting: boolean
2133
}
2234

23-
const TabBar = ({
24-
sessions,
25-
isApp,
26-
selectedSessionId,
27-
onNewSession,
28-
}: Props & ReduxProps) => (
29-
<StyledTabBar>
30-
<Tabs isApp={isApp}>
31-
{sessions.map(session => (
32-
<Tab
33-
key={session.id}
34-
session={session}
35-
selectedSessionId={selectedSessionId}
36-
/>
37-
))}
38-
<Plus onClick={onNewSession}>
39-
<Icon
40-
src={require('graphcool-styles/icons/stroke/add.svg')}
41-
width={34}
42-
height={34}
43-
stroke={true}
44-
strokeWidth={4}
45-
/>
46-
</Plus>
47-
</Tabs>
48-
</StyledTabBar>
49-
)
35+
const SortableTab = SortableElement<TabProps>(Tab)
36+
37+
class TabBar extends React.PureComponent<Props & ReduxProps, State> {
38+
state = { sorting: false }
39+
40+
render() {
41+
const { sessions, isApp, selectedSessionId, onNewSession } = this.props
42+
const { sorting } = this.state
43+
return (
44+
<SortableTabBar
45+
onSortStart={this.onSortStart}
46+
onSortEnd={this.onSortEnd}
47+
getHelperDimensions={this.getHelperDimensions}
48+
axis="x"
49+
lockAxis="x"
50+
lockToContainerEdges={true}
51+
distance={10}
52+
transitionDuration={200}
53+
>
54+
<Tabs isApp={isApp}>
55+
{sessions.map((session, ndx) => (
56+
<SortableTab
57+
key={session.id}
58+
session={session}
59+
selectedSessionId={selectedSessionId}
60+
index={ndx}
61+
/>
62+
))}
63+
<Plus onClick={onNewSession} sorting={sorting}>
64+
<Icon
65+
src={require('graphcool-styles/icons/stroke/add.svg')}
66+
width={34}
67+
height={34}
68+
stroke={true}
69+
strokeWidth={4}
70+
/>
71+
</Plus>
72+
</Tabs>
73+
</SortableTabBar>
74+
)
75+
}
76+
77+
private onSortStart = ({ index }: SortStart) => {
78+
this.setState({ sorting: true })
79+
}
80+
81+
private onSortEnd = ({ oldIndex, newIndex }: SortEnd) => {
82+
this.props.reorderTabs(oldIndex, newIndex)
83+
this.setState({ sorting: false })
84+
}
85+
86+
private getHelperDimensions = ({ node }: SortStart) => {
87+
const { width, height } = node.getBoundingClientRect()
88+
return { width, height }
89+
}
90+
}
5091

5192
const mapStateToProps = createStructuredSelector({
5293
sessions: getSessionsArray,
5394
selectedSessionId: getSelectedSessionIdFromRoot,
5495
})
5596

56-
export default connect(mapStateToProps)(TabBar)
97+
export default connect(
98+
mapStateToProps,
99+
{ reorderTabs },
100+
)(TabBar)
57101

58102
const StyledTabBar = styled.div`
59103
color: white;
@@ -66,6 +110,8 @@ const StyledTabBar = styled.div`
66110
}
67111
`
68112

113+
const SortableTabBar = SortableContainer(StyledTabBar)
114+
69115
interface TabsProps {
70116
isApp?: boolean
71117
}
@@ -77,12 +123,16 @@ const Tabs = withProps<TabsProps>()(styled.div)`
77123
padding-left: ${p => (p.isApp ? '43px' : '0')};
78124
`
79125

80-
const Plus = styled.div`
126+
interface PlusProps {
127+
sorting: boolean
128+
}
129+
130+
const Plus = withProps<PlusProps>()(styled.div)`
81131
box-sizing: border-box;
82132
display: flex;
133+
visibility: ${p => (p.sorting ? 'hidden' : 'visible')}
83134
height: 43px;
84135
width: 43px;
85-
margin-left: 10px;
86136
border-radius: 2px;
87137
border-bottom: 2px solid ${p => p.theme.editorColours.navigationBar};
88138
background: ${p => p.theme.editorColours.tabInactive};

packages/graphql-playground-react/src/state/sessions/actions.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export const {
6363
setCurrentQueryEndTime,
6464
refetchSchema,
6565
setScrollTop,
66+
reorderTabs,
6667
} = createActions({
6768
// simple property setting
6869
EDIT_QUERY: query => ({ query }),
@@ -179,6 +180,7 @@ export const {
179180
SELECT_TAB: simpleAction('sessionId'),
180181
SELECT_TAB_INDEX: simpleAction('index'),
181182
CLOSE_TAB: simpleAction('sessionId'),
183+
REORDER_TABS: (src, dest) => ({ src, dest }),
182184

183185
// files, settings, config
184186
EDIT_SETTINGS: simpleAction(),

packages/graphql-playground-react/src/state/sessions/reducers.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { getSelectedSessionId } from './selectors'
2626
import { getDefaultSession, defaultQuery } from '../../constants'
2727
import * as cuid from 'cuid'
2828
import { formatError } from './fetchingSagas'
29+
import { arrayMove } from 'react-sortable-hoc'
2930

3031
export interface SessionStateProps {
3132
sessions: OrderedMap<string, Session>
@@ -526,6 +527,21 @@ const reducer = handleActions(
526527
state.sessions.size - 1,
527528
)
528529
},
530+
REORDER_TABS: (state, { payload: { src, dest } }) => {
531+
const seq = state.sessions.toIndexedSeq()
532+
533+
const indexes: number[] = []
534+
for (let i = 0; i < seq.size; i++) indexes.push(i)
535+
const newIndexes = arrayMove(indexes, src, dest)
536+
537+
let newSessions = OrderedMap()
538+
for (let i = 0; i < seq.size; i++) {
539+
const ndx = newIndexes[i]
540+
const val = seq.get(ndx)
541+
newSessions = newSessions.set(val.id, val)
542+
}
543+
return state.set('sessions', newSessions)
544+
},
529545
EDIT_SETTINGS: state => {
530546
return state.setIn(
531547
['sessions', getSelectedSessionId(state), 'changed'],

packages/graphql-playground-react/yarn.lock

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1089,7 +1089,7 @@ babel-register@^6.11.6, babel-register@^6.26.0:
10891089
mkdirp "^0.5.1"
10901090
source-map-support "^0.4.15"
10911091

1092-
babel-runtime@^6.18.0, babel-runtime@^6.20.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0:
1092+
babel-runtime@^6.11.6, babel-runtime@^6.18.0, babel-runtime@^6.20.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0:
10931093
version "6.26.0"
10941094
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
10951095
dependencies:
@@ -7270,6 +7270,14 @@ react-side-effect@^1.1.0:
72707270
exenv "^1.2.1"
72717271
shallowequal "^1.0.1"
72727272

7273+
react-sortable-hoc@^0.8.3:
7274+
version "0.8.3"
7275+
resolved "https://registry.yarnpkg.com/react-sortable-hoc/-/react-sortable-hoc-0.8.3.tgz#8537e8ab8d6bad6829885755a0f847817ed78648"
7276+
dependencies:
7277+
babel-runtime "^6.11.6"
7278+
invariant "^2.2.1"
7279+
prop-types "^15.5.7"
7280+
72737281
react-test-renderer@^16.0.0-0:
72747282
version "16.4.1"
72757283
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.4.1.tgz#f2fb30c2c7b517db6e5b10ed20bb6b0a7ccd8d70"

0 commit comments

Comments
 (0)