Compare commits
16
Commits
9dbb6f7b2e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba97699af6 | ||
|
|
0ed1c5ff75 | ||
|
|
64119fdd99 | ||
|
|
e53ef3d9fc | ||
|
|
a9d73ecd57 | ||
|
|
232097d999 | ||
|
|
52e527e9de | ||
|
|
3da4656f42 | ||
|
|
a595980de2 | ||
|
|
4bbc8eaa10 | ||
|
|
ad312c174a | ||
|
|
95d852de92 | ||
|
|
fc18604850 | ||
|
|
5b983d1077 | ||
|
|
3f3b9d0b9e | ||
|
|
eb6dfea3db |
No files matched your search
@@ -0,0 +1,3 @@
|
||||
[submodule "utfdump"]
|
||||
path = utfdump
|
||||
url = https://github.com/pantonshire/utfdump.git
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
FROM docker.io/library/rust:1.82-alpine AS wasm_builder
|
||||
WORKDIR /app/
|
||||
COPY utfdump/Cargo.toml utfdump/Cargo.lock ./
|
||||
COPY utfdump/lib/ ./lib/
|
||||
COPY utfdump/wasm/ ./wasm/
|
||||
COPY utfdump/bin/ ./bin/
|
||||
RUN apk update && apk add --no-cache musl-dev wasm-pack pkgconf openssl-dev openssl binaryen
|
||||
WORKDIR /app/wasm/
|
||||
RUN wasm-pack build --release --target web
|
||||
|
||||
FROM docker.io/library/node:20-alpine AS node
|
||||
|
||||
FROM node AS node_deps
|
||||
WORKDIR /app/
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci
|
||||
|
||||
FROM node AS next_builder
|
||||
WORKDIR /app/
|
||||
COPY --from=wasm_builder /app/wasm/pkg/ ./wasm/utfdump/
|
||||
COPY --from=node_deps /app/node_modules/ ./node_modules/
|
||||
COPY package.json package-lock.json docker/next.config.js next-env.d.ts tsconfig.json ./
|
||||
COPY src/ ./src/
|
||||
COPY public/ ./public/
|
||||
ENV NEXT_TELEMETRY_DISABLED 1
|
||||
RUN npm run build
|
||||
|
||||
FROM node AS runner
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV production
|
||||
ENV NEXT_TELEMETRY_DISABLED 1
|
||||
RUN addgroup -g 998 -S next && adduser -G next -u 998 -S next
|
||||
COPY --from=next_builder /app/public/ ./public/
|
||||
COPY --from=next_builder --chown=next:next /app/.next/standalone/ ./
|
||||
COPY --from=next_builder --chown=next:next /app/.next/static/ ./.next/static/
|
||||
USER next
|
||||
EXPOSE 3000
|
||||
ENV PORT 3000
|
||||
CMD ["node", "server.js"]
|
||||
|
||||
@@ -1,34 +1,13 @@
|
||||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
||||
# UTF Inspector
|
||||
|
||||
## Getting Started
|
||||
A web interface for [utfdump](https://github.com/pantonshire/utfdump/), using
|
||||
Next.js and WebAssembly.
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
||||
## Building & running
|
||||
1. Install [`Rust Cargo`](https://www.rust-lang.org/),
|
||||
[`wasm-pack`](https://github.com/rustwasm/wasm-pack) and
|
||||
[`npm`](https://www.npmjs.com/)
|
||||
2. `git submodule update --init`
|
||||
3. `npm install`
|
||||
4. `npm run build`
|
||||
5. `npm run start`
|
||||
@@ -0,0 +1,6 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
output: 'standalone',
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
+1
-1
@@ -11,7 +11,7 @@ const nextConfig = {
|
||||
loadedWasm = true;
|
||||
nextConfig.plugins.push(
|
||||
new WasmPackPlugin({
|
||||
crateDirectory: path.resolve(__dirname, '..', 'utfdump', 'wasm'),
|
||||
crateDirectory: path.resolve(__dirname, 'utfdump', 'wasm'),
|
||||
outDir: path.resolve(__dirname, 'wasm', 'utfdump'),
|
||||
forceMode: 'production',
|
||||
extraArgs: '--target web',
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 15 KiB |
@@ -1,4 +1,5 @@
|
||||
:root {
|
||||
--base_max_width: 1000px;
|
||||
--main_1: #735cd6;
|
||||
--main_2: #db52c9;
|
||||
--light_1: #a598e0;
|
||||
@@ -16,6 +17,11 @@ html,
|
||||
body {
|
||||
max-width: 100vw;
|
||||
overflow-x: hidden;
|
||||
padding:
|
||||
env(safe-area-inset-top)
|
||||
env(safe-area-inset-right)
|
||||
env(safe-area-inset-bottom)
|
||||
env(safe-area-inset-left);
|
||||
}
|
||||
|
||||
a {
|
||||
|
||||
+3
-5
@@ -1,13 +1,11 @@
|
||||
import './globals.css'
|
||||
import { Inter, Roboto_Slab } from 'next/font/google'
|
||||
|
||||
const inter = Inter({ subsets: ['latin'] });
|
||||
import { Roboto_Slab } from 'next/font/google'
|
||||
|
||||
const robotoSlab = Roboto_Slab({ subsets: ['latin'] });
|
||||
|
||||
export const metadata = {
|
||||
title: 'Create Next App',
|
||||
description: 'Generated by create next app',
|
||||
title: 'UTF Inspector',
|
||||
description: 'Unicode character info tool',
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
|
||||
+20
-8
@@ -3,7 +3,7 @@
|
||||
}
|
||||
|
||||
.title_section {
|
||||
max-width: 1000px;
|
||||
max-width: var(--base_max_width);
|
||||
margin: 0 auto 2rem auto;
|
||||
padding: 0 3rem;
|
||||
}
|
||||
@@ -20,7 +20,7 @@
|
||||
}
|
||||
|
||||
.input_section {
|
||||
max-width: 1000px;
|
||||
max-width: var(--base_max_width);
|
||||
margin: 0 auto 2rem auto;
|
||||
}
|
||||
|
||||
@@ -55,21 +55,33 @@
|
||||
|
||||
#output_table {
|
||||
margin: 0 auto;
|
||||
font-size: 0.9rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#output_table tbody th:nth-child(1) {
|
||||
.output_table_regular tbody th:nth-child(1) {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#output_table td,
|
||||
#output_table th {
|
||||
white-space: nowrap;
|
||||
.output_table_transposed th,
|
||||
.output_table_transposed td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.button_container {
|
||||
max-width: var(--base_max_width);
|
||||
margin: 1rem auto 1rem auto;
|
||||
}
|
||||
|
||||
.button {
|
||||
padding: 0.25rem 2rem;
|
||||
display: block;
|
||||
color: var(--main_1);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 40rem) {
|
||||
.main {
|
||||
padding-left: 3rem;
|
||||
padding-right: 3rem;
|
||||
padding: 2rem 3rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
|
||||
+1
-2
@@ -1,8 +1,7 @@
|
||||
import Image from 'next/image'
|
||||
import styles from './page.module.css'
|
||||
import { Inspector } from '@/components/inspector';
|
||||
|
||||
const sourceUrl = 'https://git.pantonshire.com/tom/utfinspector';
|
||||
const sourceUrl = 'https://github.com/pantonshire/utfinspector';
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
|
||||
@@ -1,17 +1,33 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import styles from '@/app/page.module.css'
|
||||
import { TextField } from './textfield';
|
||||
import { UtfdumpContextProvider } from '@/context/utfdump';
|
||||
import { OutputTable } from './output_table';
|
||||
|
||||
const lsTransposeKey = 'transpose';
|
||||
const lsTrueStr = 't';
|
||||
const lsFalseStr = 'f';
|
||||
|
||||
type InspectorProps = {
|
||||
sourceUrl: string,
|
||||
};
|
||||
|
||||
export function Inspector(props: InspectorProps) {
|
||||
const [currentString, setCurrentString] = useState('');
|
||||
const [transpose, setTranspose] = useState<boolean>(true);
|
||||
|
||||
useEffect(() => {
|
||||
const lsTranspose = localStorage.getItem(lsTransposeKey) !== lsFalseStr;
|
||||
setTranspose(lsTranspose);
|
||||
}, []);
|
||||
|
||||
function toggleTranspose() {
|
||||
const newTranspose = !transpose;
|
||||
setTranspose(newTranspose);
|
||||
localStorage.setItem(lsTransposeKey, newTranspose ? lsTrueStr : lsFalseStr);
|
||||
}
|
||||
|
||||
return (
|
||||
<UtfdumpContextProvider>
|
||||
@@ -30,7 +46,11 @@ export function Inspector(props: InspectorProps) {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<OutputTable currentString={currentString} />
|
||||
<OutputTable
|
||||
currentString={currentString}
|
||||
transpose={transpose}
|
||||
toggleTranspose={toggleTranspose}
|
||||
/>
|
||||
</section>
|
||||
</UtfdumpContextProvider>
|
||||
);
|
||||
|
||||
+119
-24
@@ -3,10 +3,16 @@ import styles from '@/app/page.module.css'
|
||||
import { UtfdumpContext } from '@/context/utfdump';
|
||||
import { EncodedCodepoint } from 'utfdump_wasm';
|
||||
|
||||
export function OutputTable(props: { currentString: string }) {
|
||||
export function OutputTable(props: {
|
||||
currentString: string,
|
||||
transpose: boolean,
|
||||
toggleTranspose: () => void
|
||||
}) {
|
||||
const ctx = useContext(UtfdumpContext);
|
||||
|
||||
|
||||
let nonEmpty = false;
|
||||
let rows = [];
|
||||
let transposedData: React.JSX.Element[][] = [[], [], [], [], [], [], [], [], [], [], [], [], [], []];
|
||||
|
||||
if (ctx.utfdump) {
|
||||
for (const codepointStr of props.currentString) {
|
||||
@@ -38,24 +44,60 @@ export function OutputTable(props: { currentString: string }) {
|
||||
charDisplayStr = '\u25cc' + charDisplayStr;
|
||||
}
|
||||
|
||||
rows.push((
|
||||
<tr>
|
||||
<th>{charDisplayStr}</th>
|
||||
<td>{charData.name()}</td>
|
||||
<td>U+{codepoint.toString(16).padStart(4, '0')}</td>
|
||||
<td>{encodedUtf8Str}</td>
|
||||
<td>{encodedUtf16Str}</td>
|
||||
<td>{charData.category_full()}</td>
|
||||
<td>{combiningClassName || combiningClassNum}</td>
|
||||
<td>{charData.bidi_full()}</td>
|
||||
<td>{charData.mirrored() ? 'Yes' : 'No'}</td>
|
||||
<td>{optCharCodes(charData.decomp_string())}</td>
|
||||
<td>{optCharCodes(charData.uppercase_string())}</td>
|
||||
<td>{optCharCodes(charData.lowercase_string())}</td>
|
||||
<td>{optCharCodes(charData.titlecase_string())}</td>
|
||||
<td>{charData.numeric_value()}</td>
|
||||
</tr>
|
||||
));
|
||||
const elemChar = <th>{charDisplayStr}</th>;
|
||||
const elemName = <td>{charData.name()}</td>;
|
||||
const elemCode = <td>U+{codepoint.toString(16).padStart(4, '0')}</td>;
|
||||
const elemUtf8 = <td>{encodedUtf8Str}</td>;
|
||||
const elemUtf16 = <td>{encodedUtf16Str}</td>;
|
||||
const elemCategory = <td>{charData.category_full()}</td>;
|
||||
const elemCombining = <td>{combiningClassName || combiningClassNum}</td>;
|
||||
const elemBidi = <td>{charData.bidi_full()}</td>;
|
||||
const elemMirrored = <td>{charData.mirrored() ? 'Yes' : 'No'}</td>;
|
||||
const elemDecomp = <td>{optCharCodes(charData.decomp_string())}</td>;
|
||||
const elemUpper = <td>{optCharCodes(charData.uppercase_string())}</td>;
|
||||
const elemLower = <td>{optCharCodes(charData.lowercase_string())}</td>;
|
||||
const elemTitle = <td>{optCharCodes(charData.titlecase_string())}</td>;
|
||||
const elemNumeric = <td>{charData.numeric_value()}</td>;
|
||||
|
||||
if (props.transpose) {
|
||||
transposedData[0].push(elemChar);
|
||||
transposedData[1].push(elemName);
|
||||
transposedData[2].push(elemCode);
|
||||
transposedData[3].push(elemUtf8);
|
||||
transposedData[4].push(elemUtf16);
|
||||
transposedData[5].push(elemCategory);
|
||||
transposedData[6].push(elemCombining);
|
||||
transposedData[7].push(elemBidi);
|
||||
transposedData[8].push(elemMirrored);
|
||||
transposedData[9].push(elemDecomp);
|
||||
transposedData[10].push(elemUpper);
|
||||
transposedData[11].push(elemLower);
|
||||
transposedData[12].push(elemTitle);
|
||||
transposedData[13].push(elemNumeric);
|
||||
}
|
||||
|
||||
else {
|
||||
rows.push((
|
||||
<tr>
|
||||
{elemChar}
|
||||
{elemName}
|
||||
{elemCode}
|
||||
{elemUtf8}
|
||||
{elemUtf16}
|
||||
{elemCategory}
|
||||
{elemCombining}
|
||||
{elemBidi}
|
||||
{elemMirrored}
|
||||
{elemDecomp}
|
||||
{elemUpper}
|
||||
{elemLower}
|
||||
{elemTitle}
|
||||
{elemNumeric}
|
||||
</tr>
|
||||
));
|
||||
}
|
||||
|
||||
nonEmpty = true;
|
||||
|
||||
charData.free();
|
||||
}
|
||||
@@ -67,10 +109,37 @@ export function OutputTable(props: { currentString: string }) {
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<section className={styles.table_container}>
|
||||
<div className={styles.overflow_scroll}>
|
||||
<table id={styles.output_table}>
|
||||
let table;
|
||||
|
||||
if (nonEmpty) {
|
||||
if (props.transpose) {
|
||||
table = (
|
||||
<table id={styles.output_table} className={styles.output_table_transposed}>
|
||||
<thead>
|
||||
<tr><th></th>{transposedData[0]}</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><th>Name</th>{transposedData[1]}</tr>
|
||||
<tr><th>Code</th>{transposedData[2]}</tr>
|
||||
<tr><th>UTF-8</th>{transposedData[3]}</tr>
|
||||
<tr><th>UTF-16LE</th>{transposedData[4]}</tr>
|
||||
<tr><th>Category</th>{transposedData[5]}</tr>
|
||||
<tr><th>Combining</th>{transposedData[6]}</tr>
|
||||
<tr><th>Bidirectional</th>{transposedData[7]}</tr>
|
||||
<tr><th>Mirrored</th>{transposedData[8]}</tr>
|
||||
<tr><th>Decomp</th>{transposedData[9]}</tr>
|
||||
<tr><th>Upper</th>{transposedData[10]}</tr>
|
||||
<tr><th>Lower</th>{transposedData[11]}</tr>
|
||||
<tr><th>Title</th>{transposedData[12]}</tr>
|
||||
<tr><th>Numeric</th>{transposedData[13]}</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
else {
|
||||
table = (
|
||||
<table id={styles.output_table} className={styles.output_table_regular}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
@@ -93,6 +162,32 @@ export function OutputTable(props: { currentString: string }) {
|
||||
{rows}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
table = (<></>);
|
||||
}
|
||||
|
||||
let transposeButton;
|
||||
if (nonEmpty) {
|
||||
transposeButton = (
|
||||
<button className={styles.button} onClick={props.toggleTranspose}>
|
||||
Flip table
|
||||
</button>
|
||||
);
|
||||
} else {
|
||||
transposeButton = (<></>);
|
||||
}
|
||||
|
||||
return (
|
||||
<section className={styles.output_section}>
|
||||
<div className={styles.button_container}>
|
||||
{transposeButton}
|
||||
</div>
|
||||
<div className={styles.table_container}>
|
||||
<div className={styles.overflow_scroll}>
|
||||
{table}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
Submodule
+1
Submodule utfdump added at 69660a7e10.
Reference in new issue
Block a user