Skip to content

Commit 433a56d

Browse files
committed
Add Vite-powered GitHub Pages postcode search interface
- Created playground/ folder with modern Vite setup - Built responsive UI with Tailwind CSS and Font Awesome - Implemented real-time postcode search functionality - Added browser-compatible Buffer polyfill - Set up GitHub Actions workflow for automatic deployment - Updated package.json to use Yarn - Added comprehensive documentation
1 parent 77f7e4e commit 433a56d

13 files changed

Lines changed: 2150 additions & 17 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
name: Deploy to GitHub Pages
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
build-and-deploy:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Checkout
15+
uses: actions/checkout@v4
16+
17+
- name: Setup Node.js
18+
uses: actions/setup-node@v4
19+
with:
20+
node-version: "18"
21+
cache: "yarn"
22+
23+
- name: Install dependencies
24+
run: yarn install --frozen-lockfile
25+
26+
- name: Build postcode database
27+
run: yarn build
28+
29+
- name: Build web app
30+
run: yarn build:web
31+
32+
- name: Setup Pages
33+
if: github.ref == 'refs/heads/main'
34+
uses: actions/configure-pages@v4
35+
36+
- name: Upload artifact
37+
if: github.ref == 'refs/heads/main'
38+
uses: actions/upload-pages-artifact@v3
39+
with:
40+
path: "./dist"
41+
42+
- name: Deploy to GitHub Pages
43+
if: github.ref == 'refs/heads/main'
44+
id: deployment
45+
uses: actions/deploy-pages@v4
46+
47+
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
48+
permissions:
49+
contents: read
50+
pages: write
51+
id-token: write
52+
53+
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
54+
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
55+
concurrency:
56+
group: "pages"
57+
cancel-in-progress: false

README.md

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,43 @@ _\*Estimated performance for linear/indexed searches without specialized optimiz
276276
- latDelta = unit.latInt - sector.latMin
277277
- lonDelta = unit.lonInt - sector.lonMin
278278

279-
# Insperations
279+
# Web Interface
280+
281+
A modern web interface is available in the `playground/` directory. This provides a user-friendly way to search postcodes through a web browser.
282+
283+
## Quick Start
284+
285+
```bash
286+
# Install dependencies
287+
yarn install
288+
289+
# Start development server
290+
yarn dev
291+
292+
# Build for production
293+
yarn build:web
294+
```
295+
296+
Visit `http://localhost:3000` to use the web interface.
297+
298+
## Features
299+
300+
- **Real-time search**: Type partial or full postcodes for instant results
301+
- **Responsive design**: Works on desktop and mobile
302+
- **Modern UI**: Built with Tailwind CSS and Font Awesome icons
303+
- **Map integration**: Direct links to Google Maps
304+
- **GitHub Pages ready**: Automatic deployment workflow included
305+
306+
## Deployment
307+
308+
The project includes a GitHub Actions workflow that automatically:
309+
310+
1. Builds the postcode database
311+
2. Compiles the web interface
312+
3. Deploys to GitHub Pages
313+
314+
Simply push to the `main` branch to trigger deployment.
315+
316+
# Inspirations
280317

281318
- https://www.youtube.com/watch?v=5HRAUWrvK6o

entropy-report.txt

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
================================================================================
2+
ENTROPY ANALYSIS REPORT - postcodes.pcod
3+
================================================================================
4+
5+
File Size: 6,509,848 bytes (6.21 MB)
6+
Overall Entropy: 7.8040 bits/byte
7+
Theoretical Maximum: 8.0000 bits/byte
8+
Compression Ratio: 2.45% potential savings
9+
10+
BIT PATTERN ANALYSIS:
11+
----------------------------------------
12+
Zero bits: 28,924,685 (55.54%)
13+
One bits: 23,154,099 (44.46%)
14+
Alternating bytes: 71,040
15+
Repeating bytes: 650,347
16+
17+
SECTION ENTROPY ANALYSIS:
18+
--------------------------------------------------------------------------------
19+
Offset Size Entropy Description
20+
--------------------------------------------------------------------------------
21+
0x000000 32 3.1686 File Header (PCDB magic, version, counts, offsets)
22+
0x000020 26,487 6.3073 Outward Index (2943 entries × 9 bytes)
23+
0x006797 6,483,329 7.8062 Sector Tables + Unit Data (compressed coordinates)
24+
25+
CHUNK ENTROPY DISTRIBUTION (4KB chunks):
26+
--------------------------------------------------
27+
5.5 bits/byte: 4 chunks ( 0.3%)
28+
6.0 bits/byte: 2 chunks ( 0.1%)
29+
6.5 bits/byte: 3 chunks ( 0.2%)
30+
7.0 bits/byte: 57 chunks ( 3.6%) █
31+
7.5 bits/byte: 1524 chunks ( 95.8%) ███████████████████████████████████████████████
32+
33+
HIGHEST ENTROPY CHUNKS (most random/compressed):
34+
------------------------------------------------------------
35+
Offset 0x158000: 7.9049 bits/byte
36+
Offset 0x0be000: 7.9040 bits/byte
37+
Offset 0x161000: 7.9020 bits/byte
38+
Offset 0x0cc000: 7.9000 bits/byte
39+
Offset 0x3b6000: 7.8989 bits/byte
40+
41+
LOWEST ENTROPY CHUNKS (most structured/redundant):
42+
------------------------------------------------------------
43+
Offset 0x003000: 5.7888 bits/byte
44+
Offset 0x000000: 5.7997 bits/byte
45+
Offset 0x005000: 5.9050 bits/byte
46+
Offset 0x004000: 5.9987 bits/byte
47+
Offset 0x002000: 6.0888 bits/byte

entropy-visualization.html

Lines changed: 235 additions & 0 deletions
Large diffs are not rendered by default.

package.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,21 @@
99
"test:watch": "jest --watch",
1010
"test:coverage": "jest --coverage",
1111
"test:verbose": "jest --verbose",
12-
"test:client": "ts-node scripts/test-client.ts"
12+
"test:client": "ts-node scripts/test-client.ts",
13+
"dev": "vite",
14+
"build:web": "vite build",
15+
"preview": "vite preview"
1316
},
1417
"dependencies": {
1518
"@types/node": "^24.3.1",
1619
"typescript": "^5.9.2"
1720
},
1821
"devDependencies": {
1922
"@types/jest": "^30.0.0",
23+
"@vitejs/plugin-legacy": "^7.2.1",
2024
"jest": "^30.1.3",
2125
"ts-jest": "^29.4.1",
22-
"ts-node": "^10.9.2"
26+
"ts-node": "^10.9.2",
27+
"vite": "^7.1.4"
2328
}
2429
}

playground/README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Postcode Search Web Interface
2+
3+
This is a modern web interface for searching UK postcodes built with Vite, TypeScript, and Tailwind CSS.
4+
5+
## Features
6+
7+
- **Real-time search**: Type partial or full postcodes to get instant results
8+
- **Responsive design**: Works on desktop and mobile devices
9+
- **Fast performance**: Uses the optimized binary postcode database
10+
- **Modern UI**: Clean, accessible interface with Tailwind CSS
11+
- **Geographic data**: Shows latitude/longitude coordinates for each postcode
12+
- **Map integration**: Direct links to Google Maps for each result
13+
14+
## Development
15+
16+
```bash
17+
# Start development server
18+
yarn dev
19+
20+
# Build for production
21+
yarn build:web
22+
23+
# Preview production build
24+
yarn preview
25+
```
26+
27+
## Usage
28+
29+
1. **Full postcode search**: Enter a complete postcode like "SW1A 1AA" to find its exact coordinates
30+
2. **Partial search**: Enter partial postcodes like "SW1A" or "SW1" to see all matching postcodes
31+
3. **Interactive results**: Click "View on Map" to open the location in Google Maps
32+
33+
## Technical Details
34+
35+
- Built with **Vite** for fast development and optimized builds
36+
- Uses **TypeScript** for type safety
37+
- **Tailwind CSS** for styling with CDN delivery
38+
- **Font Awesome** icons for better UX
39+
- Browser-compatible **Buffer polyfill** for Node.js compatibility
40+
- Optimized for **GitHub Pages** deployment
41+
42+
## Browser Compatibility
43+
44+
- Modern browsers (Chrome, Firefox, Safari, Edge)
45+
- Mobile browsers (iOS Safari, Chrome Mobile)
46+
- No Internet Explorer support (uses modern JavaScript features)

playground/index.html

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>UK Postcode Search</title>
7+
<link
8+
href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css"
9+
rel="stylesheet">
10+
<link
11+
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"
12+
rel="stylesheet">
13+
<!-- Preload the database -->
14+
<link rel="preload" href="/postcodes.pcod" as="fetch"
15+
type="application/octet-stream" crossorigin>
16+
</head>
17+
<body class="bg-gray-50 min-h-screen">
18+
<div id="app"></div>
19+
<script type="module" src="/src/main.ts"></script>
20+
</body>
21+
</html>

playground/public/postcodes.pcod

6.21 MB
Binary file not shown.

playground/src/buffer-polyfill.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/**
2+
* Simple Buffer polyfill for browser compatibility
3+
* This provides the minimal Buffer interface needed by PostcodeClient
4+
*/
5+
6+
export class BufferPolyfill {
7+
private data: Uint8Array;
8+
9+
constructor(data: Uint8Array | ArrayBuffer | number[]) {
10+
if (data instanceof ArrayBuffer) {
11+
this.data = new Uint8Array(data);
12+
} else if (Array.isArray(data)) {
13+
this.data = new Uint8Array(data);
14+
} else {
15+
this.data = data;
16+
}
17+
18+
// Make it behave like an array for buffer[index] access
19+
return new Proxy(this, {
20+
get(target, prop) {
21+
if (typeof prop === "string" && /^\d+$/.test(prop)) {
22+
const index = parseInt(prop, 10);
23+
return target.data[index];
24+
}
25+
return (target as any)[prop];
26+
},
27+
has(target, prop) {
28+
if (typeof prop === "string" && /^\d+$/.test(prop)) {
29+
const index = parseInt(prop, 10);
30+
return index >= 0 && index < target.data.length;
31+
}
32+
return prop in target;
33+
},
34+
});
35+
}
36+
37+
static from(data: Uint8Array | ArrayBuffer | number[]): BufferPolyfill {
38+
return new BufferPolyfill(data);
39+
}
40+
41+
static alloc(size: number, fill?: number): BufferPolyfill {
42+
const data = new Uint8Array(size);
43+
if (fill !== undefined) {
44+
data.fill(fill);
45+
}
46+
return new BufferPolyfill(data);
47+
}
48+
49+
static concat(buffers: BufferPolyfill[]): BufferPolyfill {
50+
const totalLength = buffers.reduce((sum, buf) => sum + buf.length, 0);
51+
const result = new Uint8Array(totalLength);
52+
let offset = 0;
53+
54+
for (const buffer of buffers) {
55+
result.set(buffer.data, offset);
56+
offset += buffer.length;
57+
}
58+
59+
return new BufferPolyfill(result);
60+
}
61+
62+
get length(): number {
63+
return this.data.length;
64+
}
65+
66+
readUInt8(offset: number): number {
67+
return this.data[offset] || 0;
68+
}
69+
70+
readUInt16LE(offset: number): number {
71+
return (this.data[offset] || 0) | ((this.data[offset + 1] || 0) << 8);
72+
}
73+
74+
readUInt32LE(offset: number): number {
75+
return (
76+
((this.data[offset] || 0) |
77+
((this.data[offset + 1] || 0) << 8) |
78+
((this.data[offset + 2] || 0) << 16) |
79+
((this.data[offset + 3] || 0) << 24)) >>>
80+
0
81+
); // Convert to unsigned 32-bit
82+
}
83+
84+
readInt32LE(offset: number): number {
85+
const value = this.readUInt32LE(offset);
86+
// Convert unsigned to signed
87+
return value > 0x7fffffff ? value - 0x100000000 : value;
88+
}
89+
90+
readUIntLE(offset: number, byteLength: number): number {
91+
let value = 0;
92+
for (let i = 0; i < byteLength; i++) {
93+
value |= (this.data[offset + i] || 0) << (i * 8);
94+
}
95+
return value >>> 0;
96+
}
97+
98+
readIntLE(offset: number, byteLength: number): number {
99+
const value = this.readUIntLE(offset, byteLength);
100+
const maxValue = Math.pow(2, byteLength * 8 - 1);
101+
return value >= maxValue ? value - Math.pow(2, byteLength * 8) : value;
102+
}
103+
104+
toString(encoding: string, start?: number, end?: number): string {
105+
const slice = this.data.slice(start, end);
106+
if (encoding === "ascii") {
107+
return String.fromCharCode(...slice);
108+
}
109+
throw new Error(`Encoding ${encoding} not supported`);
110+
}
111+
112+
subarray(start?: number, end?: number): Uint8Array {
113+
return this.data.subarray(start, end);
114+
}
115+
}
116+
117+
// Make Buffer available globally for the postcode client
118+
declare global {
119+
interface Window {
120+
Buffer: typeof BufferPolyfill;
121+
}
122+
}
123+
124+
if (typeof window !== "undefined") {
125+
window.Buffer = BufferPolyfill as any;
126+
}

0 commit comments

Comments
 (0)