forked from xivanalysis/xivanalysis
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ResultSegment.tsx
155 lines (129 loc) · 4.19 KB
/
ResultSegment.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import {Trans} from '@lingui/react'
import {Segment} from 'akkd'
import NormalisedMessage from 'components/ui/NormalisedMessage'
import {DISPLAY_MODE} from 'parser/core/Module'
import {Result} from 'parser/core/Parser'
import React from 'react'
import ReactDOM from 'react-dom'
import {Header, Icon} from 'semantic-ui-react'
import {gutter} from 'theme'
import styles from './Analyse.module.css'
import {Consumer, Context, Scrollable} from './SegmentPositionContext'
interface Props {
index: number
result: Result
}
interface State {
collapsed?: boolean
}
export const OFFSET_FROM_VIEWPORT_TOP = gutter
const MODULE_HEIGHT_MAX = 400
const MODULE_HEIGHT_LEEWAY = 200
export default class ResultSegment extends React.PureComponent<Props, State> implements Scrollable {
private static instances = new Map<string, ResultSegment>()
public static scrollIntoView(handle: string) {
const instance = this.instances.get(handle)
if (instance !== undefined) {
instance.scrollIntoView()
}
}
private readonly observer = new IntersectionObserver(this.handleIntersection.bind(this), {
rootMargin: `${-(OFFSET_FROM_VIEWPORT_TOP + 1)}px 0px 0px 0px`,
})
private ref: HTMLElement|null = null
private positionContext!: Context
constructor(props: Props) {
super(props)
this.scrollIntoView.bind(this)
const state: State = {}
if (props.result.mode === DISPLAY_MODE.FULL) {
state.collapsed = false
}
this.state = state
}
override componentDidMount() {
// semantic-ui-react doesn't support refs at all, so we'd either need a wrapping div that's there
// just to be ref'd, or we need the ReactDOM hacks. We _need_ the element to have a size so we can't
// just jam it in as a 0-size child that wouldn't cause any trouble.
// eslint-disable-next-line react/no-find-dom-node
this.ref = ReactDOM.findDOMNode(this) as HTMLElement
this.observer.observe(this.ref)
if (!ResultSegment.instances.has(this.props.result.handle)) {
ResultSegment.instances.set(this.props.result.handle, this)
}
}
override componentDidUpdate(prevProps: Readonly<Props>) {
if (this.props.index !== prevProps.index) {
this.positionContext.unregister(prevProps.index)
}
// eslint-disable-next-line react/no-find-dom-node
const ref = ReactDOM.findDOMNode(this) as HTMLElement
if (ref !== this.ref) {
this.ref != null && this.observer.unobserve(this.ref)
this.ref = ref
this.observer.observe(ref)
}
}
override componentWillUnmount() {
if (ResultSegment.instances.get(this.props.result.handle) === this) {
ResultSegment.instances.delete(this.props.result.handle)
}
this.observer.disconnect()
this.positionContext.unregister(this.props.index)
}
override render() {
return <Consumer>{value => {
this.positionContext = value
return this.renderContent()
}}</Consumer>
}
private renderContent = () => {
const {result} = this.props
if (result.mode === DISPLAY_MODE.RAW) {
return <div>{result.markup}</div>
}
const contents = <>
<Header><NormalisedMessage message={result.name} id={result.i18n_id}/></Header>
<div>{result.markup}</div>
</>
if (result.mode === DISPLAY_MODE.FULL) {
return <Segment>{contents}</Segment>
}
const {collapsed} = this.state
const seeMore = <>
<Icon name="chevron down"/>
<strong className={styles.seeMore}>
<Trans id="core.analyse.see-more">See more</Trans>
</strong>
<Icon name="chevron down"/>
</>
return (
<Segment.Expandable
collapsed={collapsed}
maxHeight={MODULE_HEIGHT_MAX}
leeway={MODULE_HEIGHT_LEEWAY}
seeMore={seeMore}
>
{contents}
</Segment.Expandable>
)
}
private handleIntersection(entries: IntersectionObserverEntry[]) {
for (const entry of entries) {
const active = entry.boundingClientRect.bottom > OFFSET_FROM_VIEWPORT_TOP
this.positionContext.register(this, this.props.index, active)
}
}
scrollIntoView() {
if (this.ref == null) { return }
// Try to use the smooth scrolling, fall back to the old method
const scrollAmount = this.ref.getBoundingClientRect().top - OFFSET_FROM_VIEWPORT_TOP
try {
scrollBy({top: scrollAmount, behavior: 'smooth'})
} catch {
scrollBy(0, scrollAmount)
}
// Make sure the segment is expanded
this.setState({collapsed: false})
}
}