Skip to content

Commit ea6a1f1

Browse files
authored
Merge pull request #301 from bavix/info-type
added additional info
2 parents fa29eb9 + b4914f2 commit ea6a1f1

12 files changed

+1266
-1159
lines changed

package-lock.json

Lines changed: 917 additions & 1027 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@
3636
"@rollup/plugin-terser": "^0.4.4",
3737
"@theme-toggles/react": "^4.1.0",
3838
"@web/dev-server": "^0.4.6",
39-
"babel-preset-minify": "^0.5.2",
4039
"bulma": "^1.0.4",
4140
"concurrently": "^9.1.2",
41+
"json5": "^2.2.3",
4242
"notiflix": "^3.2.8",
4343
"preact": "^10.26.8",
4444
"react-helmet-async": "^2.0.5",

public/assets/bundle-D2zYReHC.js

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/assets/bundle-D2zYReHC.js.map

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/assets/bundle-FOTFJ970.js

Lines changed: 0 additions & 2 deletions
This file was deleted.

public/assets/bundle-FOTFJ970.js.map

Lines changed: 0 additions & 1 deletion
This file was deleted.

public/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,6 @@
6868

6969
gtag('config', 'G-0E805HG8JN');
7070
</script>
71-
<script src="assets/bundle-FOTFJ970.js"></script>
71+
<script src="assets/bundle-D2zYReHC.js"></script>
7272
</body>
7373
</html>

rollup.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export default {
4040
preventAssignment: true
4141
}),
4242
babel({
43-
presets: ["@babel/preset-react", "minify"],
43+
presets: ["@babel/preset-react"],
4444
plugins: ["@babel/plugin-transform-react-jsx"],
4545
babelHelpers: 'bundled'
4646
}),

src/history.jsx

Lines changed: 247 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,99 +1,258 @@
11
import React from 'react';
22
import { Notify } from 'notiflix/build/notiflix-notify-aio';
3+
import { version as uuidVersion } from 'uuid';
4+
5+
// Import constants
6+
import {
7+
TYPE_ULID,
8+
TYPE_BASE64,
9+
TYPE_HIGH_LOW,
10+
TYPE_UUID,
11+
TYPE_BYTES,
12+
typeDetector,
13+
} from './type-detector';
14+
15+
// Mapping of kind to human-readable type labels
16+
const TYPE_LABELS = {
17+
[TYPE_ULID]: 'ULID',
18+
[TYPE_BASE64]: 'Base64',
19+
[TYPE_HIGH_LOW]: 'HighLow',
20+
[TYPE_UUID]: 'UUID',
21+
[TYPE_BYTES]: 'Bytes',
22+
};
23+
24+
// Mapping of kind to Bulma color classes
25+
const TYPE_COLORS = {
26+
[TYPE_ULID]: 'is-primary',
27+
[TYPE_UUID]: 'is-success',
28+
[TYPE_BASE64]: 'is-warning',
29+
[TYPE_HIGH_LOW]: 'is-info',
30+
[TYPE_BYTES]: 'is-info',
31+
};
332

433
export default class HistoryComponent extends React.Component {
5-
/**
6-
* Constructor for the HistoryComponent.
7-
*
8-
* @param {Object} props - The properties passed to the component.
9-
*/
10-
constructor(props) {
11-
super(props);
12-
// Call the parent class constructor with the passed props
34+
/**
35+
* Copies the clicked text to the clipboard and displays a notification.
36+
*
37+
* @param {Event} e - The click event on an anchor tag.
38+
*/
39+
copy = (e) => {
40+
const text = e.target.innerText;
41+
42+
navigator.clipboard.writeText(text)
43+
.then(() => {
44+
Notify.success(`Text ${text} copied`);
45+
})
46+
.catch((error) => {
47+
Notify.failure(`Error copying text: ${error}`);
48+
});
49+
};
50+
51+
/**
52+
* Returns a human-readable label for the given kind.
53+
*
54+
* @param {number} kind - The kind value from typeDetector.
55+
* @returns {string} - Human-readable type name.
56+
*/
57+
getTypeLabel(kind) {
58+
return TYPE_LABELS[kind] || 'Unknown';
59+
}
60+
61+
/**
62+
* Returns a Bulma color class based on the kind.
63+
*
64+
* @param {number} kind - The kind value from typeDetector.
65+
* @returns {string} - Bulma color class.
66+
*/
67+
getTypeColor(kind) {
68+
return TYPE_COLORS[kind] || 'is-info';
69+
}
70+
71+
/**
72+
* Extracts timestamp from ULID.
73+
*
74+
* @param {string} ulid - ULID string.
75+
* @returns {string} - ISO date string.
76+
*/
77+
getTimestampFromULID(ulid) {
78+
const base32 = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
79+
const timestampPart = ulid.slice(0, 10);
80+
81+
let time = 0;
82+
for (let i = 0; i < timestampPart.length; i++) {
83+
const char = timestampPart[i];
84+
const index = base32.indexOf(char);
85+
if (index === -1) throw new Error(`Invalid ULID character: ${char} at position ${i}`);
86+
time = (time * 32) + index;
87+
}
88+
89+
return new Date(time).toISOString();
90+
}
91+
92+
/**
93+
* Extracts the timestamp from a UUID of version 1, 6, or 7.
94+
*
95+
* @param {string} uuid - The UUID string to extract the timestamp from.
96+
* @returns {string|null} - Returns the ISO date string if the UUID is of a supported version, otherwise returns null.
97+
*/
98+
getTimestampFromUUID(uuid) {
99+
const normalized = uuid.replace(/-/g, '').toLowerCase();
100+
101+
// Get the version of UUID
102+
const versionHex = normalized[12];
103+
const version = parseInt(versionHex, 16);
104+
105+
let timestampMs = null;
106+
107+
switch (version) {
108+
case 1: {
109+
const timeLowStr = normalized.substring(0, 8);
110+
const timeMidStr = normalized.substring(8, 12);
111+
const timeHiStr = normalized.substring(13, 16);
112+
113+
const uuidTime = ((BigInt(timeHiStr) << 32n) + (BigInt(timeMidStr) << 16n) + BigInt(timeLowStr)) * 10000n;
114+
115+
const GregorianToUnixOffsetMicroseconds = 12219292800000000n;
116+
timestampMs = Number((uuidTime - GregorianToUnixOffsetMicroseconds) / 1000n);
117+
break;
118+
}
119+
120+
case 6: {
121+
const timeHighStr = normalized.substring(0, 8);
122+
const timeMidStr = normalized.substring(8, 12);
123+
const timeLowStr = normalized.substring(13, 16) + normalized.substring(16, 20);
124+
125+
const timeHigh = BigInt('0x' + timeHighStr);
126+
const timeMid = BigInt('0x' + timeMidStr);
127+
const timeLow = BigInt('0x' + timeLowStr);
128+
129+
const totalTimestamp = (timeHigh << 28n) | (timeMid << 12n) | timeLow;
130+
131+
const GregorianToUnixOffsetMs = 12219292800000n;
132+
timestampMs = Number((totalTimestamp / 10000n) - GregorianToUnixOffsetMs);
133+
break;
134+
}
135+
136+
case 7: {
137+
const unixTimestamp = parseInt(normalized.substring(0, 12), 16);
138+
timestampMs = unixTimestamp;
139+
break;
140+
}
141+
142+
default:
143+
return null;
13144
}
14145

15-
/**
16-
* Copies the text of the clicked <a> tag to the clipboard and displays a success message.
17-
*
18-
* @param {Event} e - The event object containing the clicked <a> tag.
19-
*/
20-
copy = (e) => {
21-
// Get the text content of the clicked <a> tag
22-
const text = e.target.innerText;
23-
24-
// Copy the text to the clipboard
25-
navigator.clipboard.writeText(text)
26-
.then(() => {
27-
// Display a success message
28-
Notify.success('Text ' + text + ' copied');
29-
})
30-
.catch((error) => {
31-
// Display an error message if the copy operation fails
32-
Notify.failure('Error copying text: ' + error);
33-
});
146+
// Check timestamp validity
147+
if (!timestampMs || isNaN(timestampMs) || timestampMs < 0) return null;
148+
149+
const date = new Date(timestampMs);
150+
151+
return date.toISOString();
152+
}
153+
154+
/**
155+
* Processes input/output item to determine its type and optional timestamp.
156+
*
157+
* @param {string} value - Input or output value from history item.
158+
* @returns {{ type: string, timestamp: string | null }}
159+
*/
160+
processItem(value) {
161+
const kind = typeDetector(value);
162+
const fullType = this.getTypeLabel(kind);
163+
let timestamp = null;
164+
165+
try {
166+
if (kind === TYPE_UUID && uuidVersion(value)) {
167+
const version = uuidVersion(value);
168+
return {
169+
type: `UUID v${version}`,
170+
timestamp: this.getTimestampFromUUID(value),
171+
};
172+
} else if (kind === TYPE_ULID) {
173+
timestamp = this.getTimestampFromULID(value);
174+
}
175+
} catch (err) {
176+
// Ignore invalid formats
34177
}
35178

36-
/**
37-
* Render method for the HistoryComponent.
38-
*
39-
* Returns a navigation panel (<nav>) with a heading "History" and a list of items.
40-
* Each item is a panel block (<div>) with a field (<div>) containing two tags (<a>).
41-
*
42-
* @returns {JSX.Element} The rendered HistoryComponent.
43-
*/
44-
render() {
45-
// Destructure the props
46-
const { items, clearItems, isToggled } = this.props;
47-
48-
return (
49-
// Navigation panel
50-
<nav className={isToggled ? "panel is-dark" : "panel is-light"}>
51-
{/* Panel heading */}
52-
<p className="panel-heading">History</p>
53-
54-
{/* Clear history button */}
55-
<div className={items.length === 0 ? "panel-block is-hidden" : "panel-block"}>
56-
<button onClick={clearItems}
57-
className="button is-danger is-outlined is-fullwidth is-small">
58-
Clear the history
59-
</button>
179+
return {
180+
type: fullType,
181+
timestamp,
182+
};
183+
}
184+
185+
render() {
186+
const { items, clearItems, isToggled } = this.props;
187+
188+
return (
189+
<nav className={isToggled ? "panel is-dark" : "panel is-light"}>
190+
<p className="panel-heading">History</p>
191+
192+
{/* Clear history button */}
193+
<div className={items.length === 0 ? "panel-block is-hidden" : "panel-block"}>
194+
<button
195+
onClick={clearItems}
196+
className="button is-danger is-outlined is-fullwidth is-small"
197+
>
198+
Clear the history
199+
</button>
200+
</div>
201+
202+
{/* Items list */}
203+
{[...items].slice(0, 30).map(i => {
204+
const inputResult = this.processItem(i.input);
205+
const outputResult = this.processItem(i.output);
206+
207+
return (
208+
<div key={i.toString()} className="panel-block">
209+
<div className="field">
210+
{/* Output */}
211+
<div className="tags has-addons">
212+
<a
213+
href="javascript:"
214+
onClick={this.copy}
215+
className="tag is-link is-light is-clickable"
216+
data-tooltip={i.info}
217+
>
218+
{i.output}
219+
</a>
220+
<span className="tag is-rounded">{outputResult.type} (Output)</span>
60221
</div>
61222

62-
{/* List of items */}
63-
{ [...items].slice(0, 30).map(i => {
64-
// Panel block for each item
65-
return (
66-
<div key={i.toString()} className="panel-block">
67-
{/* Field containing two tags */}
68-
<div className="field">
69-
{/* Output tag */}
70-
<div className="tags">
71-
{/* Copy output to clipboard and display a success message */}
72-
<a href="javascript:"
73-
onClick={this.copy}
74-
className="tag is-link is-light"
75-
data-tooltip={i.info}>
76-
{/* Output text */}
77-
{ i.output }
78-
</a>
79-
</div>
80-
81-
{/* Input tag */}
82-
<div className="tags">
83-
{/* Copy input to clipboard and display a success message */}
84-
<a href="javascript:"
85-
onClick={this.copy}
86-
className="tag is-primary is-light"
87-
data-tooltip={i.info}>
88-
{/* Input text */}
89-
{ i.input }
90-
</a>
91-
</div>
92-
</div>
93-
</div>
94-
);
95-
}) }
96-
</nav>
97-
);
98-
}
223+
{/* Input */}
224+
<div className="tags has-addons">
225+
<a
226+
href="javascript:"
227+
onClick={this.copy}
228+
className={`tag ${this.getTypeColor(typeDetector(i.input))} is-clickable`}
229+
data-tooltip={i.info}
230+
>
231+
{i.input}
232+
</a>
233+
<span className="tag is-rounded">{inputResult.type} (Input)</span>
234+
</div>
235+
236+
{/* Timestamps */}
237+
{(inputResult.timestamp || outputResult.timestamp) && (
238+
<div className="mt-2">
239+
{inputResult.timestamp && (
240+
<p className="is-size-7 has-text-weight-normal mb-2">
241+
<strong>Input TS:</strong> {inputResult.timestamp}
242+
</p>
243+
)}
244+
{outputResult.timestamp && (
245+
<p className="is-size-7 has-text-weight-normal mb-2">
246+
<strong>Output TS:</strong> {outputResult.timestamp}
247+
</p>
248+
)}
249+
</div>
250+
)}
251+
</div>
252+
</div>
253+
);
254+
})}
255+
</nav>
256+
);
257+
}
99258
}

0 commit comments

Comments
 (0)