Skip to content

Commit 08a176b

Browse files
committed
Provide a way to paste/edit existing CIDR notation
1 parent d4817ee commit 08a176b

File tree

6 files changed

+166
-6
lines changed

6 files changed

+166
-6
lines changed

src/components/CIDR.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ Visualize CIDR
4848
</script>
4949

5050
<div class="sm:mx--6">
51-
<IPv4 bind:address bind:prefix>
51+
<IPv4 bind:address bind:prefix {renderedAddress}>
5252
<IPv4Network
5353
slot="extra-bits"
5454
bind:address

src/components/blend/IPv4.svelte

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Represent IPv4 address including prefix notation
55
import BitHex from './BitHex.svelte';
66
import IPv4Prefix from './IPv4Prefix.svelte';
77
import InputNumberLocaled from '../parts/InputNumberLocaled.svelte';
8+
import IPv4Input from '../parts/IPv4Input.svelte';
89
import { assemble, disassemble, calcHosts } from '@lib/ipv4';
910
1011
const min = 0;
@@ -14,6 +15,7 @@ Represent IPv4 address including prefix notation
1415

1516
<script lang="ts">
1617
export let address = 0xc0a80000;
18+
export let renderedAddress = 'placeholder';
1719
export let prefix = 16;
1820
1921
// break down into 4 octets
@@ -29,10 +31,14 @@ Represent IPv4 address including prefix notation
2931
};
3032
3133
$: addresses = calcHosts(prefix) + 2;
34+
35+
let showingWholeInput = false;
3236
</script>
3337

38+
<IPv4Input bind:address bind:prefix {renderedAddress} bind:show={showingWholeInput} />
39+
3440
<!-- for xs only -->
35-
<div class="sm:hidden items-end mb-2 flex">
41+
<div class="sm:hidden items-end mb-2 flex" class:invisible={showingWholeInput}>
3642
<div class="grow" />
3743
<div class="flex text-2xl lt-xs:text-lg mr-2 items-center">
3844
<div>
@@ -70,7 +76,7 @@ Represent IPv4 address including prefix notation
7076
<div class="flex lt-sm:overflow-x-auto px-2 mx--2">
7177
<div class="grow" />
7278
<div>
73-
<div class="flex lt-sm:hidden">
79+
<div class="flex lt-sm:hidden" class:invisible={showingWholeInput}>
7480
{#each octets as octet, idx}
7581
<div class="flex-1 flex group text-2xl mb-2 relative">
7682
<!-- adding max-w-[[n]rem] for Firefox -->
@@ -114,7 +120,7 @@ Represent IPv4 address including prefix notation
114120
<div class="grow" />
115121
</div>
116122
<div class="flex flex-col lt-sm:hidden">
117-
<div class="lt-sm:hidden mb-2">
123+
<div class="lt-sm:hidden mb-2" class:invisible={showingWholeInput}>
118124
<InputNumberLocaled
119125
min={0}
120126
max={31}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<!-- @component
2+
A convenient addition to allow pasting/editing as whole
3+
-->
4+
<script context="module" lang="ts">
5+
import { pattern, parseFrom } from '@lib/ipv4';
6+
</script>
7+
8+
<script lang="ts">
9+
export let address = 0xc0a80000;
10+
export let renderedAddress = 'placeholder';
11+
export let prefix = 16;
12+
export let show = false;
13+
14+
$: placeholder = `${renderedAddress}/${prefix}`;
15+
$: inputAsWhole = placeholder;
16+
17+
let input: HTMLInputElement;
18+
let button: HTMLButtonElement;
19+
20+
const selectText = (e: Event) => {
21+
const elm = e.target as HTMLInputElement;
22+
elm.select();
23+
};
24+
25+
const evaluate = () => {
26+
const { address: addr, prefix: pref } = parseFrom(inputAsWhole);
27+
const isValid = addr > 0 && pref > 0;
28+
29+
if (!isValid) {
30+
return;
31+
}
32+
33+
address = addr;
34+
prefix = pref;
35+
};
36+
37+
const onBtnClick = () => {
38+
show = true;
39+
setTimeout(() => {
40+
if (input) input.focus();
41+
}, 0);
42+
};
43+
44+
const onInputBlur = (e: Event) => {
45+
// to ignore when window itself get blurred
46+
if (document.activeElement === e.target) {
47+
return;
48+
}
49+
50+
show = false;
51+
setTimeout(() => {
52+
if (button) button.focus();
53+
inputAsWhole = placeholder;
54+
}, 0);
55+
};
56+
</script>
57+
58+
<div class="relative">
59+
<div class="absolute text-gray-600 left-5 top-2 lt-sm:top-5 lt-sm:left-0 lt-xs:top--1">
60+
<button
61+
class="i-bi-pencil ml-1"
62+
on:click={onBtnClick}
63+
bind:this={button}
64+
class:invisible={show}
65+
/>
66+
</div>
67+
<div
68+
class="absolute font-mono text-xl lt-sm:text-2xl lt-xs:text-lg text-gray-600 left-11 top-1 lt-sm:top-3 lt-xs:top-4 lt-sm:left-9 lt-xs:left-6 z-30 w-[88%]"
69+
class:hidden={!show}
70+
>
71+
<input
72+
class="invalid:bg-yellow-100 w-full px-1 text-center rounded border border-gray sm:tracking-widest"
73+
type="text"
74+
bind:this={input}
75+
bind:value={inputAsWhole}
76+
{placeholder}
77+
{pattern}
78+
on:focus={selectText}
79+
on:change={evaluate}
80+
on:blur={onInputBlur}
81+
/>
82+
</div>
83+
</div>

src/components/parts/InputNumberLocaled.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ Two things are enhanced:
6666
const onBlur = (e: Event) => {
6767
whenFocused = false;
6868
69-
// to ignore window itself get blurred - somehow not progressing to below prevents element's being invisible
69+
// to ignore when window itself get blurred - somehow not progressing to below prevents element's being invisible
7070
if (document.activeElement === e.target) {
7171
return;
7272
}

src/lib/ipv4.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,26 @@ export const calcIP = (address: number, prefix: number): CalcIPResult => {
9999

100100
export const newAddressFrom = (address: number, mask: number, network: number) =>
101101
((address & ~mask) >>> 0) + ((network & mask) >>> 0);
102+
103+
const patternToMatch = /^([0-9]{1,3}).([0-9]{1,3}).([0-9]{1,3}).([0-9]{1,3})\/([0-9]{1,2})$/;
104+
// thanks to https://stackoverflow.com/a/36760050/1570165
105+
const patternToTest = /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}\/(3[0-2]|[1-2][0-9]|[0-9])$/;
106+
// to be inserted as https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern
107+
export const pattern = patternToTest.toString().slice(1, -1);
108+
109+
const failed = { address: -1, prefix: -1 };
110+
111+
export const parseFrom = (text: string) => {
112+
const passed = patternToTest.test(text);
113+
114+
if (!passed) return failed;
115+
116+
const matched = text.match(patternToMatch);
117+
118+
if (!matched) return failed;
119+
120+
const [a, b, c, d, prefix] = matched.slice(1).map((n) => Number(n));
121+
const address = assemble([a, b, c, d]);
122+
123+
return { address, prefix };
124+
};

test/lib/ipv4.test.ts

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,53 @@
11
import { expect, describe, it } from 'vitest';
2-
import { calcHosts, calcIP, assemble } from '../../src/lib/ipv4';
2+
import { calcHosts, calcIP, assemble, parseFrom } from '../../src/lib/ipv4';
3+
4+
describe('parseFrom', () => {
5+
it('1.2.3.4/5', () => {
6+
expect(parseFrom('1.2.3.4/5')).toEqual({ address: assemble([1, 2, 3, 4]), prefix: 5 });
7+
});
8+
9+
it('255.255.255.255/32', () => {
10+
expect(parseFrom('255.255.255.255/32')).toEqual({
11+
address: assemble([255, 255, 255, 255]),
12+
prefix: 32,
13+
});
14+
});
15+
16+
it('255.255.255.255/33', () => {
17+
expect(parseFrom('255.255.255.255/33')).toEqual({
18+
address: -1,
19+
prefix: -1,
20+
});
21+
});
22+
23+
it('256.255.255.255/32', () => {
24+
expect(parseFrom('256.255.255.255/32')).toEqual({
25+
address: -1,
26+
prefix: -1,
27+
});
28+
});
29+
30+
it('255.256.255.255/32', () => {
31+
expect(parseFrom('255.256.255.255/32')).toEqual({
32+
address: -1,
33+
prefix: -1,
34+
});
35+
});
36+
37+
it('255.255.256.255/32', () => {
38+
expect(parseFrom('255.255.256.255/32')).toEqual({
39+
address: -1,
40+
prefix: -1,
41+
});
42+
});
43+
44+
it('255.255.255.256/32', () => {
45+
expect(parseFrom('255.255.255.256/32')).toEqual({
46+
address: -1,
47+
prefix: -1,
48+
});
49+
});
50+
});
351

452
describe('assemble', () => {
553
it('0.0.0.0', () => {

0 commit comments

Comments
 (0)