Change columns state structure to byId and allIds

This commit is contained in:
Bruno Lemos
2018-11-10 03:15:24 -02:00
parent 1c19532f70
commit bd2df9fed0
10 changed files with 189 additions and 94 deletions

View File

@@ -8,16 +8,16 @@ import {
ViewStyle,
} from 'react-native'
import { ColumnContainer } from '../../containers/ColumnContainer'
import { emitter } from '../../setup'
import { Column, Omit } from '../../types'
import { Omit } from '../../types'
import { Separator } from '../common/Separator'
import { DimensionsConsumer } from '../context/DimensionsContext'
import { EventColumn } from './EventColumn'
import { NotificationColumn } from './NotificationColumn'
export interface ColumnsProps
extends Omit<FlatListProps<Column>, 'renderItem'> {
extends Omit<FlatListProps<string>, 'data' | 'renderItem'> {
contentContainerStyle?: StyleProp<ViewStyle>
columnIds: string[]
style?: StyleProp<ViewStyle>
}
@@ -32,7 +32,7 @@ const styles = StyleSheet.create({
})
export class Columns extends PureComponent<ColumnsProps> {
flatListRef = React.createRef<FlatList<Column>>()
flatListRef = React.createRef<FlatList<string>>()
focusOnColumnListener?: EventSubscription
pagingEnabled: boolean = true
swipeable: boolean = false
@@ -58,9 +58,9 @@ export class Columns extends PureComponent<ColumnsProps> {
highlight?: boolean
}) => {
if (!this.flatListRef.current) return
if (!(this.props.data && this.props.data!.length)) return
if (!(this.props.columnIds && this.props.columnIds.length)) return
if (columnIndex >= 0 && columnIndex < this.props.data.length) {
if (columnIndex >= 0 && columnIndex < this.props.columnIds.length) {
this.flatListRef.current.scrollToIndex({
animated,
index: columnIndex,
@@ -68,48 +68,22 @@ export class Columns extends PureComponent<ColumnsProps> {
}
}
keyExtractor(column: Column) {
return `column-container-${column.id}`
keyExtractor(columnId: string) {
return `column-container-${columnId}`
}
renderItem: FlatListProps<Column>['renderItem'] = ({
item: column,
index,
}) => {
switch (column.type) {
case 'notifications': {
return (
<NotificationColumn
key={`notification-column-${column.id}`}
column={column}
columnIndex={index}
pagingEnabled={this.pagingEnabled}
swipeable={this.swipeable}
/>
)
}
case 'activity': {
return (
<EventColumn
key={`event-column-${column.id}`}
column={column}
columnIndex={index}
pagingEnabled={this.pagingEnabled}
swipeable={this.swipeable}
/>
)
}
default: {
console.error('Invalid Column type: ', (column as any).type)
return null
}
}
renderItem: FlatListProps<string>['renderItem'] = ({ item: columnId }) => {
return (
<ColumnContainer
columnId={columnId}
pagingEnabled={this.pagingEnabled}
swipeable={this.swipeable}
/>
)
}
render() {
const { data, style, ...props } = this.props
const { columnIds, style, ...props } = this.props
return (
<DimensionsConsumer>
@@ -126,7 +100,7 @@ export class Columns extends PureComponent<ColumnsProps> {
ListHeaderComponent={small ? Separator : undefined}
bounces={!this.swipeable}
className="snap-container"
data={data}
data={columnIds}
horizontal
keyExtractor={this.keyExtractor}
onScrollToIndexFailed={() => undefined}

View File

@@ -43,7 +43,7 @@ const connectToStore = connect(
const user = selectors.currentUserSelector(state)
return {
columns: selectors.columnsSelector(state),
columns: selectors.columnsArrSelector(state),
currentOpenedModal: selectors.currentOpenedModal(state),
username: (user && user.login) || '',
}

View File

@@ -0,0 +1,76 @@
import _ from 'lodash'
import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
import { EventColumn } from '../components/columns/EventColumn'
import { NotificationColumn } from '../components/columns/NotificationColumn'
import * as selectors from '../redux/selectors'
import { ExtractPropsFromConnector } from '../types'
export interface ColumnContainerProps {
columnId: string
pagingEnabled?: boolean
swipeable?: boolean
}
export interface ColumnContainerState {}
const connectToStore = connect(() => {
const columnSelector = selectors.createColumnSelector()
return (state: any, { columnId }: ColumnContainerProps) => ({
column: columnSelector(state, columnId),
columnIndex: selectors.columnIdsSelector(state).indexOf(columnId),
})
})
class ColumnContainerComponent extends PureComponent<
ColumnContainerProps & ExtractPropsFromConnector<typeof connectToStore>,
ColumnContainerState
> {
render() {
const {
columnIndex,
column,
pagingEnabled,
swipeable,
...props
} = this.props
delete props.columnId
if (!column) return null
switch (column.type) {
case 'notifications': {
return (
<NotificationColumn
key={`notification-column-${column.id}`}
column={column}
columnIndex={columnIndex}
pagingEnabled={pagingEnabled}
swipeable={swipeable}
/>
)
}
case 'activity': {
return (
<EventColumn
key={`event-column-${column.id}`}
column={column}
columnIndex={columnIndex}
pagingEnabled={pagingEnabled}
swipeable={swipeable}
/>
)
}
default: {
console.error('Invalid Column type: ', (column as any).type)
return null
}
}
}
}
export const ColumnContainer = connectToStore(ColumnContainerComponent)

View File

@@ -11,7 +11,7 @@ export interface ColumnsContainerProps {}
export interface ColumnsContainerState {}
const connectToStore = connect((state: any) => ({
columns: selectors.columnsSelector(state),
columnIds: selectors.columnIdsSelector(state),
}))
class ColumnsContainerComponent extends PureComponent<
@@ -19,8 +19,8 @@ class ColumnsContainerComponent extends PureComponent<
ColumnsContainerState
> {
render() {
const columns = this.props.columns || []
return <Columns key="columns-container" data={columns} />
const columnIds = this.props.columnIds || []
return <Columns key="columns-container" columnIds={columnIds} />
}
}

View File

@@ -1,63 +1,67 @@
import immer from 'immer'
import { REHYDRATE } from 'redux-persist'
import { Column, Reducer } from '../../types'
import { columnsSelector } from '../selectors'
export interface State {
columns?: Column[]
allIds: string[]
byId: Record<string, Column> | null
}
const initialState: State = {}
const initialState: State = {
allIds: [],
byId: null,
}
export const columnsReducer: Reducer<State> = (
state = initialState,
action,
) => {
switch (action.type) {
case REHYDRATE as any:
return immer(state, draft => {
const columns =
columnsSelector((action.payload as any) || {}) || draft.columns
if (columns) draft.columns = columns.filter(c => c && c.id)
})
case 'ADD_COLUMN':
return immer(state, draft => {
draft.columns = draft.columns || []
draft.columns = [action.payload, ...draft.columns]
draft.allIds = draft.allIds || []
draft.byId = draft.byId || {}
draft.allIds.unshift(action.payload.id)
draft.byId[action.payload.id] = action.payload
})
case 'DELETE_COLUMN':
return immer(state, draft => {
if (!draft.columns) return
draft.columns = draft.columns.filter(c => c.id !== action.payload)
if (draft.allIds)
draft.allIds = draft.allIds.filter(id => id !== action.payload)
if (draft.byId) delete draft.byId[action.payload]
})
case 'MOVE_COLUMN':
return immer(state, draft => {
if (!draft.columns) return
if (!draft.allIds) return
const currentIndex = draft.columns.findIndex(
c => c.id === action.payload.id,
const currentIndex = draft.allIds.findIndex(
id => id === action.payload.id,
)
if (!(currentIndex >= 0 && currentIndex < draft.columns.length)) return
if (!(currentIndex >= 0 && currentIndex < draft.allIds.length)) return
const newIndex = Math.max(
0,
Math.min(action.payload.index, draft.columns.length - 1),
Math.min(action.payload.index, draft.allIds.length - 1),
)
if (Number.isNaN(newIndex)) return
// move column inside array
const column = draft.columns[currentIndex]
draft.columns = draft.columns.filter(c => c !== column)
draft.columns.splice(newIndex, 0, column)
const columnId = draft.allIds[currentIndex]
draft.allIds = draft.allIds.filter(id => id !== columnId)
draft.allIds.splice(newIndex, 0, columnId)
})
case 'REPLACE_COLUMNS':
return immer(state, draft => {
draft.columns = action.payload
draft.byId = {}
draft.allIds = action.payload.map(c => {
draft.byId![c.id] = c
return c.id
})
})
default:

View File

@@ -39,8 +39,9 @@ function* onLoginSuccess(
action: ExtractActionFromActionCreator<typeof actions.loginSuccess>,
) {
const username = action.payload.login
const columns = yield select(selectors.columnsSelector)
if (!columns) yield put(actions.replaceColumns(getDefaultColumns(username)))
const hasCreatedColumn = yield select(selectors.hasCreatedColumnSelector)
if (!hasCreatedColumn)
yield put(actions.replaceColumns(getDefaultColumns(username)))
}
function* onAddColumn(
@@ -48,8 +49,8 @@ function* onAddColumn(
) {
const columnId = action.payload.id
const columns: Column[] | undefined = yield select(selectors.columnsSelector)
const columnIndex = columns && columns.findIndex(c => c.id === columnId)
const ids: string[] = yield select(selectors.columnIdsSelector)
const columnIndex = ids.findIndex(id => id === columnId)
yield delay(300)
emitter.emit('FOCUS_ON_COLUMN', {
@@ -63,12 +64,12 @@ function* onAddColumn(
function* onMoveColumn(
action: ExtractActionFromActionCreator<typeof actions.moveColumn>,
) {
const columns: Column[] | undefined = yield select(selectors.columnsSelector)
if (!columns) return
const ids: string[] = yield select(selectors.columnIdsSelector)
if (!(ids && ids.length)) return
const columnIndex = Math.max(
0,
Math.min(action.payload.index, columns.length - 1),
Math.min(action.payload.index, ids.length - 1),
)
if (Number.isNaN(columnIndex)) return

View File

@@ -1,5 +1,22 @@
import { createSelector } from 'reselect'
import { RootState } from '../../types'
const s = (state: RootState) => state.columns || {}
export const columnsSelector = (state: RootState) => s(state).columns
export const createColumnSelector = () =>
createSelector(
(state: RootState) => s(state).byId,
(_state: RootState, id: string) => id,
(byId, id) => byId && byId[id],
)
export const columnIdsSelector = (state: RootState) => s(state).allIds
export const columnsArrSelector = createSelector(
(state: RootState) => columnIdsSelector(state),
(state: RootState) => s(state).byId,
(allIds, byId) => (byId ? allIds.map(id => byId[id]) : []),
)
export const hasCreatedColumnSelector = (state: RootState) =>
s(state).byId !== null

View File

@@ -1,18 +1,42 @@
import immer from 'immer'
import { applyMiddleware, createStore } from 'redux'
import { composeWithDevTools } from 'redux-devtools-extension'
import { PersistConfig, persistReducer, persistStore } from 'redux-persist'
import {
createMigrate,
PersistConfig,
persistReducer,
persistStore,
} from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import createSagaMiddleware from 'redux-saga'
import { Column, RootState } from '../types'
import { rootReducer } from './reducers'
import { rootSaga } from './sagas'
const migrations = {
0: (state: any) => state,
1: (state: any) => state,
2: (state: any) =>
immer(state, draft => {
const columns: Column[] = draft.columns && draft.columns.columns
if (!columns) return
draft.columns.byId = {}
draft.columns.allIds = columns.map(column => {
draft.columns.byId![column.id] = column
return column.id
})
}),
}
export function configureStore(key = 'root') {
const persistConfig: PersistConfig = {
blacklist: ['navigation'],
key,
migrate: createMigrate(migrations, { debug: __DEV__ }),
storage,
version: 1,
version: 2,
}
const persistedReducer = persistReducer(persistConfig, rootReducer)

View File

@@ -35,7 +35,7 @@ const styles = StyleSheet.create({
const connectToStore = connect(
(state: any) => ({
currentOpenedModal: selectors.currentOpenedModal(state),
columns: (selectors.columnsSelector(state) || []) as Column[],
columnIds: selectors.columnIdsSelector(state),
}),
{
closeAllModals: actions.closeAllModals,
@@ -103,27 +103,26 @@ class MainScreenComponent extends PureComponent<
return
}
if (this.props.columns.length > 0) {
if (this.props.columnIds.length > 0) {
if (e.keyCode - 48 === 0) {
const columnIndex = this.props.columns.length - 1
const columnIndex = this.props.columnIds.length - 1
emitter.emit('FOCUS_ON_COLUMN', {
animated: true,
columnId:
this.props.columns[columnIndex] &&
this.props.columns[columnIndex].id,
columnId: this.props.columnIds[columnIndex],
columnIndex,
highlight: true,
})
return
}
if (e.keyCode - 48 >= 1 && e.keyCode - 48 <= this.props.columns.length) {
if (
e.keyCode - 48 >= 1 &&
e.keyCode - 48 <= this.props.columnIds.length
) {
const columnIndex = e.keyCode - 48 - 1
emitter.emit('FOCUS_ON_COLUMN', {
animated: true,
columnId:
this.props.columns[columnIndex] &&
this.props.columns[columnIndex].id,
columnId: this.props.columnIds[columnIndex],
columnIndex,
highlight: true,
})

View File

@@ -33,7 +33,7 @@ export type ExtractActionFromActionCreator<AC> = AC extends () => infer A
export type ExtractPropsFromConnector<
Connector
> = Connector extends InferableComponentEnhancerWithProps<infer T, {}>
> = Connector extends InferableComponentEnhancerWithProps<infer T, any>
? T
: never