Skip to content

Commit ce3a499

Browse files
committed
feat: license filter
1 parent 8f38ca2 commit ce3a499

2 files changed

Lines changed: 244 additions & 0 deletions

File tree

components/License/helper.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { FeatureAttitude, InfectionRange } from 'license-filter';
2+
3+
import { i18n } from '../../models/Translation';
4+
5+
type OptionValue = Record<string, { value: number; text: string }[]>;
6+
7+
type LicenseTips = Record<string, { text: string }[]>;
8+
9+
const options: string[] = [
10+
'popularity',
11+
'reuseCondition',
12+
'infectionIntensity',
13+
'jurisdiction',
14+
'patentStatement',
15+
'patentRetaliation',
16+
'enhancedAttribution',
17+
'privacyLoophole',
18+
'marketingEndorsement',
19+
];
20+
21+
export const optionValue = ({ t }: typeof i18n) => {
22+
const optionValue = options.reduce((optionValue, option) => {
23+
optionValue[option] = [
24+
{ value: FeatureAttitude.Undefined, text: t('feature_attitude_undefined') },
25+
{ value: FeatureAttitude.Positive, text: t('feature_attitude_positive') },
26+
{ value: FeatureAttitude.Negative, text: t('feature_attitude_negative') },
27+
];
28+
29+
return optionValue;
30+
}, {} as OptionValue);
31+
32+
optionValue.infectionRange = [
33+
{ value: 0, text: t('infection_range_undefined') },
34+
{ value: InfectionRange.Library, text: t('infection_range_library') },
35+
{ value: InfectionRange.File, text: t('infection_range_file') },
36+
{ value: InfectionRange.Module, text: t('infection_range_module') },
37+
];
38+
39+
return optionValue;
40+
};
41+
42+
export const licenseTips = ({ t }: typeof i18n): LicenseTips => ({
43+
popularity: [{ text: t('tip_popularity_0') }, { text: t('tip_popularity_1') }],
44+
reuseCondition: [{ text: t('tip_reuse_condition') }],
45+
infectionIntensity: [{ text: t('tip_infection_intensity') }],
46+
jurisdiction: [{ text: t('tip_jurisdiction') }],
47+
patentStatement: [{ text: t('tip_patent_statement') }],
48+
patentRetaliation: [{ text: t('tip_patent_retaliation') }],
49+
enhancedAttribution: [{ text: t('tip_enhanced_attribution') }],
50+
privacyLoophole: [{ text: t('tip_privacy_loophole') }],
51+
marketingEndorsement: [{ text: t('tip_marketing_endorsement') }],
52+
infectionRange: [{ text: t('tip_infection_range') }],
53+
});

pages/license-filter.tsx

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
import { FeatureAttitude, filterLicenses, InfectionRange, License } from 'license-filter';
2+
import { observer } from 'mobx-react';
3+
import { FC, useContext, useEffect, useState } from 'react';
4+
import { Accordion, Button, ButtonGroup, Container, ProgressBar } from 'react-bootstrap';
5+
6+
import { PageHead } from '../components/Layout/PageHead';
7+
import { licenseTips, optionValue } from '../components/License/helper';
8+
import { i18n, I18nContext } from '../models/Translation';
9+
10+
interface List {
11+
license: License;
12+
score: number;
13+
}
14+
15+
const choiceSteps = [
16+
'popularity',
17+
'reuseCondition',
18+
'infectionIntensity',
19+
'infectionRange',
20+
'jurisdiction',
21+
'patentStatement',
22+
'patentRetaliation',
23+
'enhancedAttribution',
24+
'privacyLoophole',
25+
'marketingEndorsement',
26+
] as const;
27+
28+
const LicenseTool: FC = observer(() => {
29+
const i18n = useContext(I18nContext);
30+
const { t } = i18n;
31+
32+
const [stepIndex, setStepIndex] = useState(0);
33+
const [keyIndex, setKeyIndex] = useState(0);
34+
const [filterOption, setFilterOption] = useState({});
35+
const [disableChoose, setDisableChoose] = useState(false);
36+
const [lists, setLists] = useState<List[]>([]);
37+
38+
const now = Math.ceil(100 / choiceSteps.length);
39+
40+
useEffect(() => {
41+
if (stepIndex === choiceSteps.length) setDisableChoose(true);
42+
}, [stepIndex]);
43+
44+
const handleChoose = (value: string | null) => {
45+
const choice = value ? +value : 0;
46+
const key = choiceSteps[keyIndex];
47+
48+
const newObject = { ...filterOption, [key]: choice };
49+
const tempLists = filterLicenses(newObject);
50+
51+
setFilterOption(newObject);
52+
53+
setLists(tempLists);
54+
55+
setStepIndex(stepIndex < choiceSteps.length ? stepIndex + 1 : stepIndex);
56+
57+
setKeyIndex(keyIndex < choiceSteps.length - 1 ? keyIndex + 1 : keyIndex);
58+
};
59+
60+
const backToLast = () => {
61+
const choice = 0;
62+
const key = choiceSteps[keyIndex];
63+
64+
const newObject = { ...filterOption, [key]: choice };
65+
const tempLists = filterLicenses(newObject);
66+
67+
setFilterOption(newObject);
68+
69+
setStepIndex(
70+
stepIndex === choiceSteps.length ? stepIndex - 2 : stepIndex > 0 ? stepIndex - 1 : stepIndex,
71+
);
72+
setKeyIndex(keyIndex > 0 ? keyIndex - 1 : keyIndex);
73+
74+
if (disableChoose) setDisableChoose(false);
75+
setLists(tempLists);
76+
};
77+
78+
return (
79+
<Container className="py-5">
80+
<PageHead title={t('license_tool_headline')} />
81+
<h1>{t('license_tool_headline')}</h1>
82+
83+
<p>{t('license_tool_description')}</p>
84+
<p className="text-warning">{t('warn_info')}</p>
85+
86+
<h2>
87+
{t('filter_option')}: {t(choiceSteps[keyIndex])}
88+
</h2>
89+
90+
{licenseTips(i18n)[choiceSteps[keyIndex]].map(({ text }) => (
91+
<p key={text}>{text}</p>
92+
))}
93+
<ProgressBar
94+
className="mb-3"
95+
variant="info"
96+
now={(keyIndex + 1) * now}
97+
label={t('step_x', { step: keyIndex + 1 })}
98+
/>
99+
<Button className="mb-2" variant="warning" onClick={backToLast}>
100+
{t('last_step')}
101+
</Button>
102+
<ButtonGroup className="mb-2">
103+
{optionValue(i18n)[choiceSteps[keyIndex]].map(({ value, text }) => (
104+
<Button
105+
key={value}
106+
className="mx-1"
107+
value={value}
108+
id={`tb-${value}`}
109+
disabled={disableChoose}
110+
onClick={({ currentTarget: { value } }) => handleChoose(value)}
111+
>
112+
{text}
113+
</Button>
114+
))}
115+
</ButtonGroup>
116+
117+
<Accordion defaultActiveKey="0">
118+
{lists.map(({ license, score }, index) => (
119+
<Accordion.Item key={license.name} eventKey={index + 1 + ''}>
120+
<Accordion.Header>
121+
{license.name} {t('license_score')}: {score * 10}
122+
</Accordion.Header>
123+
<Accordion.Body>{renderInfo(license, i18n)}</Accordion.Body>
124+
</Accordion.Item>
125+
))}
126+
</Accordion>
127+
</Container>
128+
);
129+
});
130+
131+
function renderInfo({ link, feature }: License, { t }: typeof i18n) {
132+
const judge = (attitude: FeatureAttitude) =>
133+
({
134+
[FeatureAttitude.Positive]: t('attitude_positive'),
135+
[FeatureAttitude.Negative]: t('attitude_negative'),
136+
[FeatureAttitude.Undefined]: t('option_undefined'),
137+
})[attitude] || t('option_undefined');
138+
139+
const judgeInfectionRange = (infectionRange: InfectionRange | undefined) =>
140+
infectionRange !== undefined
141+
? {
142+
[InfectionRange.Library]: t('range_library'),
143+
[InfectionRange.File]: t('range_file'),
144+
[InfectionRange.Module]: t('range_module'),
145+
}[infectionRange]
146+
: t('option_undefined');
147+
148+
return (
149+
<>
150+
<ul>
151+
<li>
152+
{t('popularity')}: {judge(feature.popularity)}
153+
</li>
154+
<li>
155+
{t('reuseCondition')}: {judge(feature.reuseCondition)}
156+
</li>
157+
<li>
158+
{t('infectionIntensity')}: {judge(feature.infectionIntensity)}
159+
</li>
160+
161+
<li>
162+
{t('infectionRange')}: {judgeInfectionRange(feature.infectionRange)}
163+
</li>
164+
165+
<li>
166+
{t('jurisdiction')}: {judge(feature.jurisdiction)}
167+
</li>
168+
<li>
169+
{t('patentStatement')}: {judge(feature.patentStatement)}
170+
</li>
171+
<li>
172+
{t('patentRetaliation')}: {judge(feature.patentRetaliation)}
173+
</li>
174+
<li>
175+
{t('enhancedAttribution')}: {judge(feature.enhancedAttribution)}
176+
</li>
177+
<li>
178+
{t('privacyLoophole')}: {judge(feature.privacyLoophole)}
179+
</li>
180+
<li>
181+
{t('marketingEndorsement')}: {judge(feature.marketingEndorsement)}
182+
</li>
183+
</ul>
184+
<Button size="sm" target="_blank" href={link}>
185+
{t('license_detail')}
186+
</Button>
187+
</>
188+
);
189+
}
190+
191+
export default LicenseTool;

0 commit comments

Comments
 (0)