mirror of
https://github.com/zhigang1992/devhub.git
synced 2026-06-17 19:25:38 +08:00
Change columns state structure to byId and allIds
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -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) || '',
|
||||
}
|
||||
|
||||
76
packages/shared/src/containers/ColumnContainer.tsx
Normal file
76
packages/shared/src/containers/ColumnContainer.tsx
Normal 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)
|
||||
@@ -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} />
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user