2019-09-17 14:30:26 +09:00
|
|
|
import React, {useEffect, useState, useRef} from 'react';
|
|
|
|
import styled from 'styled-components';
|
|
|
|
import {useTranslation} from 'react-i18next';
|
|
|
|
import fetch from 'isomorphic-unfetch';
|
|
|
|
import {TiArrowSync} from 'react-icons/ti';
|
2019-08-06 02:07:05 +09:00
|
|
|
|
2019-09-17 14:30:26 +09:00
|
|
|
import {capitalize} from '../util/text';
|
|
|
|
import {mobile} from '../util/css';
|
2019-08-03 16:44:48 +09:00
|
|
|
|
2019-09-17 14:30:26 +09:00
|
|
|
type Modifier = (word: string) => string;
|
2019-09-01 01:28:24 +09:00
|
|
|
|
2019-12-24 01:57:07 +09:00
|
|
|
function lower(word: string): string {
|
|
|
|
return word.toLowerCase();
|
|
|
|
}
|
|
|
|
|
2019-09-17 14:30:26 +09:00
|
|
|
const maximumCount = 3;
|
2019-09-01 01:28:24 +09:00
|
|
|
const modifiers: Modifier[] = [
|
2019-12-24 01:57:07 +09:00
|
|
|
(word): string => `${capitalize(word)}ify`,
|
|
|
|
(word): string => `lib${lower(word)}`,
|
|
|
|
(word): string => `Omni${capitalize(word)}`,
|
|
|
|
(word): string => `${capitalize(word)}Lab`,
|
|
|
|
(word): string => `${capitalize(word)}Kit`,
|
|
|
|
(word): string => `Open${capitalize(word)}`,
|
|
|
|
(word): string => `${capitalize(word)}box`,
|
|
|
|
(word): string => `Insta${lower(word)}`,
|
|
|
|
(word): string => `${capitalize(word)}Hub`,
|
|
|
|
(word): string => `Cloud${capitalize(word)}`,
|
|
|
|
(word): string => `quick${lower(word)}`,
|
|
|
|
(word): string => `fast${lower(word)}`,
|
|
|
|
(word): string => `super-${lower(word)}`,
|
|
|
|
(word): string => `Hyper${capitalize(word)}`,
|
|
|
|
(word): string => `${capitalize(word)}Go`,
|
|
|
|
(word): string => `${lower(word)}-io`,
|
|
|
|
(word): string => `Go${capitalize(word)}`,
|
|
|
|
(word): string => `${capitalize(word)}X`,
|
|
|
|
(word): string => `${capitalize(word)}time`,
|
|
|
|
(word): string => `${capitalize(word)}flow`,
|
|
|
|
(word): string => `${capitalize(word)}ful`,
|
|
|
|
(word): string => `${capitalize(word)}ery`,
|
|
|
|
(word): string => `${lower(word)}ly`,
|
|
|
|
(word): string => `${lower(word)}joy`,
|
|
|
|
(word): string => `${capitalize(word)}Hunt`,
|
|
|
|
(word): string => `${capitalize(word)}gram`,
|
|
|
|
(word): string => `${capitalize(word)}base`,
|
|
|
|
(word): string => `${capitalize(word)}API`,
|
|
|
|
(word): string => `${capitalize(word)}note`,
|
|
|
|
(word): string => `In${capitalize(word)}`,
|
|
|
|
(word): string => `Uni${lower(word)}`,
|
|
|
|
(word): string => `${capitalize(word)}`,
|
2019-09-17 14:30:26 +09:00
|
|
|
];
|
2019-08-03 22:26:37 +09:00
|
|
|
|
2019-12-24 01:57:07 +09:00
|
|
|
function shuffleArray<T>(array: T[]): T[] {
|
2019-08-03 22:26:37 +09:00
|
|
|
for (let i = array.length - 1; i > 0; i--) {
|
2019-09-17 14:30:26 +09:00
|
|
|
const j = Math.floor(Math.random() * (i + 1));
|
|
|
|
const temp = array[i];
|
|
|
|
array[i] = array[j];
|
|
|
|
array[j] = temp;
|
2019-08-03 22:26:37 +09:00
|
|
|
}
|
2019-09-17 14:30:26 +09:00
|
|
|
return array;
|
2019-08-03 22:26:37 +09:00
|
|
|
}
|
|
|
|
|
2019-12-24 01:57:07 +09:00
|
|
|
function sampleFromArray<T>(array: T[], maximum: number): T[] {
|
2019-09-17 14:30:26 +09:00
|
|
|
return shuffleArray(array).slice(0, maximum);
|
2019-08-07 22:26:03 +09:00
|
|
|
}
|
|
|
|
|
2019-12-24 01:57:07 +09:00
|
|
|
function modifyWord(word: string): string {
|
2019-09-17 14:30:26 +09:00
|
|
|
return modifiers[Math.floor(Math.random() * modifiers.length)](word);
|
2019-08-03 21:51:12 +09:00
|
|
|
}
|
|
|
|
|
2019-12-24 01:57:07 +09:00
|
|
|
function fillArray<T>(array: T[], filler: string, maximum: number): T[] {
|
2019-09-17 14:30:26 +09:00
|
|
|
const deficit = maximum - array.length;
|
2019-08-07 22:26:03 +09:00
|
|
|
if (deficit > 0) {
|
2019-09-17 14:30:26 +09:00
|
|
|
array = [...array, ...Array(deficit).fill(filler)];
|
2019-08-07 22:26:03 +09:00
|
|
|
}
|
2019-09-17 14:30:26 +09:00
|
|
|
return array;
|
2019-08-03 21:51:12 +09:00
|
|
|
}
|
|
|
|
|
2019-12-24 01:57:07 +09:00
|
|
|
async function findSynonyms(word: string): Promise<string[]> {
|
2019-08-03 21:51:12 +09:00
|
|
|
try {
|
|
|
|
const response = await fetch(
|
|
|
|
`https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&dt=ss&ie=UTF-8&oe=UTF-8&dj=1&q=${encodeURIComponent(
|
2019-09-17 14:30:26 +09:00
|
|
|
word,
|
|
|
|
)}`,
|
|
|
|
);
|
2019-09-01 01:28:24 +09:00
|
|
|
const json: {
|
2019-09-17 14:30:26 +09:00
|
|
|
synsets: Array<{entry: Array<{synonym: string[]}>}>;
|
|
|
|
} = await response.json();
|
2019-09-01 01:28:24 +09:00
|
|
|
const synonyms = Array.from(
|
|
|
|
new Set<string>(
|
2019-08-07 22:26:03 +09:00
|
|
|
json.synsets.reduce(
|
2019-09-01 01:28:24 +09:00
|
|
|
(sum, synset) => [...sum, ...synset.entry.map((e) => e.synonym[0])],
|
2019-09-17 14:30:26 +09:00
|
|
|
[] as string[],
|
|
|
|
),
|
|
|
|
),
|
2019-12-24 01:57:07 +09:00
|
|
|
).filter((word) => !/[\s-]/.exec(word));
|
2019-09-17 14:30:26 +09:00
|
|
|
return synonyms;
|
2019-08-03 21:51:12 +09:00
|
|
|
} catch (err) {
|
2019-09-17 14:30:26 +09:00
|
|
|
return [];
|
2019-08-03 21:51:12 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-01 01:28:24 +09:00
|
|
|
const Suggestion: React.FC<{
|
2019-09-17 14:30:26 +09:00
|
|
|
query: string;
|
|
|
|
onSubmit: (name: string) => void;
|
|
|
|
}> = ({query, onSubmit}) => {
|
|
|
|
const {t} = useTranslation();
|
|
|
|
const synonymRef = useRef<string[]>([]);
|
|
|
|
const [bestWords, setBestWords] = useState<string[]>([]);
|
2019-08-07 22:26:03 +09:00
|
|
|
|
2019-12-24 01:57:07 +09:00
|
|
|
function shuffle(): void {
|
2019-08-07 22:26:03 +09:00
|
|
|
const best = fillArray(
|
|
|
|
sampleFromArray(synonymRef.current, maximumCount),
|
|
|
|
query,
|
2019-09-17 14:30:26 +09:00
|
|
|
maximumCount,
|
|
|
|
).map((word) => modifyWord(word));
|
|
|
|
setBestWords(best);
|
2019-08-07 22:26:03 +09:00
|
|
|
}
|
|
|
|
|
2019-12-24 01:57:07 +09:00
|
|
|
function applyQuery(name: string): void {
|
2019-09-17 14:30:26 +09:00
|
|
|
onSubmit(name);
|
2019-08-07 22:26:03 +09:00
|
|
|
}
|
2019-08-03 16:44:48 +09:00
|
|
|
|
2019-08-03 21:51:12 +09:00
|
|
|
useEffect(() => {
|
2019-12-24 01:57:07 +09:00
|
|
|
const fn = async (): Promise<void> => {
|
2019-08-03 21:51:12 +09:00
|
|
|
if (query && query.length > 0) {
|
2019-09-17 14:30:26 +09:00
|
|
|
const synonyms = await findSynonyms(query);
|
|
|
|
synonymRef.current = synonyms;
|
2019-08-07 22:26:03 +09:00
|
|
|
const best = fillArray(
|
|
|
|
sampleFromArray(synonyms, maximumCount),
|
|
|
|
query,
|
2019-09-17 14:30:26 +09:00
|
|
|
maximumCount,
|
|
|
|
).map((word) => modifyWord(word));
|
|
|
|
setBestWords(best);
|
2019-08-03 21:51:12 +09:00
|
|
|
}
|
2019-09-17 14:30:26 +09:00
|
|
|
};
|
|
|
|
fn();
|
|
|
|
}, [query]);
|
2019-08-03 16:44:48 +09:00
|
|
|
|
|
|
|
return (
|
|
|
|
<Container>
|
|
|
|
<Title>{t('try')}</Title>
|
|
|
|
<Items>
|
2019-08-07 22:26:03 +09:00
|
|
|
{bestWords &&
|
|
|
|
bestWords.map((name) => (
|
2019-12-24 01:57:07 +09:00
|
|
|
<Item key={name} onClick={(): void => applyQuery(name)}>
|
2019-08-03 21:51:12 +09:00
|
|
|
{name}
|
|
|
|
</Item>
|
|
|
|
))}
|
2019-08-07 22:26:03 +09:00
|
|
|
<Icon>
|
|
|
|
<TiArrowSync onClick={shuffle} />
|
|
|
|
</Icon>
|
2019-08-03 16:44:48 +09:00
|
|
|
</Items>
|
|
|
|
</Container>
|
2019-09-17 14:30:26 +09:00
|
|
|
);
|
|
|
|
};
|
2019-08-03 16:44:48 +09:00
|
|
|
|
2019-09-17 14:30:26 +09:00
|
|
|
export default Suggestion;
|
2019-09-01 01:28:24 +09:00
|
|
|
|
2019-08-03 16:44:48 +09:00
|
|
|
const Container = styled.div`
|
|
|
|
margin-bottom: 10px;
|
|
|
|
display: flex;
|
2019-08-07 22:26:03 +09:00
|
|
|
flex-direction: column;
|
|
|
|
align-items: center;
|
2019-08-03 16:44:48 +09:00
|
|
|
flex-wrap: wrap;
|
|
|
|
justify-content: center;
|
2019-09-17 14:30:26 +09:00
|
|
|
`;
|
2019-08-03 16:44:48 +09:00
|
|
|
|
|
|
|
const Title = styled.div`
|
|
|
|
margin-top: 15px;
|
2019-08-07 22:26:03 +09:00
|
|
|
padding: 5px 12px;
|
|
|
|
color: gray;
|
|
|
|
border: 1px solid gray;
|
|
|
|
border-radius: 2em;
|
2019-09-17 14:30:26 +09:00
|
|
|
`;
|
2019-08-03 16:44:48 +09:00
|
|
|
|
|
|
|
const Items = styled.div`
|
2019-08-07 22:26:03 +09:00
|
|
|
margin-top: 2px;
|
2019-08-03 16:44:48 +09:00
|
|
|
display: flex;
|
|
|
|
flex-direction: row;
|
2019-08-03 21:51:12 +09:00
|
|
|
flex-wrap: wrap;
|
|
|
|
justify-content: center;
|
2019-08-07 22:26:03 +09:00
|
|
|
|
|
|
|
${mobile} {
|
|
|
|
flex-direction: column;
|
|
|
|
align-items: center;
|
|
|
|
}
|
2019-09-17 14:30:26 +09:00
|
|
|
`;
|
2019-08-03 16:44:48 +09:00
|
|
|
|
|
|
|
const Item = styled.div`
|
2019-08-07 22:26:03 +09:00
|
|
|
margin-top: 8px;
|
|
|
|
margin-right: 14px;
|
2019-08-03 16:44:48 +09:00
|
|
|
cursor: pointer;
|
|
|
|
font-weight: bold;
|
2019-08-03 21:51:12 +09:00
|
|
|
font-family: monospace;
|
2019-08-03 22:26:37 +09:00
|
|
|
border-bottom: 1px dashed black;
|
2019-08-07 22:26:03 +09:00
|
|
|
color: black;
|
|
|
|
|
|
|
|
${mobile} {
|
|
|
|
margin-right: 0;
|
|
|
|
}
|
2019-09-17 14:30:26 +09:00
|
|
|
`;
|
2019-08-07 22:26:03 +09:00
|
|
|
|
|
|
|
const Icon = styled(Item)`
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
border-bottom: none;
|
2019-09-17 14:30:26 +09:00
|
|
|
`;
|