Skip to content
Merged
53 changes: 53 additions & 0 deletions components/License/helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { FeatureAttitude, InfectionRange } from 'license-filter';

import { i18n } from '../../models/Translation';

type OptionValue = Record<string, { value: number; text: string }[]>;

type LicenseTips = Record<string, { text: string }[]>;

const options: string[] = [
'popularity',
'reuseCondition',
'infectionIntensity',
'jurisdiction',
'patentStatement',
'patentRetaliation',
'enhancedAttribution',
'privacyLoophole',
'marketingEndorsement',
];

export const optionValue = ({ t }: typeof i18n) => {
const optionValue = options.reduce((optionValue, option) => {
optionValue[option] = [
{ value: FeatureAttitude.Undefined, text: t('feature_attitude_undefined') },
{ value: FeatureAttitude.Positive, text: t('feature_attitude_positive') },
{ value: FeatureAttitude.Negative, text: t('feature_attitude_negative') },
];

return optionValue;
}, {} as OptionValue);

optionValue.infectionRange = [
{ value: 0, text: t('infection_range_undefined') },
{ value: InfectionRange.Library, text: t('infection_range_library') },
{ value: InfectionRange.File, text: t('infection_range_file') },
{ value: InfectionRange.Module, text: t('infection_range_module') },
];

return optionValue;
};

export const licenseTips = ({ t }: typeof i18n): LicenseTips => ({
popularity: [{ text: t('tip_popularity_0') }, { text: t('tip_popularity_1') }],
reuseCondition: [{ text: t('tip_reuse_condition') }],
infectionIntensity: [{ text: t('tip_infection_intensity') }],
jurisdiction: [{ text: t('tip_jurisdiction') }],
patentStatement: [{ text: t('tip_patent_statement') }],
patentRetaliation: [{ text: t('tip_patent_retaliation') }],
enhancedAttribution: [{ text: t('tip_enhanced_attribution') }],
privacyLoophole: [{ text: t('tip_privacy_loophole') }],
marketingEndorsement: [{ text: t('tip_marketing_endorsement') }],
infectionRange: [{ text: t('tip_infection_range') }],
});
1 change: 1 addition & 0 deletions components/Navigator/MainNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const topNavBarMenu = ({ t }: typeof i18n): MenuItem[] => [
href: 'https://github.com/Open-Source-Bazaar/Git-Hackathon-scaffold',
name: t('hackathon'),
},
{ href: '/license-filter', name: t('license_filter') },
];

export interface MainNavigatorProps {
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@
"idea-react": "^2.0.0-rc.13",
"koa": "^2.16.1",
"koajax": "^3.1.2",
"license-filter": "^0.2.5",
"marked": "^16.0.0",
"mime": "^4.0.7",
"mobx": "^6.13.7",
"mobx-github": "^0.3.11",
"mobx-i18n": "^0.7.1",
"mobx-lark": "^2.2.0",
"mobx-react": "^9.2.0",
"mobx-react-helper": "^0.5.1",
"mobx-restful": "^2.1.0",
"mobx-restful-table": "^2.5.2",
"next": "^15.3.5",
Expand Down
204 changes: 204 additions & 0 deletions pages/license-filter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
import { FeatureAttitude, filterLicenses, InfectionRange, License } from 'license-filter';
import { observable } from 'mobx';
import { observer } from 'mobx-react';
import { ObservedComponent } from 'mobx-react-helper';
import { Accordion, Button, ButtonGroup, Container, ProgressBar } from 'react-bootstrap';

import { PageHead } from '../components/Layout/PageHead';
import { licenseTips, optionValue } from '../components/License/helper';
import { i18n, I18nContext } from '../models/Translation';

interface List {
license: License;
score: number;
}

const choiceSteps = [
'popularity',
'reuseCondition',
'infectionIntensity',
'infectionRange',
'jurisdiction',
'patentStatement',
'patentRetaliation',
'enhancedAttribution',
'privacyLoophole',
'marketingEndorsement',
] as const;

@observer
export default class LicenseTool extends ObservedComponent<{}, typeof i18n> {
static contextType = I18nContext;

@observable
accessor stepIndex = 0;

@observable
accessor keyIndex = 0;

@observable
accessor filterOption = {};

@observable
accessor disableChoose = false;

@observable
accessor lists: List[] = [];

componentDidMount() {
if (this.stepIndex === choiceSteps.length) this.disableChoose = true;
}

handleChoose = (value: string | null) => {
const { stepIndex, keyIndex, filterOption } = this;

const choice = value ? +value : 0;
const key = choiceSteps[keyIndex];

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

this.filterOption = newObject;

this.lists = tempLists;

this.stepIndex = stepIndex < choiceSteps.length ? stepIndex + 1 : stepIndex;

this.keyIndex = keyIndex < choiceSteps.length - 1 ? keyIndex + 1 : keyIndex;
};

backToLast = () => {
const { stepIndex, keyIndex, filterOption, disableChoose } = this;
const choice = 0;
const key = choiceSteps[keyIndex];

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

this.filterOption = newObject;

this.stepIndex =
stepIndex === choiceSteps.length ? stepIndex - 2 : stepIndex > 0 ? stepIndex - 1 : stepIndex;
this.keyIndex = keyIndex > 0 ? keyIndex - 1 : keyIndex;

if (disableChoose) this.disableChoose = false;
this.lists = tempLists;
};

render() {
const i18n = this.observedContext;
const { t } = i18n,
{ keyIndex, disableChoose, lists } = this,
now = Math.ceil(100 / choiceSteps.length);
const percent = (keyIndex + 1) * now;

return (
<Container className="py-5">
<PageHead title={t('license_tool_headline')} />
<h1>{t('license_tool_headline')}</h1>

<p>{t('license_tool_description')}</p>
<p className="text-warning">{t('warn_info')}</p>

<h2>
{t('filter_option')}: {t(choiceSteps[keyIndex])}
</h2>

{licenseTips(i18n)[choiceSteps[keyIndex]].map(({ text }) => (
<p key={text}>{text}</p>
))}
<ProgressBar className="mb-3" variant="info" now={percent} label={`${percent}%`} />

<Button className="mb-2" variant="warning" onClick={this.backToLast}>
{t('last_step')}
</Button>
<ButtonGroup className="mb-2">
{optionValue(i18n)[choiceSteps[keyIndex]].map(({ value, text }) => (
<Button
key={value}
className="mx-1"
value={value}
id={`tb-${value}`}
disabled={disableChoose}
onClick={({ currentTarget: { value } }) => this.handleChoose(value)}
>
{text}
</Button>
))}
</ButtonGroup>

<Accordion defaultActiveKey="0">
{lists.map(({ license, score }, index) => (
<Accordion.Item key={license.name} eventKey={index + 1 + ''}>
<Accordion.Header>
{license.name} {t('license_score')}: {score * 10}
</Accordion.Header>
<Accordion.Body>{this.renderInfo(license)}</Accordion.Body>
</Accordion.Item>
))}
</Accordion>
</Container>
);
}

renderInfo({ link, feature }: License) {
const { t } = this.observedContext;
const judge = (attitude: FeatureAttitude) =>
({
[FeatureAttitude.Positive]: t('attitude_positive'),
[FeatureAttitude.Negative]: t('attitude_negative'),
[FeatureAttitude.Undefined]: t('option_undefined'),
})[attitude] || t('option_undefined');

const judgeInfectionRange = (infectionRange: InfectionRange | undefined) =>
infectionRange !== undefined
? {
[InfectionRange.Library]: t('range_library'),
[InfectionRange.File]: t('range_file'),
[InfectionRange.Module]: t('range_module'),
}[infectionRange]
: t('option_undefined');

return (
<>
<ul>
<li>
{t('popularity')}: {judge(feature.popularity)}
</li>
<li>
{t('reuseCondition')}: {judge(feature.reuseCondition)}
</li>
<li>
{t('infectionIntensity')}: {judge(feature.infectionIntensity)}
</li>

<li>
{t('infectionRange')}: {judgeInfectionRange(feature.infectionRange)}
</li>

<li>
{t('jurisdiction')}: {judge(feature.jurisdiction)}
</li>
<li>
{t('patentStatement')}: {judge(feature.patentStatement)}
</li>
<li>
{t('patentRetaliation')}: {judge(feature.patentRetaliation)}
</li>
<li>
{t('enhancedAttribution')}: {judge(feature.enhancedAttribution)}
</li>
<li>
{t('privacyLoophole')}: {judge(feature.privacyLoophole)}
</li>
<li>
{t('marketingEndorsement')}: {judge(feature.marketingEndorsement)}
</li>
</ul>
<Button size="sm" target="_blank" href={link}>
{t('license_detail')}
</Button>
</>
);
}
}
29 changes: 29 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading