~gbrlsnchs/kana-guru

3100ff2dcdaf92f7a8bce22dc9f157dbcaf28536 — Gabriel Sanches 1 year, 7 months ago 264d734 v0.1.0
Improve several elements altogether

Items improved:

	- Better code organization
	- Correctly sync files while watching
	- Fixed UI glitches
	- Adjusted some colors
	- Some refactoring
	- Added favicon
14 files changed, 118 insertions(+), 67 deletions(-)

M build.zig
M deno.json
M scripts/build.ts
A server/assets/favicon.svg
A server/assets/index.html
A server/assets/kana.min.js
A server/assets/lib/kana.wasm
A server/assets/style.min.css
A server/main.ts
R src/site/index.html => site/index.html
R src/site/kana.js => site/kana.js
R src/kana.zig => site/kana.zig
R src/site/style.css => site/style.css
D src/server.ts
M build.zig => build.zig +1 -1
@@ 5,7 5,7 @@ pub fn build(b: *std.build.Builder) void {
    const mode = b.standardReleaseOptions();
    const target = zig.CrossTarget.parse(.{ .arch_os_abi = "wasm32-freestanding" }) catch unreachable;

    const lib = b.addSharedLibrary("kana", "src/kana.zig", .unversioned);
    const lib = b.addSharedLibrary("kana", "site/kana.zig", .unversioned);
    lib.setBuildMode(mode);
    lib.setTarget(target);
    lib.addPackagePath("kana", "deps/kana/src/lib.zig");

M deno.json => deno.json +4 -2
@@ 7,7 7,9 @@
	},
	"tasks": {
		"build": "deno run --allow-read --allow-write --allow-run ./scripts/build.ts",
		"serve": "deno run --allow-read --allow-net=:8000 src/server.ts",
		"prepare": "deno task build && mkdir -p dist && cp -r www src/server.ts dist/"
		"build-watch": "deno run --allow-read --allow-write --allow-run --watch=./site ./scripts/build.ts",
		"serve": "deno run --allow-read --allow-net=:8000 main.ts",
		"serve-watch": "deno run --allow-read --allow-net=:8000 --watch=/tmp/kana-guru-sync main.ts",
		"watch": "deno task build-watch & deno task --cwd server serve-watch"
	}
}

M scripts/build.ts => scripts/build.ts +9 -5
@@ 1,14 1,17 @@
import { Language, minify } from "https://deno.land/x/minifier@v1.1.1/mod.ts";

await Deno.mkdir("www", { recursive: true });
const encoder = new TextEncoder();
const now = new Date();

await Deno.mkdir("server/assets", { recursive: true });

async function minifyFile(lang: Language, src: string, dst?: string): Promise<void> {
	const encoder = new TextEncoder();
	const decoder = new TextDecoder();
	const bytes = await Deno.readFile(`src/site/${src}`);
	const bytes = await Deno.readFile(`site/${src}`);
	const minified = minify(lang, decoder.decode(bytes));

	return Deno.writeFile(`www/${dst || src}`, encoder.encode(minified));
	return Deno.writeFile(`server/assets/${dst || src}`, encoder.encode(minified));
}

const tasks = [


@@ 16,11 19,12 @@ const tasks = [
	minifyFile(Language.CSS, "style.css", "style.min.css"),
	minifyFile(Language.JS, "kana.js", "kana.min.js"),
	Deno.run({
		cmd: ["zig", "build", "-Drelease-fast", "--prefix", "www"],
		cmd: ["zig", "build", "-Drelease-fast", "--prefix", "assets"],
		env: {
			"DESTDIR": "",
			"DESTDIR": "server",
		},
	}).status(),
];

await Promise.all(tasks);
await Deno.writeFile("/tmp/kana-guru-sync", encoder.encode(now.toString()), { mode: 0o777 });

A server/assets/favicon.svg => server/assets/favicon.svg +36 -0
@@ 0,0 1,36 @@
<?xml version="1.0" encoding="windows-1252"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<circle style="fill:#F0F0F0;" cx="256" cy="256" r="256"/>
<circle style="fill:#D80027;" cx="256" cy="256" r="111.304"/>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>
\ No newline at end of file

A server/assets/index.html => server/assets/index.html +1 -0
@@ 0,0 1,1 @@
<!DOCTYPE html><html><head><title>kana.guru - Convert romaji to hiragana/katakana</title><meta charset=utf-8><meta name=viewport content="width=device-width, initial-scale=1"><link rel=icon href=favicon.svg><link rel=stylesheet href=style.css><script type=module src=kana.js></script><body><div><h2>かな.グル <span id=copyright> © <span id=copyright-year></span> Gabriel Sanches / <a href=https://git.sr.ht/~gbrlsnchs/kana-guru>source code</a> </span></h2><h3>Options</h3><div><input id=katakana type=checkbox><label for=katakana>Start with katakana</label></div><div><input id=extended type=checkbox><label for=extended>Extended katakana</label></div><div><input id=punctuation type=checkbox><label for=punctuation>Parse punctuation</label></div><div><input id=force-prolongation type=checkbox><label for=force-prolongation>Force prolongation</label></div><div><input id=kana-toggle type=checkbox><label for=kana-toggle>Use @ to toggle kanas</label></div><div><input id=raw-toggle type=checkbox><label for=raw-toggle>Use # to toggle raw text</label></div><div><input id=prolongation-reset type=checkbox><label for=prolongation-reset>Use ^ to reset prolongation</label></div><div><input id=vowel-shortener type=checkbox><label for=vowel-shortener>Use _ to shorten vowel</label></div><div><input id=virtual-stop type=checkbox><label for=virtual-stop>Use % to add virtual stop</label></div><h3>Input</h3><textarea id=romaji autocomplete=off spellcheck=false></textarea><div id=output><h3>Output</h3><button id=copy>(copy)</button></div><p id=result></div>
\ No newline at end of file

A server/assets/kana.min.js => server/assets/kana.min.js +1 -0
@@ 0,0 1,1 @@
let result = ""; const inputs = { romaji: document.getElementById("romaji"), katakana: document.getElementById("katakana"), extended: document.getElementById("extended"), punctuation: document.getElementById("punctuation"), forceProlongation: document.getElementById("force-prolongation"), kanaToggle: document.getElementById("kana-toggle"), rawToggle: document.getElementById("raw-toggle"), prolongationReset: document.getElementById("prolongation-reset"), vowelShortener: document.getElementById("vowel-shortener"), virtualStop: document.getElementById("virtual-stop"), }; const output = document.getElementById("result"); const loading = document.getElementById("loading"); const copy = document.getElementById("copy"); const encodeString = (value) => { const buffer = new TextEncoder().encode(value); const ptr = allocString(buffer.length + 1); const slice = new Uint8Array(memory.buffer, ptr, buffer.length + 1); slice.set(buffer); slice[buffer.length] = 0; return ptr; }; const decodeString = (ptr, len) => { const slice = new Uint8Array(memory.buffer, ptr, len); return new TextDecoder().decode(slice); }; const parseCheckbox = (el) => { if (!el) { return false; } return el.checked; }; const parseAscii = (el, char) => { return parseCheckbox(el) ? char.charCodeAt(0) : 0; }; const convert = () => { setTimeout(() => {}, 0); const ptr = encodeString(inputs.romaji.value); transliterate( ptr, parseCheckbox(inputs.katakana), parseCheckbox(inputs.extended), parseCheckbox(inputs.punctuation), parseCheckbox(inputs.forceProlongation), parseAscii(inputs.kanaToggle, "@"), parseAscii(inputs.rawToggle, "#"), parseAscii(inputs.prolongationReset, "^"), parseAscii(inputs.vowelShortener, "_"), parseAscii(inputs.virtualStop, "%"), ); freeString(ptr); }; const initialYear = 2023; const currentYear = new Date().getUTCFullYear(); const copyright = document.getElementById("copyright-year"); copyright.innerText = initialYear === currentYear ? initialYear : `${initialYear}-${currentYear}`; const { instance: { exports: { memory, allocString, freeString, transliterate }, }, } = await WebAssembly.instantiateStreaming(fetch("kana.wasm"), { env: { handleResult(ptr, len) { output.innerText = len > 0 ? decodeString(ptr, len) : ""; }, }, }); for (let [key, value] of Object.entries(inputs)) { value.addEventListener(key === "romaji" ? "keyup" : "input", () => { convert(); }); } copy.addEventListener("click", () => { navigator.clipboard.writeText(output.innerText || output.textContent); });
\ No newline at end of file

A server/assets/lib/kana.wasm => server/assets/lib/kana.wasm +0 -0
A server/assets/style.min.css => server/assets/style.min.css +1 -0
@@ 0,0 1,1 @@
:root { --bg-color: #1d1f21; --fg-color: #c5c8c6; --gray: #373b41; --copy: #8c9440; --link: #5f819d; } @media (prefers-color-scheme: light) { :root { --bg-color: #c5c8c6; --fg-color: #1d1f21; --gray: #707880; --copy: #5e8d87; --link: #a54242; } } body { margin: 0; padding: 0; width: 100vw; display: flex; justify-content: center; background-color: var(--bg-color); color: var(--fg-color); overflow: auto; font-family: monospace; } body > div { display: flex; flex-direction: column; gap: 8px; width: 640px; padding: 16px; } @media (max-width: 640px) { body > div { width: 100vw; } } h2 { display: flex; align-items: center; justify-content: space-between; } @media (max-width: 640px) { h2 { flex-direction: column; gap: 8px; } } h2, h3 { overflow: hidden; position: relative; margin-bottom: 0; } p { margin: 0; } h3:first-of-type:after { content: ""; width: 100%; height: 1px; background: var(--gray); position: absolute; top: 50%; margin-left: 16px; } a { color: var(--gray); text-decoration: none; } a:hover { color: var(--link); text-decoration: underline; } input[type="checkbox"] { margin-right: 8px; } label:hover, input[type="checkbox"]:hover { cursor: pointer; } textarea { height: 128px; resize: none; overflow-y: scroll; font-size: inherit; border: 1px solid var(--gray); padding: 8px; } textarea, input[type="checkbox"] { background-color: var(--bg); color: var(--fg); } #copyright { color: var(--gray); font-size: 0.8rem; } #result { white-space: pre-line; overflow-wrap: anywhere; } #copy { font-family: monospace; font-size: 0.8rem; font-weight: bold; color: var(--fg-color); border: 0; background-color: var(--bg-color); padding: 0; } #copy:hover { cursor: pointer; color: var(--copy); } #output { display: flex; flex-direction: row; justify-content: space-between; align-items: end; gap: 16px; } #output > h3 { flex: 1; }
\ No newline at end of file

A server/main.ts => server/main.ts +35 -0
@@ 0,0 1,35 @@
import { serve } from "https://deno.land/std@0.177.0/http/server.ts";
import { Status } from "https://deno.land/std@0.177.0/http/http_status.ts";

const [index, style, core, wasm, favicon] = await Promise.all([
	Deno.readFile("assets/index.html"),
	Deno.readFile("assets/style.min.css"),
	Deno.readFile("assets/kana.min.js"),
	Deno.readFile("assets/lib/kana.wasm"),
	Deno.readFile("assets/favicon.svg"),
]);

const assets: Map<string, [Uint8Array, string]> = new Map([
	["/", [index, "text/html"]],
	["/style.css", [style, "text/css"]],
	["/kana.js", [core, "text/javascript"]],
	["/kana.wasm", [wasm, "application/wasm"]],
	["/favicon.svg", [favicon, "image/svg+xml"]],
]);

function handler(req: Request) {
	const url = new URL(req.url);
	const { pathname } = url;

	const asset = assets.get(pathname);

	if (!asset) {
		return Response.redirect(url.origin);
	}

	const [data, mimeType] = asset;

	return new Response(data, { headers: { "content-type": mimeType } });
}

serve(handler, { port: 8000 });

R src/site/index.html => site/index.html +5 -1
@@ 4,6 4,7 @@
		<title>kana.guru - Convert romaji to hiragana/katakana</title>
		<meta charset="utf-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1" />
		<link rel="icon" href="favicon.svg" />
		<link rel="stylesheet" href="style.css" />
		<script type="module" src="kana.js"></script>
	</head>


@@ 66,7 67,10 @@
			<h3>Input</h3>
			<textarea id="romaji" autocomplete="off" spellcheck="false"></textarea>

			<h3>Output <span id="copy" tabindex="0">(copy)</span></h3>
			<div id="output">
				<h3>Output</h3>
				<button id="copy">(copy)</button>
			</div>
			<p id="result"></p>
		</div>
	</body>

R src/site/kana.js => site/kana.js +0 -0
R src/kana.zig => site/kana.zig +0 -0
R src/site/style.css => site/style.css +25 -6
@@ 10,7 10,8 @@
	:root {
		--bg-color: #c5c8c6;
		--fg-color: #1d1f21;
		--copy: #de935f;
		--gray: #707880;
		--copy: #5e8d87;
		--link: #a54242;
	}
}


@@ 65,15 66,14 @@ p {
	margin: 0;
}

h3:first-of-type:after,
h3:last-of-type:after {
h3:first-of-type:after {
	content: "";
	width: 100%;
	height: 1px;
	background: var(--gray);
	position: absolute;
	top: 50%;
	margin-left: 1rem;
	margin-left: 16px;
}

a {


@@ 83,6 83,7 @@ a {

a:hover {
	color: var(--link);
	text-decoration: underline;
}

input[type="checkbox"] {


@@ 95,7 96,7 @@ input[type="checkbox"]:hover {
}

textarea {
	height: 8rem;
	height: 128px;
	resize: none;
	overflow-y: scroll;
	font-size: inherit;


@@ 116,14 117,32 @@ input[type="checkbox"] {

#result {
	white-space: pre-line;
	overflow-wrap: break-word;
	overflow-wrap: anywhere;
}

#copy {
	font-family: monospace;
	font-size: 0.8rem;
	font-weight: bold;
	color: var(--fg-color);
	border: 0;
	background-color: var(--bg-color);
	padding: 0;
}

#copy:hover {
	cursor: pointer;
	color: var(--copy);
}

#output {
	display: flex;
	flex-direction: row;
	justify-content: space-between;
	align-items: end;
	gap: 16px;
}

#output > h3 {
	flex: 1;
}

D src/server.ts => src/server.ts +0 -52
@@ 1,52 0,0 @@
import { serve } from "https://deno.land/std@0.177.0/http/server.ts";
import { Status } from "https://deno.land/std@0.177.0/http/http_status.ts";

const [index, style, core, wasm] = await Promise.all([
	Deno.readFile("www/index.html"),
	Deno.readFile("www/style.min.css"),
	Deno.readFile("www/kana.min.js"),
	Deno.readFile("www/lib/kana.wasm"),
]);

function handler(req: Request) {
	const { pathname } = new URL(req.url);

	if (pathname === "/") {
		return new Response(index, {
			headers: {
				"content-type": "text/html",
			},
		});
	}

	if (pathname.startsWith("/style.css")) {
		return new Response(style, {
			headers: {
				"content-type": "text/css",
			},
		});
	}

	if (pathname.startsWith("/kana.js")) {
		return new Response(core, {
			headers: {
				"content-type": "text/javascript",
			},
		});
	}

	if (pathname.startsWith("/kana.wasm")) {
		return new Response(wasm, {
			headers: {
				"content-type": "application/wasm",
			},
		});
	}

	const url = new URL(req.url);
	return Response.redirect(url.origin);
}

serve(handler, {
	port: 8000,
});