Skip to content

Commit ff061ac

Browse files
[add] Class version of License Filter (#25)
Co-authored-by: South Drifter <[email protected]>
1 parent da817f9 commit ff061ac

File tree

8 files changed

+458
-0
lines changed

8 files changed

+458
-0
lines changed

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+
});

components/Navigator/MainNavigator.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const topNavBarMenu = ({ t }: typeof i18n): MenuItem[] => [
2626
href: 'https://github.com/Open-Source-Bazaar/Git-Hackathon-scaffold',
2727
name: t('hackathon'),
2828
},
29+
{ href: '/license-filter', name: t('license_filter') },
2930
];
3031

3132
export interface MainNavigatorProps {

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@
2121
"idea-react": "^2.0.0-rc.13",
2222
"koa": "^2.16.1",
2323
"koajax": "^3.1.2",
24+
"license-filter": "^0.2.5",
2425
"marked": "^16.0.0",
2526
"mime": "^4.0.7",
2627
"mobx": "^6.13.7",
2728
"mobx-github": "^0.3.11",
2829
"mobx-i18n": "^0.7.1",
2930
"mobx-lark": "^2.2.0",
3031
"mobx-react": "^9.2.0",
32+
"mobx-react-helper": "^0.5.1",
3133
"mobx-restful": "^2.1.0",
3234
"mobx-restful-table": "^2.5.2",
3335
"next": "^15.3.5",

pages/license-filter.tsx

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
import { FeatureAttitude, filterLicenses, InfectionRange, License } from 'license-filter';
2+
import { observable } from 'mobx';
3+
import { observer } from 'mobx-react';
4+
import { ObservedComponent } from 'mobx-react-helper';
5+
import { Accordion, Button, ButtonGroup, Container, ProgressBar } from 'react-bootstrap';
6+
7+
import { PageHead } from '../components/Layout/PageHead';
8+
import { licenseTips, optionValue } from '../components/License/helper';
9+
import { i18n, I18nContext } from '../models/Translation';
10+
11+
interface List {
12+
license: License;
13+
score: number;
14+
}
15+
16+
const choiceSteps = [
17+
'popularity',
18+
'reuseCondition',
19+
'infectionIntensity',
20+
'infectionRange',
21+
'jurisdiction',
22+
'patentStatement',
23+
'patentRetaliation',
24+
'enhancedAttribution',
25+
'privacyLoophole',
26+
'marketingEndorsement',
27+
] as const;
28+
29+
@observer
30+
export default class LicenseTool extends ObservedComponent<{}, typeof i18n> {
31+
static contextType = I18nContext;
32+
33+
@observable
34+
accessor stepIndex = 0;
35+
36+
@observable
37+
accessor keyIndex = 0;
38+
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;
54+
55+
const choice = value ? +value : 0;
56+
const key = choiceSteps[keyIndex];
57+
58+
const newObject = { ...filterOption, [key]: choice };
59+
const tempLists = filterLicenses(newObject);
60+
61+
this.filterOption = newObject;
62+
63+
this.lists = tempLists;
64+
65+
this.stepIndex = stepIndex < choiceSteps.length ? stepIndex + 1 : stepIndex;
66+
67+
this.keyIndex = keyIndex < choiceSteps.length - 1 ? keyIndex + 1 : keyIndex;
68+
};
69+
70+
backToLast = () => {
71+
const { stepIndex, keyIndex, filterOption, disableChoose } = this;
72+
const choice = 0;
73+
const key = choiceSteps[keyIndex];
74+
75+
const newObject = { ...filterOption, [key]: choice };
76+
const tempLists = filterLicenses(newObject);
77+
78+
this.filterOption = newObject;
79+
80+
this.stepIndex =
81+
stepIndex === choiceSteps.length ? stepIndex - 2 : stepIndex > 0 ? stepIndex - 1 : stepIndex;
82+
this.keyIndex = keyIndex > 0 ? keyIndex - 1 : keyIndex;
83+
84+
if (disableChoose) this.disableChoose = false;
85+
this.lists = tempLists;
86+
};
87+
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>
109+
))}
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+
}
204+
}

pnpm-lock.yaml

Lines changed: 29 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)