Skip to content

Commit 3473afa

Browse files
committed
[refactor] replace React hooks function with MobX observable class to simplify License Filter source code
1 parent 4d8b780 commit 3473afa

3 files changed

Lines changed: 174 additions & 156 deletions

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"mobx-i18n": "^0.7.1",
3030
"mobx-lark": "^2.2.0",
3131
"mobx-react": "^9.2.0",
32+
"mobx-react-helper": "^0.5.1",
3233
"mobx-restful": "^2.1.0",
3334
"mobx-restful-table": "^2.5.2",
3435
"next": "^15.3.5",

pages/license-filter.tsx

Lines changed: 154 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,12 @@
1-
import {
2-
FeatureAttitude,
3-
filterLicenses,
4-
InfectionRange,
5-
License,
6-
} from 'license-filter';
1+
import { FeatureAttitude, filterLicenses, InfectionRange, License } from 'license-filter';
2+
import { observable } from 'mobx';
73
import { observer } from 'mobx-react';
8-
import { FC, useContext, useEffect, useState } from 'react';
9-
import {
10-
Accordion,
11-
Button,
12-
ButtonGroup,
13-
Container,
14-
ProgressBar,
15-
} from 'react-bootstrap';
4+
import { ObservedComponent } from 'mobx-react-helper';
5+
import { Accordion, Button, ButtonGroup, Container, ProgressBar } from 'react-bootstrap';
166

177
import { PageHead } from '../components/Layout/PageHead';
188
import { licenseTips, optionValue } from '../components/License/helper';
19-
import { i18n, I18nContext, I18nProps} from '../models/Translation';
9+
import { i18n, I18nContext } from '../models/Translation';
2010

2111
interface List {
2212
license: License;
@@ -36,171 +26,179 @@ const choiceSteps = [
3626
'marketingEndorsement',
3727
] as const;
3828

39-
const LicenseTool: FC<I18nProps> = observer(() => {
40-
const i18n = useContext(I18nContext);
41-
const { t } = i18n;
29+
@observer
30+
export default class LicenseTool extends ObservedComponent<{}, typeof i18n> {
31+
static contextType = I18nContext;
4232

43-
const [stepIndex, setStepIndex] = useState(0);
44-
const [keyIndex, setKeyIndex] = useState(0);
45-
const [filterOption, setFilterOption] = useState({});
46-
const [disableChoose, setDisableChoose] = useState(false);
47-
const [lists, setLists] = useState<List[]>([]);
33+
@observable
34+
accessor stepIndex = 0;
4835

49-
const now = Math.ceil(100 / choiceSteps.length);
36+
@observable
37+
accessor keyIndex = 0;
5038

51-
useEffect(() => {
52-
if (stepIndex === choiceSteps.length) setDisableChoose(true);
53-
}, [stepIndex]);
39+
@observable
40+
accessor filterOption = {};
41+
42+
@observable
43+
accessor disableChoose = false;
44+
45+
@observable
46+
accessor lists: List[] = [];
47+
48+
componentDidMount() {
49+
if (this.stepIndex === choiceSteps.length) this.disableChoose = true;
50+
}
51+
52+
handleChoose = (value: string | null) => {
53+
const { stepIndex, keyIndex, filterOption } = this;
5454

55-
const handleChoose = (value: string | null) => {
5655
const choice = value ? +value : 0;
5756
const key = choiceSteps[keyIndex];
5857

5958
const newObject = { ...filterOption, [key]: choice };
6059
const tempLists = filterLicenses(newObject);
6160

62-
setFilterOption(newObject);
61+
this.filterOption = newObject;
6362

64-
setLists(tempLists);
63+
this.lists = tempLists;
6564

66-
setStepIndex(stepIndex < choiceSteps.length ? stepIndex + 1 : stepIndex);
65+
this.stepIndex = stepIndex < choiceSteps.length ? stepIndex + 1 : stepIndex;
6766

68-
setKeyIndex(keyIndex < choiceSteps.length - 1 ? keyIndex + 1 : keyIndex);
67+
this.keyIndex = keyIndex < choiceSteps.length - 1 ? keyIndex + 1 : keyIndex;
6968
};
7069

71-
const backToLast = () => {
70+
backToLast = () => {
71+
const { stepIndex, keyIndex, filterOption, disableChoose } = this;
7272
const choice = 0;
7373
const key = choiceSteps[keyIndex];
7474

7575
const newObject = { ...filterOption, [key]: choice };
7676
const tempLists = filterLicenses(newObject);
7777

78-
setFilterOption(newObject);
78+
this.filterOption = newObject;
7979

80-
setStepIndex(
81-
stepIndex === choiceSteps.length
82-
? stepIndex - 2
83-
: stepIndex > 0
84-
? stepIndex - 1
85-
: stepIndex,
86-
);
87-
setKeyIndex(keyIndex > 0 ? keyIndex - 1 : keyIndex);
80+
this.stepIndex =
81+
stepIndex === choiceSteps.length ? stepIndex - 2 : stepIndex > 0 ? stepIndex - 1 : stepIndex;
82+
this.keyIndex = keyIndex > 0 ? keyIndex - 1 : keyIndex;
8883

89-
if (disableChoose) setDisableChoose(false);
90-
setLists(tempLists);
84+
if (disableChoose) this.disableChoose = false;
85+
this.lists = tempLists;
9186
};
9287

93-
return (
94-
<Container className="py-5">
95-
<PageHead title={t('license_tool_headline')} />
96-
<h1>{t('license_tool_headline')}</h1>
97-
98-
<p>{t('license_tool_description')}</p>
99-
<p className="text-warning">{t('warn_info')}</p>
100-
101-
<h2>
102-
{t('filter_option')}: {t(choiceSteps[keyIndex])}
103-
</h2>
104-
105-
{licenseTips(i18n)[choiceSteps[keyIndex]].map(({ text }) => (
106-
<p key={text}>{text}</p>
107-
))}
108-
<ProgressBar
109-
className="mb-3"
110-
variant="info"
111-
now={(keyIndex + 1) * now}
112-
label={`${(keyIndex + 1) * now}%`}
113-
/>
114-
<Button className="mb-2" variant="warning" onClick={backToLast}>
115-
{t('last_step')}
116-
</Button>
117-
<ButtonGroup className="mb-2">
118-
{optionValue(i18n)[choiceSteps[keyIndex]].map(({ value, text }) => (
119-
<Button
120-
key={value}
121-
className="mx-1"
122-
value={value}
123-
id={`tb-${value}`}
124-
disabled={disableChoose}
125-
onClick={({ currentTarget: { value } }) => handleChoose(value)}
126-
>
127-
{text}
128-
</Button>
129-
))}
130-
</ButtonGroup>
131-
132-
<Accordion defaultActiveKey="0">
133-
{lists.map(({ license, score }, index) => (
134-
<Accordion.Item key={license.name} eventKey={index + 1 + ''}>
135-
<Accordion.Header>
136-
{license.name} {t('license_score')}: {score * 10}
137-
</Accordion.Header>
138-
<Accordion.Body>{renderInfo(license, i18n)}</Accordion.Body>
139-
</Accordion.Item>
88+
render() {
89+
const i18n = this.observedContext;
90+
const { t } = i18n,
91+
{ keyIndex, disableChoose, lists } = this,
92+
now = Math.ceil(100 / choiceSteps.length);
93+
const percent = (keyIndex + 1) * now;
94+
95+
return (
96+
<Container className="py-5">
97+
<PageHead title={t('license_tool_headline')} />
98+
<h1>{t('license_tool_headline')}</h1>
99+
100+
<p>{t('license_tool_description')}</p>
101+
<p className="text-warning">{t('warn_info')}</p>
102+
103+
<h2>
104+
{t('filter_option')}: {t(choiceSteps[keyIndex])}
105+
</h2>
106+
107+
{licenseTips(i18n)[choiceSteps[keyIndex]].map(({ text }) => (
108+
<p key={text}>{text}</p>
140109
))}
141-
</Accordion>
142-
</Container>
143-
);
144-
});
145-
146-
function renderInfo({ link, feature }: License, { t }: typeof i18n) {
147-
const judge = (attitude: FeatureAttitude) =>
148-
({
149-
[FeatureAttitude.Positive]: t('attitude_positive'),
150-
[FeatureAttitude.Negative]: t('attitude_negative'),
151-
[FeatureAttitude.Undefined]: t('option_undefined'),
152-
})[attitude] || t('option_undefined');
153-
154-
const judgeInfectionRange = (infectionRange: InfectionRange | undefined) =>
155-
infectionRange !== undefined
156-
? {
157-
[InfectionRange.Library]: t('range_library'),
158-
[InfectionRange.File]: t('range_file'),
159-
[InfectionRange.Module]: t('range_module'),
160-
}[infectionRange]
161-
: t('option_undefined');
162-
163-
return (
164-
<>
165-
<ul>
166-
<li>
167-
{t('popularity')}: {judge(feature.popularity)}
168-
</li>
169-
<li>
170-
{t('reuseCondition')}: {judge(feature.reuseCondition)}
171-
</li>
172-
<li>
173-
{t('infectionIntensity')}: {judge(feature.infectionIntensity)}
174-
</li>
175-
176-
<li>
177-
{t('infectionRange')}: {judgeInfectionRange(feature.infectionRange)}
178-
</li>
179-
180-
<li>
181-
{t('jurisdiction')}: {judge(feature.jurisdiction)}
182-
</li>
183-
<li>
184-
{t('patentStatement')}: {judge(feature.patentStatement)}
185-
</li>
186-
<li>
187-
{t('patentRetaliation')}: {judge(feature.patentRetaliation)}
188-
</li>
189-
<li>
190-
{t('enhancedAttribution')}: {judge(feature.enhancedAttribution)}
191-
</li>
192-
<li>
193-
{t('privacyLoophole')}: {judge(feature.privacyLoophole)}
194-
</li>
195-
<li>
196-
{t('marketingEndorsement')}: {judge(feature.marketingEndorsement)}
197-
</li>
198-
</ul>
199-
<Button size="sm" target="_blank" href={link}>
200-
{t('license_detail')}
201-
</Button>
202-
</>
203-
);
110+
<ProgressBar className="mb-3" variant="info" now={percent} label={`${percent}%`} />
111+
112+
<Button className="mb-2" variant="warning" onClick={this.backToLast}>
113+
{t('last_step')}
114+
</Button>
115+
<ButtonGroup className="mb-2">
116+
{optionValue(i18n)[choiceSteps[keyIndex]].map(({ value, text }) => (
117+
<Button
118+
key={value}
119+
className="mx-1"
120+
value={value}
121+
id={`tb-${value}`}
122+
disabled={disableChoose}
123+
onClick={({ currentTarget: { value } }) => this.handleChoose(value)}
124+
>
125+
{text}
126+
</Button>
127+
))}
128+
</ButtonGroup>
129+
130+
<Accordion defaultActiveKey="0">
131+
{lists.map(({ license, score }, index) => (
132+
<Accordion.Item key={license.name} eventKey={index + 1 + ''}>
133+
<Accordion.Header>
134+
{license.name} {t('license_score')}: {score * 10}
135+
</Accordion.Header>
136+
<Accordion.Body>{this.renderInfo(license)}</Accordion.Body>
137+
</Accordion.Item>
138+
))}
139+
</Accordion>
140+
</Container>
141+
);
142+
}
143+
144+
renderInfo({ link, feature }: License) {
145+
const { t } = this.observedContext;
146+
const judge = (attitude: FeatureAttitude) =>
147+
({
148+
[FeatureAttitude.Positive]: t('attitude_positive'),
149+
[FeatureAttitude.Negative]: t('attitude_negative'),
150+
[FeatureAttitude.Undefined]: t('option_undefined'),
151+
})[attitude] || t('option_undefined');
152+
153+
const judgeInfectionRange = (infectionRange: InfectionRange | undefined) =>
154+
infectionRange !== undefined
155+
? {
156+
[InfectionRange.Library]: t('range_library'),
157+
[InfectionRange.File]: t('range_file'),
158+
[InfectionRange.Module]: t('range_module'),
159+
}[infectionRange]
160+
: t('option_undefined');
161+
162+
return (
163+
<>
164+
<ul>
165+
<li>
166+
{t('popularity')}: {judge(feature.popularity)}
167+
</li>
168+
<li>
169+
{t('reuseCondition')}: {judge(feature.reuseCondition)}
170+
</li>
171+
<li>
172+
{t('infectionIntensity')}: {judge(feature.infectionIntensity)}
173+
</li>
174+
175+
<li>
176+
{t('infectionRange')}: {judgeInfectionRange(feature.infectionRange)}
177+
</li>
178+
179+
<li>
180+
{t('jurisdiction')}: {judge(feature.jurisdiction)}
181+
</li>
182+
<li>
183+
{t('patentStatement')}: {judge(feature.patentStatement)}
184+
</li>
185+
<li>
186+
{t('patentRetaliation')}: {judge(feature.patentRetaliation)}
187+
</li>
188+
<li>
189+
{t('enhancedAttribution')}: {judge(feature.enhancedAttribution)}
190+
</li>
191+
<li>
192+
{t('privacyLoophole')}: {judge(feature.privacyLoophole)}
193+
</li>
194+
<li>
195+
{t('marketingEndorsement')}: {judge(feature.marketingEndorsement)}
196+
</li>
197+
</ul>
198+
<Button size="sm" target="_blank" href={link}>
199+
{t('license_detail')}
200+
</Button>
201+
</>
202+
);
203+
}
204204
}
205-
206-
export default LicenseTool;

pnpm-lock.yaml

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)