2020-11-20 20:57:24 +01:00
|
|
|
const responseDiv = document.getElementById("response");
|
|
|
|
const clearButton = document.getElementById("clear");
|
|
|
|
const canvas = document.getElementById("canvas");
|
2020-11-21 04:12:00 +01:00
|
|
|
const toggleButtons = Array.from(document.getElementById('toggleButtons').children);
|
2020-11-20 20:57:24 +01:00
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
/* API */
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
const MAX_RESULTS = 10;
|
|
|
|
const API = 'https://inputtools.google.com/request?itc=ja-t-i0-handwrit&app=translate';
|
|
|
|
const DEVICE = navigator.userAgent
|
|
|
|
|
|
|
|
const generateRequestData = strokes => ({
|
|
|
|
app_version: 0.4,
|
|
|
|
api_level: '537.36',
|
|
|
|
device: DEVICE,
|
|
|
|
input_type: 0,
|
|
|
|
options: 'enable_pre_space',
|
|
|
|
requests: [
|
|
|
|
{
|
|
|
|
writing_guide: {
|
|
|
|
writing_area_width: canvas.width,
|
|
|
|
writing_area_height: canvas.height
|
|
|
|
},
|
|
|
|
pre_context: '',
|
|
|
|
max_num_results: MAX_RESULTS,
|
|
|
|
max_completions: 0,
|
|
|
|
language: 'ja',
|
|
|
|
ink: strokes
|
|
|
|
}
|
|
|
|
]
|
|
|
|
});
|
|
|
|
|
|
|
|
const sendKanjiRequest = async (strokes) => fetch(API, {
|
|
|
|
headers: { "content-type": "application/json; charset=UTF-8" },
|
|
|
|
body: JSON.stringify(generateRequestData(strokes)),
|
|
|
|
method: "POST"
|
|
|
|
})
|
|
|
|
.then(data => data.json())
|
|
|
|
.then(res => {
|
|
|
|
if (res[0] != "SUCCESS") throw Error(res[0]);
|
2020-11-21 04:12:00 +01:00
|
|
|
return res;
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const updateResponses = async (strokes) =>
|
|
|
|
sendKanjiRequest(strokes)
|
|
|
|
.then(res => responseDiv.innerText = postProcess(res[1][0][1]).join(', '))
|
|
|
|
.catch(err => console.error(err));
|
2020-11-20 20:57:24 +01:00
|
|
|
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
/* CANVAS */
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
const MILLISECONDS_PER_POINT = 20;
|
|
|
|
|
|
|
|
canvas.height = 500;
|
|
|
|
canvas.width = 500;
|
|
|
|
|
|
|
|
ctx.strokeStyle = 'black';
|
|
|
|
ctx.lineWidth = 2;
|
|
|
|
ctx.lineCap = 'round';
|
|
|
|
ctx.lineJoin = 'round';
|
|
|
|
|
|
|
|
let strokes = [];
|
|
|
|
|
|
|
|
let newStroke = {};
|
|
|
|
let mouseDown = false;
|
|
|
|
let drawingTimeout = -1;
|
|
|
|
let timerStart = 0;
|
|
|
|
let x = 0;
|
|
|
|
let y = 0;
|
|
|
|
|
2020-11-21 04:12:00 +01:00
|
|
|
const resetNewStroke = () => {
|
2020-11-20 20:57:24 +01:00
|
|
|
newStroke = {
|
|
|
|
'xs': [],
|
|
|
|
'ys': [],
|
|
|
|
'times': [],
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-11-21 04:12:00 +01:00
|
|
|
resetNewStroke();
|
2020-11-20 20:57:24 +01:00
|
|
|
|
|
|
|
const addPointToNewStroke = (x, y) => {
|
|
|
|
newStroke.xs.push(x);
|
|
|
|
newStroke.ys.push(y);
|
|
|
|
newStroke.times.push(Date.now() - timerStart);
|
|
|
|
}
|
|
|
|
|
|
|
|
const drawLine = (dx, dy) => {
|
|
|
|
ctx.moveTo(x, y);
|
|
|
|
ctx.lineTo(dx, dy);
|
|
|
|
ctx.stroke();
|
|
|
|
}
|
|
|
|
|
2020-11-21 04:12:00 +01:00
|
|
|
const updateStroke = (newX, newY) => {
|
2020-11-20 20:57:24 +01:00
|
|
|
drawLine(newX, newY);
|
|
|
|
addPointToNewStroke(newX, newY);
|
|
|
|
x = newX;
|
|
|
|
y = newY;
|
|
|
|
drawingTimeout = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
const initStroke = (initX, initY) => {
|
2020-11-21 04:12:00 +01:00
|
|
|
resetNewStroke();
|
2020-11-20 20:57:24 +01:00
|
|
|
timerStart = Date.now();
|
|
|
|
x = initX;
|
|
|
|
y = initY;
|
|
|
|
mouseDown = true;
|
|
|
|
ctx.beginPath();
|
|
|
|
}
|
|
|
|
|
|
|
|
const updateStrokeIfPrevLineIsDone = (newX, newY) => {
|
|
|
|
if (mouseDown === true && drawingTimeout === -1)
|
|
|
|
drawingTimeout = setTimeout(
|
|
|
|
() => updateStroke(newX, newY),
|
|
|
|
MILLISECONDS_PER_POINT
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const endStroke = () => {
|
|
|
|
if (!(drawingTimeout === -1)) {
|
|
|
|
clearTimeout(drawingTimeout);
|
|
|
|
drawingTimeout = -1;
|
|
|
|
}
|
|
|
|
mouseDown = false;
|
|
|
|
ctx.closePath();
|
|
|
|
strokes.push([newStroke.xs, newStroke.ys, newStroke.times]);
|
2020-11-21 04:12:00 +01:00
|
|
|
updateResponses(strokes);
|
2020-11-20 20:57:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const clearBoard = () => {
|
|
|
|
strokes = [];
|
|
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
|
|
responseDiv.innerHTML = 'None';
|
|
|
|
}
|
|
|
|
|
2020-11-21 04:12:00 +01:00
|
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
/* POST-PROCESSING AND FILTERING */
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
const ON_COLOR = '#4d9e2a';
|
|
|
|
const OFF_COLOR = '#fa2b2b';
|
|
|
|
|
|
|
|
const regexState = {
|
|
|
|
state: {
|
|
|
|
kanji: true,
|
|
|
|
hiragana: true,
|
|
|
|
katakana: true,
|
|
|
|
all: false
|
|
|
|
},
|
|
|
|
regex: {
|
|
|
|
kanji: RegExp(/\p{Script_Extensions=Han}/u),
|
|
|
|
hiragana: RegExp(/\p{Script_Extensions=Hiragana}/u),
|
|
|
|
katakana: RegExp(/\p{Script_Extensions=Katakana}/u),
|
|
|
|
all: RegExp(/./)
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const genCharSet = id => id.slice(6).toLowerCase();
|
|
|
|
|
|
|
|
const colorButton = id => {
|
|
|
|
const button = document.getElementById(id);
|
|
|
|
button.style.backgroundColor =
|
|
|
|
regexState.state[genCharSet(id)] ? ON_COLOR : OFF_COLOR;
|
|
|
|
}
|
|
|
|
|
|
|
|
const toggleRegexState = event => {
|
|
|
|
const characterSet = genCharSet(event.target.id);
|
2020-11-20 20:57:24 +01:00
|
|
|
|
2020-11-21 04:12:00 +01:00
|
|
|
regexState.state[characterSet] = !regexState.state[characterSet];
|
|
|
|
colorButton(event.target.id);
|
|
|
|
if (strokes.length !== 0) updateResponses(strokes);
|
|
|
|
}
|
|
|
|
|
|
|
|
const genCombinedRegexString = () => '^[' +
|
|
|
|
Object.keys(regexState.regex)
|
|
|
|
.filter(key => regexState.state[key])
|
|
|
|
.map(key => regexState.regex[key].source)
|
|
|
|
.join('') + ']*$';
|
|
|
|
|
|
|
|
const postProcess = results => {
|
|
|
|
const regexString = regexState.state.all
|
|
|
|
? '.*'
|
|
|
|
: genCombinedRegexString();
|
|
|
|
|
|
|
|
const combinedRegex = RegExp(`${regexString}`, 'u');
|
|
|
|
return results.filter(result => combinedRegex.test(result));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
/* EVENT LISTENERS AND SETUP */
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
// Add eventlisteners and colors to all toggle-buttons
|
|
|
|
toggleButtons
|
|
|
|
.map(button => {
|
|
|
|
button.addEventListener('click', toggleRegexState);
|
|
|
|
colorButton(button.id);
|
|
|
|
});
|
|
|
|
|
|
|
|
canvas.onmousedown = event => initStroke(event.offsetX, event.offsetY);
|
|
|
|
canvas.onmousemove = event => updateStrokeIfPrevLineIsDone(event.offsetX, event.offsetY);
|
|
|
|
canvas.onmouseup = () => endStroke();
|
|
|
|
clearButton.onclick = () => clearBoard();
|
|
|
|
|
|
|
|
// Touch support
|
|
|
|
|
|
|
|
const correctTouchOffset = (x, y) => {
|
2020-11-20 20:57:24 +01:00
|
|
|
const canvasRect = canvas.getBoundingClientRect();
|
|
|
|
return [x - canvasRect.left, y - canvasRect.top];
|
|
|
|
}
|
|
|
|
|
|
|
|
canvas.ontouchstart = event => {
|
|
|
|
event.preventDefault();
|
|
|
|
({clientX, clientY} = event.changedTouches[0]);
|
2020-11-21 04:12:00 +01:00
|
|
|
[x, y] = correctTouchOffset(clientX, clientY);
|
2020-11-20 20:57:24 +01:00
|
|
|
initStroke(x, y);
|
|
|
|
}
|
|
|
|
|
|
|
|
canvas.ontouchmove = event => {
|
|
|
|
event.preventDefault();
|
|
|
|
({clientX, clientY} = event.changedTouches[0]);
|
2020-11-21 04:12:00 +01:00
|
|
|
[x, y] = correctTouchOffset(clientX, clientY);
|
2020-11-20 20:57:24 +01:00
|
|
|
updateStrokeIfPrevLineIsDone(x, y);
|
|
|
|
}
|
|
|
|
|
|
|
|
canvas.ontouchend = () => endStroke();
|