1
0
mirror of https://github.com/uetchy/namae.git synced 2025-07-01 14:00:03 +09:00
namae/web/src/components/Suggestion.tsx

293 lines
9.0 KiB
TypeScript
Raw Normal View History

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
2020-02-06 00:32:05 +09:00
import {capitalize, stem, germanify, njoin, lower, upper} from '../util/text';
import {sampleFromArray, fillArray} from '../util/array';
2019-09-17 14:30:26 +09:00
import {mobile} from '../util/css';
2020-02-11 17:57:10 +09:00
import {sanitize} from '../util/text';
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-09-17 14:30:26 +09:00
const maximumCount = 3;
2019-09-01 01:28:24 +09:00
const modifiers: Modifier[] = [
2020-02-06 00:06:04 +09:00
(word): string => `${capitalize(germanify(word))}`,
(word): string => `${capitalize(stem(word))}able`,
2020-02-06 00:32:05 +09:00
(word): string => `${capitalize(stem(word))}al`,
2020-02-06 00:06:04 +09:00
(word): string => `${capitalize(stem(word))}el`,
(word): string => `${capitalize(stem(word))}em`,
(word): string => `${capitalize(stem(word))}en`,
(word): string => `${capitalize(stem(word))}er`,
(word): string => `${capitalize(stem(word))}ery`,
(word): string => `${capitalize(stem(word))}ia`,
(word): string => `${capitalize(stem(word))}ible`,
2020-02-06 00:32:05 +09:00
(word): string => `${capitalize(stem(word))}ics`,
2020-02-06 00:06:04 +09:00
(word): string => `${capitalize(stem(word))}ifier`,
(word): string => `${capitalize(stem(word))}ify`,
(word): string => `${capitalize(stem(word))}ii`,
(word): string => `${capitalize(stem(word))}ist`,
(word): string => `${capitalize(stem(word))}in`,
(word): string => `${capitalize(stem(word))}io`,
2020-02-06 00:32:05 +09:00
(word): string => `${capitalize(stem(word))}iverse`,
2020-02-06 00:06:04 +09:00
(word): string => `${capitalize(stem(word))}ium`,
2020-02-06 00:32:05 +09:00
(word): string => `${capitalize(stem(word))}um`,
(word): string => `${capitalize(stem(word))}ory`,
2020-02-06 00:06:04 +09:00
(word): string => njoin(capitalize(stem(word)), 'y'),
2020-02-05 21:25:00 +09:00
(word): string => `${capitalize(word)}`,
(word): string => `${capitalize(word)}AI`,
(word): string => `${capitalize(word)}API`,
(word): string => `${capitalize(word)}base`,
(word): string => `${capitalize(word)}book`,
(word): string => `${capitalize(word)}Bot`,
(word): string => `${capitalize(word)}butler`,
(word): string => `${capitalize(word)}cast`,
2020-02-06 00:32:05 +09:00
(word): string => `${capitalize(word)}CDN`,
2020-02-05 21:25:00 +09:00
(word): string => `${capitalize(word)}Club`,
(word): string => `${capitalize(word)}DB`,
(word): string => `${capitalize(word)}feed`,
(word): string => `${capitalize(word)}Finder`,
(word): string => `${capitalize(word)}flow`,
(word): string => `${capitalize(word)}form`,
2020-02-06 00:06:04 +09:00
(word): string => njoin(capitalize(word), 'ful'),
2020-02-05 21:25:00 +09:00
(word): string => `${capitalize(word)}Go`,
(word): string => `${capitalize(word)}gram`,
(word): string => `${capitalize(word)}Hero`,
(word): string => `${capitalize(word)}Hub`,
(word): string => `${capitalize(word)}Hunt`,
(word): string => `${capitalize(word)}It`,
2019-12-24 01:57:07 +09:00
(word): string => `${capitalize(word)}Kit`,
2020-02-05 21:25:00 +09:00
(word): string => `${capitalize(word)}Lab`,
2020-02-06 00:06:04 +09:00
(word): string => njoin(capitalize(word), 'let'),
2020-02-05 21:25:00 +09:00
(word): string => `${capitalize(word)}Link`,
(word): string => `${capitalize(word)}list`,
(word): string => `${capitalize(word)}mind`,
2020-02-06 00:06:04 +09:00
(word): string => njoin(capitalize(word), 'note'),
2020-02-05 21:25:00 +09:00
(word): string => `${capitalize(word)}Notes`,
(word): string => `${capitalize(word)}Pod`,
(word): string => `${capitalize(word)}Pro`,
(word): string => `${capitalize(word)}Scan`,
2020-02-06 00:06:04 +09:00
(word): string => njoin(capitalize(word), 'shot'),
(word): string => njoin(capitalize(word), 'space'),
2020-02-05 21:25:00 +09:00
(word): string => `${capitalize(word)}Stack`,
(word): string => `${capitalize(word)}Studio`,
2020-02-06 00:06:04 +09:00
(word): string => njoin(capitalize(word), 'time'),
(word): string => njoin(capitalize(word), 'way'),
2020-02-05 21:25:00 +09:00
(word): string => `${capitalize(word)}x`,
2020-02-06 00:06:04 +09:00
(word): string => `${lower(word)}-IO`,
2020-02-05 21:25:00 +09:00
(word): string => `${lower(word)}check`,
(word): string => `${lower(word)}lint`,
(word): string => `${lower(word)}ly`,
(word): string => `Air${capitalize(word)}`,
(word): string => `All${capitalize(word)}`,
2019-12-24 01:57:07 +09:00
(word): string => `Cloud${capitalize(word)}`,
2020-02-05 21:25:00 +09:00
(word): string => `Co${lower(word)}`,
(word): string => `Deep${capitalize(word)}`,
2020-02-06 00:32:05 +09:00
(word): string => `Easy${capitalize(word)}`,
2020-02-06 00:06:04 +09:00
(word): string => `Fast${lower(word)}`,
2020-02-06 00:32:05 +09:00
(word): string => `Fusion${capitalize(word)}`,
2020-02-05 21:25:00 +09:00
(word): string => `Git${capitalize(word)}`,
2019-12-24 01:57:07 +09:00
(word): string => `Go${capitalize(word)}`,
2020-02-05 21:25:00 +09:00
(word): string => `Hyper${capitalize(word)}`,
2019-12-24 01:57:07 +09:00
(word): string => `In${capitalize(word)}`,
2020-02-05 21:25:00 +09:00
(word): string => `Insta${lower(word)}`,
(word): string => `Lead${lower(word)}`,
(word): string => `lib${lower(word)}`,
2020-02-06 00:06:04 +09:00
(word): string => `Max${upper(word)}`,
2020-02-05 21:25:00 +09:00
(word): string => `Micro${lower(word)}`,
2020-02-06 00:32:05 +09:00
(word): string => `Native${capitalize(word)}`,
2020-02-05 21:25:00 +09:00
(word): string => `nano${lower(word)}`,
2020-02-06 00:06:04 +09:00
(word): string => `No${upper(word)}`,
2020-02-05 21:25:00 +09:00
(word): string => `Omni${capitalize(word)}`,
(word): string => `One${capitalize(word)}`,
(word): string => `Open${capitalize(word)}`,
2020-02-06 00:32:05 +09:00
(word): string => njoin('Octo', capitalize(word)),
2020-02-05 21:25:00 +09:00
(word): string => `Pro${capitalize(word)}`,
(word): string => `quick${lower(word)}`,
(word): string => `Smart${capitalize(word)}`,
2020-02-06 00:32:05 +09:00
(word): string => `Snap${capitalize(word)}`,
(word): string => `Super${lower(word)}`,
(word): string => `Semantic${capitalize(word)}`,
2020-02-05 21:25:00 +09:00
(word): string => `Up${lower(word)}`,
2020-02-06 13:16:30 +09:00
(word): string => `Un${lower(word)}`,
2020-02-06 00:06:04 +09:00
(word): string => `Wunder${lower(germanify(word))}`,
2020-02-05 21:25:00 +09:00
(word): string => `Zen${capitalize(word)}`,
2020-02-06 00:06:04 +09:00
(word): string => njoin('Many', lower(word)),
(word): string => njoin('mini', lower(word)),
(word): string => njoin('Mono', lower(word)),
(word): string => njoin('Next', lower(word)),
(word): string => njoin('Uni', lower(word)),
(word): string => njoin(lower(word), 'joy'),
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 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
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[],
),
),
2020-02-11 17:57:10 +09:00
)
.filter((word) => !/[\s-]/.exec(word))
.map((word) => sanitize(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(() => {
2020-02-05 15:59:53 +09:00
let isEffective = true;
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);
2020-02-05 15:59:53 +09:00
if (!isEffective) {
return;
}
2019-09-17 14:30:26 +09:00
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();
2020-02-05 15:59:53 +09:00
return () => {
isEffective = false;
};
2019-09-17 14:30:26 +09:00
}, [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 &&
2020-02-05 15:59:53 +09:00
bestWords.map((name, i) => (
<Item key={name + i} onClick={(): void => applyQuery(name)}>
2019-08-03 21:51:12 +09:00
{name}
</Item>
))}
2019-08-03 16:44:48 +09:00
</Items>
2020-02-05 20:33:24 +09:00
<Icon onClick={shuffle}>
<TiArrowSync />
2020-02-05 15:59:53 +09:00
</Icon>
2019-08-03 16:44:48 +09:00
</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`
2020-02-08 19:05:33 +09:00
margin-top: 20px;
2019-08-03 16:44:48 +09:00
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;
2020-02-08 19:05:33 +09:00
${mobile} {
margin-top: 15px;
}
2019-09-17 14:30:26 +09:00
`;
2019-08-03 16:44:48 +09:00
const Title = styled.div`
2020-02-06 17:21:22 +09:00
padding: 0 10px;
2019-08-07 22:26:03 +09:00
color: gray;
border: 1px solid gray;
border-radius: 2em;
2020-02-06 03:39:06 +09:00
text-transform: uppercase;
font-size: 12px;
user-select: none;
2019-09-17 14:30:26 +09:00
`;
2019-08-03 16:44:48 +09:00
const Items = styled.div`
2020-02-06 17:21:22 +09:00
margin: 5px 0;
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`
2020-02-06 17:21:22 +09:00
margin-top: 10px;
2020-02-08 19:05:33 +09:00
margin: 10px 12px 0;
2019-08-03 16:44:48 +09:00
cursor: pointer;
font-weight: bold;
2019-08-03 21:51:12 +09:00
font-family: monospace;
2020-02-06 13:16:30 +09:00
font-size: 1.5rem;
2020-02-06 17:21:22 +09:00
line-height: 1em;
2019-08-03 22:26:37 +09:00
border-bottom: 1px dashed black;
2019-08-07 22:26:03 +09:00
color: black;
${mobile} {
2020-02-08 19:05:33 +09:00
margin: 10px 0 0;
2020-02-06 13:16:30 +09:00
font-size: 1.3rem;
2019-08-07 22:26:03 +09:00
}
2019-09-17 14:30:26 +09:00
`;
2019-08-07 22:26:03 +09:00
const Icon = styled(Item)`
2020-02-05 20:33:24 +09:00
margin: 15px 0 0 0;
padding: 8px 9px;
2019-08-07 22:26:03 +09:00
display: flex;
align-items: center;
border-bottom: none;
2020-02-05 20:33:24 +09:00
color: white;
border-radius: 4px;
font-size: 1.3rem;
user-select: none;
2020-02-05 20:47:03 +09:00
background: #5610ff;
2020-02-05 20:33:24 +09:00
transition: background 0.1s ease-out;
&:hover {
2020-02-05 20:47:03 +09:00
background: #723df3;
2020-02-05 20:33:24 +09:00
}
&:active {
2020-02-05 20:47:03 +09:00
background: #a17ff5;
2020-02-05 20:33:24 +09:00
}
2019-09-17 14:30:26 +09:00
`;