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 @@ -25,6 +25,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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"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",
Expand Down
230 changes: 230 additions & 0 deletions pages/license-filter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
import {
FeatureAttitude,
filterLicenses,
InfectionRange,
License,
} from 'license-filter';
import { observer } from 'mobx-react';
import { GetServerSidePropsContext } from 'next';
import { cache, compose, errorLogger } from 'next-ssr-middleware';
import { FC, useContext, useEffect, useState } from 'react';
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, I18nProps, loadSSRLanguage } from '../models/Translation';

export const getServerSideProps = compose(cache(), errorLogger, async (context: GetServerSidePropsContext) => {
const ssrData = await loadSSRLanguage(context as any);

return {
props: JSON.parse(JSON.stringify({
...ssrData,
})),
};
});

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

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

const LicenseTool: FC<I18nProps> = observer(() => {
const i18n = useContext(I18nContext);
Comment thread
luojiyin1987 marked this conversation as resolved.
Outdated
const { t } = i18n;

const [stepIndex, setStepIndex] = useState(0);
const [keyIndex, setKeyIndex] = useState(0);
const [filterOption, setFilterOption] = useState({});
const [disableChoose, setDisableChoose] = useState(false);
const [lists, setLists] = useState<List[]>([]);

const now = Math.ceil(100 / choiceSteps.length);

useEffect(() => {
if (stepIndex === choiceSteps.length) setDisableChoose(true);
}, [stepIndex]);

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

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

setFilterOption(newObject);

setLists(tempLists);

setStepIndex(stepIndex < choiceSteps.length ? stepIndex + 1 : stepIndex);

setKeyIndex(keyIndex < choiceSteps.length - 1 ? keyIndex + 1 : keyIndex);
};

const backToLast = () => {
const choice = 0;
const key = choiceSteps[keyIndex];

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

setFilterOption(newObject);

setStepIndex(
stepIndex === choiceSteps.length
? stepIndex - 2
: stepIndex > 0
? stepIndex - 1
: stepIndex,
);
setKeyIndex(keyIndex > 0 ? keyIndex - 1 : keyIndex);

if (disableChoose) setDisableChoose(false);
setLists(tempLists);
};

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={(keyIndex + 1) * now}
label={(() => {
const currentLang = i18n.currentLanguage;
const stepNumber = keyIndex + 1;

switch (currentLang) {
case 'zh-CN':
case 'zh-TW':
return `第 ${stepNumber} 步`;
case 'en-US':
default:
return `step ${stepNumber}`;
}
})()}
Comment thread
luojiyin1987 marked this conversation as resolved.
Outdated
/>
<Button className="mb-2" variant="warning" onClick={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 } }) => 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>{renderInfo(license, i18n)}</Accordion.Body>
</Accordion.Item>
))}
</Accordion>
</Container>
);
});

function renderInfo({ link, feature }: License, { t }: typeof i18n) {
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>
</>
);
}

export default LicenseTool;
10 changes: 10 additions & 0 deletions pnpm-lock.yaml

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

58 changes: 58 additions & 0 deletions translation/en-US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,62 @@ export default {
// Scroll List
load_more: 'Load more...',
no_more: 'No more',

//License-tool Page
license_filter: 'Open Source License Selector',
feature_attitude_undefined: "I don't care",
feature_attitude_positive: 'I need',
feature_attitude_negative: "I don't need",
infection_range_library: 'Infection range to library',
infection_range_file: 'Infection range to file',
infection_range_module: 'Infection range to module',
infection_range_undefined: 'No request',

tip_popularity_0:
'Do you want to limit the result to a license agreement that is "popular and widely used, or has a broad community" as described by the Open Source Initiative (OSI)?',
tip_popularity_1:
'This will sacrifice some less popular but perhaps useful features to ensure that the license becomes a mainstream license.',
tip_reuse_condition:
'Do you want to set license conditions for code reuse? If not, your license will be one of the so-called "permissive" licenses.',
tip_infection_intensity:
'Do you want to choose a strongly Copyleft licensing? When a software project contains some of your code, the project as a whole must be distributed under your license, if it is distributed at all. The effect of this will be that the source code for all additions made to the code will be available. If not,the parts of the project you originated from must be distributed under your license, if it is distributed at all. Other parts may be distributed under other licenses, even though they form part of a work with is - as a whole - a modified version of your code. The effect of this will be that the source code to some additions made to the code may not be available.',
tip_jurisdiction: 'Do you want your region to be the jurisdiction?',
tip_patent_statement:
'Do you want to use a license agreement that explicitly grants patent rights (if any)?',
tip_patent_retaliation:
'Do you want to use a license agreement that includes a patent retaliation clause? who brings legal action alleging that the licensed software embodies one of their software patents will lose the license you have granted to copy, use, adapt, and distribute the code. It is intended to dissuade people from bringing this kind of legal action.',
tip_enhanced_attribution:
'Do you want to use a license agreement that specifies "enhanced attribution"? It must take a particular form and appear in specific instances, for example on the user interface of softwares every time it is run. ',
tip_privacy_loophole:
'Do you want to use a license that addresses a "privacy loophole". Require that source code must also be released when services are provided over the Web or when code is deployed internally. The purpose of this is to ensure that all those who benefit from open source projects have a responsibility to give back to the community by sharing their improved and adapted versions.',
tip_marketing_endorsement:
"Do you want to allow promotional licenses? Avoid using the author's name to promote products or services based on the author's code. Such a restriction is intended to protect the authors reputation or prevent misleading publicity.",
tip_infection_range:
'Which parts of the modified version do you want to allow for other licenses, with four options: module-level, file-level, library interface-level, and no requirements ?',
license_tool_headline: 'Open Source License Selector',
license_tool_description:
'This tool is designed to help users understand their own preferences for free and open source software licensing agreements. Users must read these license agreements themselves. It is important to read and fully understand the license agreement you choose before applying it to your project. The classification of license types that support the operation of the tool will inevitably be somewhat reduced. Therefore, the output of the tool cannot and must not be taken as legal advice.',
warn_info:
'Remember: You must read and understand the license agreement you choose',
filter_option: 'filter option',
option_undefined: 'Not required',
step_x: ({ step }: { step: number }) => `step ${step}`,
license_score: 'score',
popularity: 'Popularity',
reuseCondition: 'Reuse Condition',
infectionIntensity: 'Infection Intensity',
infectionRange: 'Infection Range',
jurisdiction: 'Jurisdiction',
patentStatement: 'Patent Statement',
patentRetaliation: 'Patent Retaliation',
enhancedAttribution: 'Enhanced Attribution',
privacyLoophole: 'Privacy Loophole',
marketingEndorsement: 'Marketing Endorsement',
license_detail: 'license detail',
attitude_positive: 'Yes',
attitude_negative: 'Yes',
range_library: 'library',
range_file: 'file',
range_module: 'module',
last_step: 'back',
};
Loading
Loading