Skip to content

Commit

Permalink
refactor(content): refactor redux to be stronger typed
Browse files Browse the repository at this point in the history
  • Loading branch information
crimx committed May 13, 2018
1 parent 47ef11b commit 1d4c1e7
Show file tree
Hide file tree
Showing 12 changed files with 958 additions and 586 deletions.
18 changes: 9 additions & 9 deletions src/content/components/DictItem/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { SelectionInfo, getDefaultSelectionInfo } from '@/_helpers/selection'

export interface DictItemDispatchers {
readonly searchText: (arg: { id: DictID } | { info: SelectionInfo }) => any
readonly updateItemHeight: ({ id, height }: { id: DictID, height: number }) => any
readonly updateItemHeight: (id: DictID, height: number) => any
}

export interface DictItemProps extends DictItemDispatchers {
Expand Down Expand Up @@ -161,17 +161,17 @@ export class DictItem extends React.PureComponent<DictItemProps & { t: Translati
if (update) { this.setState(update as any) }
}

this.props.updateItemHeight({
id: this.props.id,
height: this.state.visibleHeight + 20,
})
this.props.updateItemHeight(
this.props.id,
this.state.visibleHeight + 20,
)
}

componentDidMount () {
this.props.updateItemHeight({
id: this.props.id,
height: this.state.visibleHeight + 20,
})
this.props.updateItemHeight(
this.props.id,
this.state.visibleHeight + 20,
)
}

render () {
Expand Down
29 changes: 19 additions & 10 deletions src/content/components/DictPanel/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react'
import { DictionariesState } from '../../redux/modules/dictionaries'
import { AppConfig, DictID } from '@/app-config'
import { AppConfig, DictID, DictConfigs } from '@/app-config'
import { SelectionInfo } from '@/_helpers/selection'
import { MsgSelection } from '@/typings/message'
import PortalFrame from '@/components/PortalFrame'
Expand All @@ -15,8 +15,12 @@ export type DictPanelDispatchers = DictItemDispatchers & MenuBarDispatchers & {
export interface DictPanelProps extends DictPanelDispatchers {
readonly isFav: boolean
readonly isPinned: boolean
readonly dictionaries: DictionariesState
readonly config: AppConfig
readonly dictionaries: DictionariesState['dictionaries']
readonly allDictsConfig: DictConfigs
readonly selectedDicts: DictID[]
readonly fontSize: number
readonly panelWidth: number
readonly isAnimation: boolean
readonly selection: MsgSelection

readonly frameDidMount: (frame: HTMLIFrameElement) => any
Expand Down Expand Up @@ -63,11 +67,16 @@ export default class DictPanel extends React.Component<DictPanelProps> {
selection,

dictionaries,
config,

allDictsConfig,
selectedDicts,
panelWidth,
fontSize,
isAnimation,

updateItemHeight,
} = this.props

const allDictsConfig = config.dicts.all
const dictsInfo = dictionaries.dicts

// wrap iframe into DictPanel so that react
Expand All @@ -93,16 +102,16 @@ export default class DictPanel extends React.Component<DictPanelProps> {
panelPinSwitch,
closePanel,
})}
<div className={`panel-DictContainer${config.animation ? ' isAnimate' : ''}`}>
{config.dicts.selected.map(id => React.createElement(DictItem, {
<div className={`panel-DictContainer${isAnimation ? ' isAnimate' : ''}`}>
{selectedDicts.map(id => React.createElement(DictItem, {
key: id,
id,
text: (dictionaries.searchHistory[0] || selection.selectionInfo).text,
dictURL: allDictsConfig[id].page,
fontSize: config.fontSize,
fontSize,
preferredHeight: allDictsConfig[id].preferredHeight,
panelWidth: config.panelWidth,
isAnimation: config.animation,
panelWidth,
isAnimation,
searchStatus: (dictsInfo[id] as any).searchStatus,
searchResult: (dictsInfo[id] as any).searchResult,
searchText,
Expand Down
191 changes: 52 additions & 139 deletions src/content/components/DictPanelPortal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,41 @@ import { Spring } from 'react-spring'
import DictPanel, { DictPanelDispatchers, DictPanelProps } from '../DictPanel'
import { MsgSelection } from '@/typings/message'
import { Omit } from '@/typings/helpers'
import { DictID } from '@/app-config'
import { DictID, DictConfigs } from '@/app-config'

const isSaladictPopupPage = Boolean(window['__SALADICT_POPUP_PAGE__'])

export type DictPanelPortalDispatchers = Omit<
DictPanelDispatchers,
'updateItemHeight' | 'handleDragStart'
>
'handleDragStart'
> & {
panelOnDrag: (x: number, y: number) => any
}

export interface DictPanelPortalProps extends DictPanelPortalDispatchers {
readonly isAnimation: boolean
readonly allDictsConfig: DictConfigs
readonly selectedDicts: DictID[]
readonly fontSize: number

readonly isFav: boolean
readonly isPinned: boolean
readonly isPanelAppear: boolean
readonly shouldPanelShow: boolean
readonly panelRect: {
x: number
y: number
width: number
height: number
}

readonly dictionaries: DictPanelProps['dictionaries']
readonly config: DictPanelProps['config']

readonly selection: MsgSelection
}

type DictPanelState= {
/** hack to reduce the overhead ceremony introduced by gDSFP */
readonly mutableArea: {
propsSelection: MsgSelection | null
dictHeights: { [k in DictID]?: number }
}

readonly isDragging: boolean
readonly x: number
readonly y: number
readonly height: number
}

export default class DictPanelPortal extends React.Component<DictPanelPortalProps, DictPanelState> {
Expand All @@ -50,76 +55,7 @@ export default class DictPanelPortal extends React.Component<DictPanelPortalProp
_frameAnimationEndTimeout: any

state = {
mutableArea: {
propsSelection: null,
dictHeights: {},
},

isDragging: false,
isNewSelection: false,
x: 0,
y: 0,
height: 30
}

static getDerivedStateFromProps (
nextProps: DictPanelPortalProps,
prevState: DictPanelState
): Partial<DictPanelState> | null {
if (isSaladictPopupPage) {
return { height: 400, x: 0, y: 0 }
}

const newSelection = nextProps.selection
const mutableArea = prevState.mutableArea
const oldSelection = mutableArea.propsSelection
if (newSelection !== oldSelection) {
mutableArea.propsSelection = newSelection
const newText = newSelection.selectionInfo.text
// only re-calculate position when new selection is made
if (newSelection.force || (newText && !nextProps.isPinned)) {
// restore height
// when word editor shows up, only relocate the panel
const isWordEditorMsg = newSelection.force && oldSelection && newText === oldSelection.selectionInfo.text
const panelWidth = nextProps.config.panelWidth
const panelHeight = isWordEditorMsg ? prevState.height : 30 + nextProps.config.dicts.selected.length * 30
if (!isWordEditorMsg) { mutableArea.dictHeights = {} }

// icon position 10px panel position
// +-------+ +------------------------+
// | | | |
// | | 30px | |
// 60px +-------+ | |
// | 30px | |
// | | |
// 40px | | |
// +-------+ | |
// cursor
const { mouseX, mouseY, force } = newSelection
const wWidth = window.innerWidth
const wHeight = window.innerHeight

let x = force
? mouseX
: mouseX + panelWidth + 80 <= wWidth
? mouseX + 80
: mouseX - panelWidth - 80
if (x < 0) { x = 10 } // too left

let y = force
? mouseY
: mouseY > 60 ? mouseY - 60 : mouseY + 60 - 30
if (y + panelHeight >= wHeight) {
// too down
// panel's max height is guaranteed to be 80% so it's safe to do this
y = wHeight - panelHeight - 10
}

return { x, y, height: panelHeight }
}
}

return null
}

frameDidMount = (frame: HTMLIFrameElement) => {
Expand Down Expand Up @@ -159,11 +95,15 @@ export default class DictPanelPortal extends React.Component<DictPanelPortalProp
iframeStyle.setProperty('transform', `translate(${x}px, ${y}px)`, 'important')
}
}
this.debouncedFrameAnimationEnd()
return null
}

panelImmediateCtrl = (key: string) => {
if (!this.props.config.animation || !this.props.shouldPanelShow) {
if (!this.props.isAnimation ||
!this.props.shouldPanelShow ||
this.props.isPanelAppear
) {
return true
}

Expand All @@ -180,49 +120,25 @@ export default class DictPanelPortal extends React.Component<DictPanelPortalProp
return false
}

onFrameAnimationEnd = () => {
debouncedFrameAnimationEnd = () => {
clearTimeout(this._frameAnimationEndTimeout)

if (!this.props.shouldPanelShow || this.state.isDragging) {
return
}

this._frameAnimationEndTimeout = setTimeout(() => {
if (this.frame) {
this.isAnimating = false
// remove hardware acceleration to prevent blurry font
const iframeStyle = this.frame.style
const { x, y } = this.state
iframeStyle.setProperty('left', x + 'px', 'important')
iframeStyle.setProperty('top', y + 'px', 'important')
iframeStyle.removeProperty('transform')
iframeStyle.removeProperty('opacity')
iframeStyle.removeProperty('will-change')
}
}, 100)
this._frameAnimationEndTimeout = setTimeout(this.onFrameAnimationEnd, 100)
}

updateItemHeight = ({ id, height }: { id: DictID, height: number }) => {
if (isSaladictPopupPage) {
return
}

const dictHeights = this.state.mutableArea.dictHeights
if (dictHeights[id] !== height) {
dictHeights[id] = height

const winHeight = window.innerHeight
const newHeight = Math.min(
winHeight * this.props.config.panelMaxHeightRatio,
30 + this.props.config.dicts.selected
.reduce((sum, id) => sum + (dictHeights[id] || 30), 0),
)

if (this.state.y + newHeight + 10 > winHeight) {
this.setState({ height: newHeight, y: winHeight - 10 - newHeight })
} else {
this.setState({ height: newHeight })
}
onFrameAnimationEnd = () => {
if (this.frame) {
this.isAnimating = false
// remove hardware acceleration to prevent blurry font
const iframeStyle = this.frame.style
const { x, y } = this.props.panelRect
iframeStyle.setProperty('left', x + 'px', 'important')
iframeStyle.setProperty('top', y + 'px', 'important')
iframeStyle.removeProperty('transform')
iframeStyle.removeProperty('opacity')
iframeStyle.removeProperty('will-change')
}
}

Expand All @@ -231,8 +147,8 @@ export default class DictPanelPortal extends React.Component<DictPanelPortalProp
e.preventDefault()
e.stopPropagation()
// e is from iframe, so there is offset
this.lastMouseX = e.clientX + this.state.x
this.lastMouseY = e.clientY + this.state.y
this.lastMouseX = e.clientX + this.props.panelRect.x
this.lastMouseY = e.clientY + this.props.panelRect.y
this.setState({ isDragging: true })
window.addEventListener('mousemove', this.handleWindowMouseMove, { capture: true })
window.addEventListener('mouseup', this.handleDragEnd, { capture: true })
Expand All @@ -246,22 +162,22 @@ export default class DictPanelPortal extends React.Component<DictPanelPortalProp

handleWindowMouseMove = (e: MouseEvent) => {
e.stopPropagation()
const { x, y } = this.state
this.setState({
x: x + e.clientX - this.lastMouseX,
y: y + e.clientY - this.lastMouseY,
})
const { x, y } = this.props.panelRect
this.props.panelOnDrag(
x + e.clientX - this.lastMouseX,
y + e.clientY - this.lastMouseY,
)
this.lastMouseX = e.clientX
this.lastMouseY = e.clientY
}

handleFrameMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
e.stopPropagation()
const { x, y } = this.state
this.setState({
x: x + x + e.clientX - this.lastMouseX,
y: y + y + e.clientY - this.lastMouseY,
})
const { x, y } = this.props.panelRect
this.props.panelOnDrag(
x + x + e.clientX - this.lastMouseX,
y + y + e.clientY - this.lastMouseY,
)
this.lastMouseX = e.clientX + x
this.lastMouseY = e.clientY + y
}
Expand All @@ -277,11 +193,8 @@ export default class DictPanelPortal extends React.Component<DictPanelPortalProp
shouldPanelShow,
} = this.props

const { x, y, height, isDragging } = this.state

const width = isSaladictPopupPage
? Math.min(this.props.config.panelWidth, 800)
: this.props.config.panelWidth
const { isDragging } = this.state
const { x, y, width, height } = this.props.panelRect

if (shouldPanelShow && !this.isMount) {
this.mountEL()
Expand All @@ -296,7 +209,7 @@ export default class DictPanelPortal extends React.Component<DictPanelPortalProp
{shouldPanelShow
? <DictPanel
{...this.props}
updateItemHeight={this.updateItemHeight}
panelWidth={width}
handleDragStart={this.handleDragStart}
frameDidMount={this.frameDidMount}
frameWillUnmount={this.frameWillUnmount}
Expand All @@ -310,7 +223,7 @@ export default class DictPanelPortal extends React.Component<DictPanelPortalProp
opacity: shouldPanelShow ? 1 : 0
}}
immediate={this.panelImmediateCtrl}
onRest={this.onFrameAnimationEnd}
onRest={this.debouncedFrameAnimationEnd}
>{this.animateFrame}</Spring>
</div>,
this.el,
Expand Down
Loading

0 comments on commit 1d4c1e7

Please sign in to comment.