1
0
mirror of https://github.com/vbalien/voca.git synced 2025-12-06 11:26:21 +09:00

add: voca list

This commit is contained in:
2022-03-03 12:42:53 +09:00
parent 2579e69a85
commit e938c82fbe
5 changed files with 317 additions and 239 deletions

File diff suppressed because one or more lines are too long

View File

@@ -1,17 +1,28 @@
import { React } from "../deps.ts";
import { downloadVocaQuiz } from "../pdf.ts";
import useDownloadFormState from "../hooks/use-download-form-state.ts";
import { downloadVoca } from "../pdf.ts";
export default function DownloadForm() {
const [levels, setLevels] = React.useState<number[]>([6, 7]);
const [day, setDay] = React.useState<number>(1);
const {
levelState: [levels, setLevels],
dayState: [day, setDay],
save,
} = useDownloadFormState();
const [formEnable, setFormEnable] = React.useState<boolean>(true);
const handleSubmit: React.FormEventHandler = React.useCallback((ev) => {
localStorage.setItem("voca-setting", JSON.stringify({ levels, day }));
const handleDownload: React.FormEventHandler = React.useCallback((ev) => {
const button = ev.target as HTMLButtonElement;
save({ levels, day });
setFormEnable(false);
downloadVocaQuiz(levels.sort(), day).then(() => {
setFormEnable(true);
});
downloadVoca(
levels.sort(),
day,
button.id === "testDownload" ? "TEST" : "LIST",
).then(
() => {
setFormEnable(true);
},
);
ev.preventDefault();
}, [levels, day]);
@@ -31,86 +42,91 @@ export default function DownloadForm() {
}
};
React.useEffect(() => {
const setting = JSON.parse(localStorage.getItem("voca-setting") ?? "{}");
setting.level && setLevels(setting.levels);
setting.day && setDay(setting.day);
}, []);
return (
<>
<form onSubmit={handleSubmit}>
<fieldset disabled={!formEnable}>
<legend> </legend>
<div>
<input
type="checkbox"
id="10"
value="10"
checked={levels.find((val) => val === 10) !== undefined}
onChange={handleLevelChange}
/>
<label htmlFor="10"> </label>
</div>
<div>
<input
type="checkbox"
id="6"
value="6"
checked={levels.find((val) => val === 6) !== undefined}
onChange={handleLevelChange}
/>
<label htmlFor="6">(550+), (650+)</label>
</div>
<div>
<input
type="checkbox"
id="7"
value="7"
checked={levels.find((val) => val === 7) !== undefined}
onChange={handleLevelChange}
/>
<label htmlFor="7">(750+)</label>
</div>
<div>
<input
type="checkbox"
id="8"
value="8"
checked={levels.find((val) => val === 8) !== undefined}
onChange={handleLevelChange}
/>
<label htmlFor="8">(850+)</label>
</div>
<div>
<input
type="checkbox"
id="9"
value="9"
checked={levels.find((val) => val === 9) !== undefined}
onChange={handleLevelChange}
/>
<label htmlFor="9">(900+)</label>
</div>
</fieldset>
<fieldset disabled={!formEnable}>
<legend> </legend>
<div>
<label htmlFor="day"></label>
<select
id="day"
value={day}
onChange={handleDayChange}
disabled={!formEnable}
>
{[...Array(30)].map((_, i) => <option value={i + 1}>{i + 1}
</option>)}
</select>
<input
type="checkbox"
id="10"
value="10"
checked={levels.find((val) => val === 10) !== undefined}
onChange={handleLevelChange}
/>
<label htmlFor="10"> </label>
</div>
<div>
<button type="submit" disabled={!formEnable}>PDF </button>
<input
type="checkbox"
id="6"
value="6"
checked={levels.find((val) => val === 6) !== undefined}
onChange={handleLevelChange}
/>
<label htmlFor="6">(550+), (650+)</label>
</div>
</form>
<div>
<input
type="checkbox"
id="7"
value="7"
checked={levels.find((val) => val === 7) !== undefined}
onChange={handleLevelChange}
/>
<label htmlFor="7">(750+)</label>
</div>
<div>
<input
type="checkbox"
id="8"
value="8"
checked={levels.find((val) => val === 8) !== undefined}
onChange={handleLevelChange}
/>
<label htmlFor="8">(850+)</label>
</div>
<div>
<input
type="checkbox"
id="9"
value="9"
checked={levels.find((val) => val === 9) !== undefined}
onChange={handleLevelChange}
/>
<label htmlFor="9">(900+)</label>
</div>
</fieldset>
<div>
<label htmlFor="day"></label>
<select
id="day"
value={day}
onChange={handleDayChange}
disabled={!formEnable}
>
{[...Array(30)].map((_, i) => <option value={i + 1}>{i + 1}</option>)}
</select>
</div>
<div>
<button
id="testDownload"
disabled={!formEnable}
onClick={handleDownload}
>
</button>
<button
id="listDownload"
disabled={!formEnable}
onClick={handleDownload}
>
</button>
</div>
</>
);
}

View File

@@ -1,5 +1,6 @@
export {
PDFDocument,
PDFFont,
PDFName,
PDFPage,
PDFRef,

View File

@@ -0,0 +1,24 @@
import { React } from "../deps.ts";
const VOCA_SETTING_KEY = "voca-setting";
export default function useDownloadFormState() {
const [levels, setLevels] = React.useState<number[]>([6, 7]);
const [day, setDay] = React.useState<number>(1);
React.useEffect(() => {
const setting = JSON.parse(localStorage.getItem(VOCA_SETTING_KEY) ?? "{}");
setting.level && setLevels(setting.levels);
setting.day && setDay(setting.day);
}, []);
const save = React.useCallback(({ levels, day }) => {
localStorage.setItem(VOCA_SETTING_KEY, JSON.stringify({ levels, day }));
}, []);
return {
levelState: [levels, setLevels] as const,
dayState: [day, setDay] as const,
save,
};
}

View File

@@ -1,6 +1,7 @@
import {
fontkit,
PDFDocument,
PDFFont,
PDFName,
PDFPage,
PDFRef,
@@ -25,17 +26,97 @@ function downloadBuffer(buffer: Uint8Array, fileName: string) {
});
}
export async function makePdf(voca_list: Voca[]) {
type DrawWordOption = {
page: PDFPage;
font: PDFFont;
pdfUrlList: PDFRef[];
num: number;
voca: Voca;
x: number;
y: number;
blind: boolean;
};
function drawWord(
option: DrawWordOption,
) {
const {
page,
font,
pdfUrlList,
num,
voca,
x,
y,
blind,
} = option;
const fontSize = 12;
const margin = 20;
const text = `${num}. ${voca.word}`;
const textWidth = font.widthOfTextAtSize(text, fontSize);
const textHeight = font.heightAtSize(fontSize);
const textX = margin + (page.getWidth() / 2) * x;
const textY = page.getHeight() - (fontSize + margin) * y;
page.drawText(text, {
x: textX,
y: textY,
size: fontSize,
font,
color: rgb(0, 0, 0),
});
page.drawText("_________________", {
x: margin + (page.getWidth() / 2) * x + 140,
y: page.getHeight() - (fontSize + margin) * y - 3,
size: fontSize,
font,
color: rgb(0, 0, 0),
});
const pdfUrlDict = page.doc.context.obj({
Type: "Annot",
Subtype: "Link",
Rect: [textX, textY, textX + textWidth, textY + textHeight],
A: {
Type: "Action",
S: "URI",
URI: PDFString.of(
`https://en.dict.naver.com/#/search?range=all&query=${
encodeURIComponent(voca.word)
}`,
),
},
});
const pdfUrl = page.doc.context.register(pdfUrlDict);
pdfUrlList.push(pdfUrl);
if (!blind) {
page.drawText(voca.answer, {
x: margin + (page.getWidth() / 2) * x + 140,
y: page.getHeight() - (fontSize + margin) * y,
size: 8,
font,
color: rgb(1, 0, 0),
});
}
}
export async function makePdf(
voca_list: Voca[],
type: "TEST" | "LIST",
) {
const pdfDoc = await PDFDocument.create();
if (type === "LIST") {
voca_list.sort(wordCompare);
} else {
voca_list = shuffle(voca_list);
}
const fontBytes = await fetch("./NanumGothic.ttf").then((res) =>
res.arrayBuffer()
);
pdfDoc.registerFontkit(fontkit);
const customFont = await pdfDoc.embedFont(fontBytes);
const fontSize = 12;
const margin = 20;
const perColumn = 25;
const perPage = 50;
@@ -45,61 +126,36 @@ export async function makePdf(voca_list: Voca[]) {
let pdfUrlList: PDFRef[] = [];
// 문제 생성
for (let i = 0; i < voca_list.length; ++i) {
const data = voca_list[i];
if (i % perPage === 0) {
if (page) {
page.node.set(PDFName.of("Annots"), pdfDoc.context.obj(pdfUrlList));
if (type === "TEST") {
for (let i = 0; i < voca_list.length; ++i) {
const data = voca_list[i];
if (i % perPage === 0) {
if (page) {
page.node.set(PDFName.of("Annots"), pdfDoc.context.obj(pdfUrlList));
}
pdfUrlList = [];
page = pdfDoc.addPage();
yCursor = 1;
col = -1;
}
if (i % perColumn === 0) {
col++;
yCursor = 1;
}
pdfUrlList = [];
page = pdfDoc.addPage();
yCursor = 1;
col = -1;
drawWord({
page,
font: customFont,
pdfUrlList,
num: i + 1,
voca: data,
x: col,
y: yCursor,
blind: true,
});
yCursor++;
}
if (i % perColumn === 0) {
col++;
yCursor = 1;
}
const text = `${i + 1}. ${data.word}`;
const textWidth = customFont.widthOfTextAtSize(text, fontSize);
const textHeight = customFont.heightAtSize(fontSize);
const textX = margin + (page.getWidth() / 2) * col;
const textY = page.getHeight() - (fontSize + margin) * yCursor;
const pdfUrlDict = pdfDoc.context.obj({
Type: "Annot",
Subtype: "Link",
Rect: [textX, textY, textX + textWidth, textY + textHeight],
A: {
Type: "Action",
S: "URI",
URI: PDFString.of(
`https://en.dict.naver.com/#/search?range=all&query=${
encodeURIComponent(data.word)
}`,
),
},
});
const pdfUrl = pdfDoc.context.register(pdfUrlDict);
pdfUrlList.push(pdfUrl);
page.drawText(text, {
x: textX,
y: textY,
size: fontSize,
font: customFont,
color: rgb(0, 0, 0),
});
page.drawText("_________________", {
x: margin + (page.getWidth() / 2) * col + 140,
y: page.getHeight() - (fontSize + margin) * yCursor - 3,
size: fontSize,
font: customFont,
color: rgb(0, 0, 0),
});
yCursor++;
}
// 답 생성
@@ -119,49 +175,15 @@ export async function makePdf(voca_list: Voca[]) {
yCursor = 1;
}
const text = `${i + 1}. ${data.word}`;
const textWidth = customFont.widthOfTextAtSize(text, fontSize);
const textHeight = customFont.heightAtSize(fontSize);
const textX = margin + (page.getWidth() / 2) * col;
const textY = page.getHeight() - (fontSize + margin) * yCursor;
const pdfUrlDict = pdfDoc.context.obj({
Type: "Annot",
Subtype: "Link",
Rect: [textX, textY, textX + textWidth, textY + textHeight],
A: {
Type: "Action",
S: "URI",
URI: PDFString.of(
`https://en.dict.naver.com/#/search?range=all&query=${
encodeURIComponent(data.word)
}`,
),
},
});
const pdfUrl = pdfDoc.context.register(pdfUrlDict);
pdfUrlList.push(pdfUrl);
page.drawText(text, {
x: textX,
y: textY,
size: fontSize,
drawWord({
page,
font: customFont,
color: rgb(0, 0, 0),
});
page.drawText("_________________", {
x: margin + (page.getWidth() / 2) * col + 140,
y: page.getHeight() - (fontSize + margin) * yCursor - 3,
size: fontSize,
font: customFont,
color: rgb(0, 0, 0),
});
page.drawText(data.answer, {
x: margin + (page.getWidth() / 2) * col + 140,
y: page.getHeight() - (fontSize + margin) * yCursor,
size: 8,
font: customFont,
color: rgb(1, 0, 0),
pdfUrlList,
num: i + 1,
voca: data,
x: col,
y: yCursor,
blind: false,
});
yCursor++;
}
@@ -170,15 +192,30 @@ export async function makePdf(voca_list: Voca[]) {
return pdfBytes;
}
export async function downloadVocaQuiz(levels: number[], day: number) {
const wordCompare = (a: Voca, b: Voca) => {
const [wordA, wordB] = [a.word.toUpperCase(), b.word.toUpperCase()];
if (wordA < wordB) {
return -1;
}
if (wordA > wordB) {
return 1;
}
return 0;
};
export async function downloadVoca(
levels: number[],
day: number,
type: "TEST" | "LIST",
) {
if (levels.includes(10)) levels = [6, 7, 8, 9];
const json: JSONType = await (await fetch("/voca.json")).json();
const voca_list = json.data.filter((chunk) =>
levels.includes(chunk.level) && chunk.day === day
).flatMap((chunk) => chunk.voca_list);
const bufferPdf = await makePdf(shuffle(voca_list));
const bufferPdf = await makePdf(shuffle(voca_list), type);
await downloadBuffer(
bufferPdf,
`voca-day${day}-level(${levels.join(",")}).pdf`,
`voca-${type.toLowerCase()}-day${day}-level(${levels.join(",")}).pdf`,
);
}