+
+
+
+
+ {/* Main Input Section */}
+
+
+
+
- );
- };
- return (
-
-
-
- {error && (
-
- {error}
-
- )}
+ }}>
+
+ Decimal value: {currentValue.toLocaleString()}
+
+
+
+ )}
+
+
-
-
- {renderBasePane('dec', 'Decimal', 'Enter decimal number...', 10)}
- {renderBasePane('hex', 'Hexadecimal', 'Enter hex number...', 16)}
-
-
- {renderBasePane('oct', 'Octal', 'Enter octal number...', 8)}
- {renderBasePane('bin', 'Binary', 'Enter binary number...', 2)}
-
-
+ {/* Results Grid */}
+
+
+ {bases.map((base) => {
+ if (base.id === inputBase) return null; // Skip current input base
+
+ const value = formatNumber(currentValue, base.base);
+ const displayValue = value || '-';
-
- {renderBasePane('custom', 'Custom', `Enter base ${customBase} number...`, customBase, true)}
+ return (
+
+
+
+
+ {base.label}
+
+
+ Base {base.base}
+
+
+
+
+
+
+ {base.prefix}{displayValue}
+
+
+ {!inputValue && (
+
+ e.g., {base.prefix}{base.example}
+
+ )}
+
+ );
+ })}
-
+
+
+ {/* Quick Tips */}
+
+
+
+ Common Values
+
+
+
+ {[
+ { dec: '255', hex: 'FF', bin: '11111111' },
+ { dec: '256', hex: '100', bin: '100000000' },
+ { dec: '1024', hex: '400', bin: '10000000000' },
+ { dec: '4096', hex: '1000', bin: '1000000000000' },
+ ].map((row, idx) => (
+
+ ))}
+
+
+
+
);
};
diff --git a/frontend/src/pages/NumberConverter/numberConverterReducer.js b/frontend/src/pages/NumberConverter/numberConverterReducer.js
new file mode 100644
index 0000000..242d799
--- /dev/null
+++ b/frontend/src/pages/NumberConverter/numberConverterReducer.js
@@ -0,0 +1,232 @@
+// Number Converter state management
+
+import { INPUT_MODES, INITIAL_STATE } from './constants';
+
+/**
+ * Action types
+ */
+export const ACTION_TYPES = {
+ SET_VALUE: 'SET_VALUE',
+ TOGGLE_BIT: 'TOGGLE_BIT',
+ SET_INPUT_MODE: 'SET_INPUT_MODE',
+ SET_CUSTOM_BASE: 'SET_CUSTOM_BASE',
+ SET_ERROR: 'SET_ERROR',
+ CLEAR_ERROR: 'CLEAR_ERROR',
+ APPLY_BITWISE_OP: 'APPLY_BITWISE_OP',
+ CLEAR_ALL: 'CLEAR_ALL',
+};
+
+/**
+ * Reducer function for Number Converter state
+ * @param {object} state - Current state
+ * @param {object} action - Action to apply
+ * @returns {object} New state
+ */
+export function numberConverterReducer(state = INITIAL_STATE, action) {
+ switch (action.type) {
+ case ACTION_TYPES.SET_VALUE:
+ return {
+ ...state,
+ value: action.payload.value,
+ errors: {}, // Clear all errors on successful value set
+ };
+
+ case ACTION_TYPES.TOGGLE_BIT: {
+ const { position } = action.payload;
+ // XOR with bit mask to toggle
+ const newValue = (state.value ^ (1 << position)) >>> 0;
+ return {
+ ...state,
+ value: newValue,
+ };
+ }
+
+ case ACTION_TYPES.SET_INPUT_MODE:
+ return {
+ ...state,
+ inputMode: action.payload.mode,
+ };
+
+ case ACTION_TYPES.SET_CUSTOM_BASE:
+ return {
+ ...state,
+ customBase: action.payload.base,
+ };
+
+ case ACTION_TYPES.SET_ERROR:
+ return {
+ ...state,
+ errors: {
+ ...state.errors,
+ [action.payload.field]: action.payload.error,
+ },
+ };
+
+ case ACTION_TYPES.CLEAR_ERROR: {
+ const newErrors = { ...state.errors };
+ delete newErrors[action.payload.field];
+ return {
+ ...state,
+ errors: newErrors,
+ };
+ }
+
+ case ACTION_TYPES.APPLY_BITWISE_OP: {
+ const { operation } = action.payload;
+ let newValue = state.value;
+
+ switch (operation) {
+ case 'shiftLeft':
+ newValue = ((state.value << 1) >>> 0);
+ break;
+ case 'shiftRight':
+ newValue = (state.value >>> 1);
+ break;
+ case 'not':
+ newValue = (~state.value >>> 0);
+ break;
+ case 'maskByte':
+ newValue = (state.value & 0xFF) >>> 0;
+ break;
+ case 'setLSB':
+ newValue = (state.value | 1) >>> 0;
+ break;
+ default:
+ break;
+ }
+
+ return {
+ ...state,
+ value: newValue,
+ };
+ }
+
+ case ACTION_TYPES.CLEAR_ALL:
+ return {
+ ...INITIAL_STATE,
+ };
+
+ default:
+ return state;
+ }
+}
+
+/**
+ * Action creator: Set the current value
+ * @param {number} value - New value
+ * @returns {object} Action object
+ */
+export function setValue(value) {
+ return {
+ type: ACTION_TYPES.SET_VALUE,
+ payload: { value },
+ };
+}
+
+/**
+ * Action creator: Toggle a bit at specific position
+ * @param {number} position - Bit position (0-31)
+ * @returns {object} Action object
+ */
+export function toggleBit(position) {
+ return {
+ type: ACTION_TYPES.TOGGLE_BIT,
+ payload: { position },
+ };
+}
+
+/**
+ * Action creator: Set the current input mode
+ * @param {string} mode - Input mode ('bin', 'oct', 'dec', 'hex', 'custom')
+ * @returns {object} Action object
+ */
+export function setInputMode(mode) {
+ return {
+ type: ACTION_TYPES.SET_INPUT_MODE,
+ payload: { mode },
+ };
+}
+
+/**
+ * Action creator: Set custom base
+ * @param {number} base - Custom base (2-36)
+ * @returns {object} Action object
+ */
+export function setCustomBase(base) {
+ return {
+ type: ACTION_TYPES.SET_CUSTOM_BASE,
+ payload: { base },
+ };
+}
+
+/**
+ * Action creator: Set an error for a specific field
+ * @param {string} field - Field name (e.g., 'hex', 'binary')
+ * @param {string} error - Error message
+ * @returns {object} Action object
+ */
+export function setError(field, error) {
+ return {
+ type: ACTION_TYPES.SET_ERROR,
+ payload: { field, error },
+ };
+}
+
+/**
+ * Action creator: Clear error for a specific field
+ * @param {string} field - Field name
+ * @returns {object} Action object
+ */
+export function clearError(field) {
+ return {
+ type: ACTION_TYPES.CLEAR_ERROR,
+ payload: { field },
+ };
+}
+
+/**
+ * Action creator: Apply a bitwise operation
+ * @param {string} operation - Operation name ('shiftLeft', 'shiftRight', 'not', 'maskByte', 'setLSB')
+ * @returns {object} Action object
+ */
+export function applyBitwiseOp(operation) {
+ return {
+ type: ACTION_TYPES.APPLY_BITWISE_OP,
+ payload: { operation },
+ };
+}
+
+/**
+ * Action creator: Clear all state (reset)
+ * @returns {object} Action object
+ */
+export function clearAll() {
+ return {
+ type: ACTION_TYPES.CLEAR_ALL,
+ };
+}
+
+/**
+ * Hook to handle input from conversion fields
+ * Parses input and dispatches appropriate actions
+ * @param {function} dispatch - Dispatch function
+ * @param {string} input - Raw input string
+ * @param {number} base - Base of the input
+ * @param {string} field - Field identifier
+ * @param {function} parseFn - Parse function for this base
+ */
+export function handleConversionInput(dispatch, input, base, field, parseFn) {
+ const result = parseFn(input);
+
+ if (result.error) {
+ // Set error but keep current value
+ dispatch(setError(field, result.error));
+ } else if (result.value !== null) {
+ // Valid value - update and clear error
+ dispatch(setValue(result.value));
+ dispatch(clearError(field));
+ } else {
+ // Empty input - just clear error
+ dispatch(clearError(field));
+ }
+}
diff --git a/frontend/src/pages/NumberConverter/utils.js b/frontend/src/pages/NumberConverter/utils.js
new file mode 100644
index 0000000..016fde0
--- /dev/null
+++ b/frontend/src/pages/NumberConverter/utils.js
@@ -0,0 +1,382 @@
+// Utility functions for number conversion and bit manipulation
+
+import { LIMITS, ERROR_MESSAGES, getValidCharsForBase } from './constants';
+
+/**
+ * Sanitize input string - trim whitespace and remove non-printable chars
+ * @param {string} input - Raw input
+ * @returns {string} Sanitized input
+ */
+export function sanitizeInput(input) {
+ if (!input || typeof input !== 'string') {
+ return '';
+ }
+ return input.trim().replace(/[\s\u0000-\u001F\u007F-\u009F]/g, '');
+}
+
+/**
+ * Check if input contains only valid characters for a base
+ * @param {string} input - Input to validate
+ * @param {number} base - Base (2-36)
+ * @returns {object} { valid: boolean, invalidChar: string|null }
+ */
+export function validateInputChars(input, base) {
+ const validChars = getValidCharsForBase(base);
+
+ for (const char of input) {
+ if (!validChars.includes(char)) {
+ return { valid: false, invalidChar: char };
+ }
+ }
+
+ return { valid: true, invalidChar: null };
+}
+
+/**
+ * Parse input string to 32-bit unsigned integer
+ * @param {string} input - Input string
+ * @param {number} base - Base (2-36)
+ * @returns {object} { value: number|null, error: string|null }
+ */
+export function parseInput(input, base) {
+ const sanitized = sanitizeInput(input);
+
+ if (sanitized === '') {
+ return { value: null, error: null }; // Empty is valid (no change)
+ }
+
+ // Check for negative sign
+ if (sanitized.startsWith('-')) {
+ return { value: null, error: ERROR_MESSAGES.NEGATIVE };
+ }
+
+ // Check for scientific notation
+ if (/[eE]/.test(sanitized)) {
+ return { value: null, error: ERROR_MESSAGES.PARSE_ERROR(base) };
+ }
+
+ // Validate characters
+ const { valid, invalidChar } = validateInputChars(sanitized, base);
+ if (!valid) {
+ return { value: null, error: ERROR_MESSAGES.INVALID_CHAR(invalidChar, base) };
+ }
+
+ // Parse the number
+ const parsed = parseInt(sanitized, base);
+
+ if (isNaN(parsed)) {
+ return { value: null, error: ERROR_MESSAGES.PARSE_ERROR(base) };
+ }
+
+ // Check for overflow and clamp
+ let value = parsed;
+ let error = null;
+
+ if (value < 0) {
+ return { value: null, error: ERROR_MESSAGES.NEGATIVE };
+ }
+
+ if (value > LIMITS.MAX_32BIT_DECIMAL) {
+ value = LIMITS.MAX_32BIT;
+ error = ERROR_MESSAGES.OVERFLOW;
+ }
+
+ // Ensure unsigned 32-bit
+ value = value >>> 0;
+
+ return { value, error };
+}
+
+/**
+ * Parse decimal input
+ * @param {string} input - Decimal string
+ * @returns {object} { value: number|null, error: string|null }
+ */
+export function parseDecimal(input) {
+ return parseInput(input, 10);
+}
+
+/**
+ * Parse hexadecimal input
+ * @param {string} input - Hex string (with or without 0x prefix)
+ * @returns {object} { value: number|null, error: string|null }
+ */
+export function parseHex(input) {
+ let sanitized = sanitizeInput(input);
+
+ // Remove 0x or 0X prefix if present
+ if (sanitized.toLowerCase().startsWith('0x')) {
+ sanitized = sanitized.slice(2);
+ }
+
+ return parseInput(sanitized, 16);
+}
+
+/**
+ * Parse binary input
+ * @param {string} input - Binary string (with or without 0b prefix)
+ * @returns {object} { value: number|null, error: string|null }
+ */
+export function parseBinary(input) {
+ let sanitized = sanitizeInput(input);
+
+ // Remove 0b or 0B prefix if present
+ if (sanitized.toLowerCase().startsWith('0b')) {
+ sanitized = sanitized.slice(2);
+ }
+
+ return parseInput(sanitized, 2);
+}
+
+/**
+ * Parse octal input
+ * @param {string} input - Octal string (with or without 0o prefix)
+ * @returns {object} { value: number|null, error: string|null }
+ */
+export function parseOctal(input) {
+ let sanitized = sanitizeInput(input);
+
+ // Remove 0o or 0O prefix if present
+ if (sanitized.toLowerCase().startsWith('0o')) {
+ sanitized = sanitized.slice(2);
+ }
+
+ return parseInput(sanitized, 8);
+}
+
+/**
+ * Parse input for custom base
+ * @param {string} input - Input string
+ * @param {number} base - Custom base (2-36)
+ * @returns {object} { value: number|null, error: string|null }
+ */
+export function parseCustomBase(input, base) {
+ if (base < 2 || base > 36) {
+ return { value: null, error: 'Base must be between 2 and 36' };
+ }
+ return parseInput(input, base);
+}
+
+/**
+ * Format number as decimal string
+ * @param {number} value - 32-bit unsigned integer
+ * @returns {string} Decimal representation
+ */
+export function formatDecimal(value) {
+ if (typeof value !== 'number' || isNaN(value)) {
+ return '';
+ }
+ // Ensure unsigned and format with thousand separators
+ const unsigned = value >>> 0;
+ return unsigned.toLocaleString('en-US');
+}
+
+/**
+ * Format number as hexadecimal string
+ * @param {number} value - 32-bit unsigned integer
+ * @returns {string} Hex representation (uppercase, no prefix)
+ */
+export function formatHex(value) {
+ if (typeof value !== 'number' || isNaN(value)) {
+ return '';
+ }
+ const unsigned = value >>> 0;
+ return unsigned.toString(16).toUpperCase();
+}
+
+/**
+ * Format number as binary string
+ * @param {number} value - 32-bit unsigned integer
+ * @returns {string} Binary representation (32 bits, grouped by 4)
+ */
+export function formatBinary(value) {
+ if (typeof value !== 'number' || isNaN(value)) {
+ return '';
+ }
+ const unsigned = value >>> 0;
+ const binary = unsigned.toString(2).padStart(32, '0');
+ // Group by 4 bits
+ return binary.match(/.{4}/g).join(' ');
+}
+
+/**
+ * Format number as octal string
+ * @param {number} value - 32-bit unsigned integer
+ * @returns {string} Octal representation
+ */
+export function formatOctal(value) {
+ if (typeof value !== 'number' || isNaN(value)) {
+ return '';
+ }
+ const unsigned = value >>> 0;
+ return unsigned.toString(8);
+}
+
+/**
+ * Format number as custom base string
+ * @param {number} value - 32-bit unsigned integer
+ * @param {number} base - Base (2-36)
+ * @returns {string} Formatted representation
+ */
+export function formatCustomBase(value, base) {
+ if (typeof value !== 'number' || isNaN(value)) {
+ return '';
+ }
+ if (base < 2 || base > 36) {
+ return '';
+ }
+ const unsigned = value >>> 0;
+ return unsigned.toString(base).toUpperCase();
+}
+
+/**
+ * Format number for display in specified base
+ * @param {number} value - 32-bit unsigned integer
+ * @param {number} base - Base (2-36)
+ * @returns {string} Formatted representation
+ */
+export function formatNumber(value, base) {
+ switch (base) {
+ case 2:
+ return formatBinary(value);
+ case 8:
+ return formatOctal(value);
+ case 10:
+ return formatDecimal(value);
+ case 16:
+ return formatHex(value);
+ default:
+ return formatCustomBase(value, base);
+ }
+}
+
+/**
+ * Get bit value at specific position
+ * @param {number} value - 32-bit unsigned integer
+ * @param {number} position - Bit position (0-31)
+ * @returns {number} 0 or 1
+ */
+export function getBit(value, position) {
+ if (position < 0 || position > 31) {
+ return 0;
+ }
+ return ((value >>> position) & 1);
+}
+
+/**
+ * Toggle bit at specific position
+ * @param {number} value - 32-bit unsigned integer
+ * @param {number} position - Bit position (0-31)
+ * @returns {number} New value with bit toggled
+ */
+export function toggleBit(value, position) {
+ if (position < 0 || position > 31) {
+ return value >>> 0;
+ }
+ return ((value ^ (1 << position)) >>> 0);
+}
+
+/**
+ * Set bit at specific position
+ * @param {number} value - 32-bit unsigned integer
+ * @param {number} position - Bit position (0-31)
+ * @returns {number} New value with bit set
+ */
+export function setBit(value, position) {
+ if (position < 0 || position > 31) {
+ return value >>> 0;
+ }
+ return ((value | (1 << position)) >>> 0);
+}
+
+/**
+ * Clear bit at specific position
+ * @param {number} value - 32-bit unsigned integer
+ * @param {number} position - Bit position (0-31)
+ * @returns {number} New value with bit cleared
+ */
+export function clearBit(value, position) {
+ if (position < 0 || position > 31) {
+ return value >>> 0;
+ }
+ return ((value & ~(1 << position)) >>> 0);
+}
+
+/**
+ * Shift left by n bits
+ * @param {number} value - 32-bit unsigned integer
+ * @param {number} n - Number of bits to shift
+ * @returns {number} Shifted value (32-bit)
+ */
+export function shiftLeft(value, n = 1) {
+ const shiftAmount = Math.max(0, Math.floor(n));
+ if (shiftAmount >= 32) {
+ return 0;
+ }
+ return ((value << shiftAmount) >>> 0);
+}
+
+/**
+ * Logical shift right by n bits
+ * @param {number} value - 32-bit unsigned integer
+ * @param {number} n - Number of bits to shift
+ * @returns {number} Shifted value (32-bit)
+ */
+export function shiftRight(value, n = 1) {
+ const shiftAmount = Math.max(0, Math.floor(n));
+ if (shiftAmount >= 32) {
+ return 0;
+ }
+ return (value >>> shiftAmount);
+}
+
+/**
+ * Bitwise NOT operation
+ * @param {number} value - 32-bit unsigned integer
+ * @returns {number} Inverted value (32-bit)
+ */
+export function bitwiseNot(value) {
+ return (~value >>> 0);
+}
+
+/**
+ * Bitwise AND with mask
+ * @param {number} value - 32-bit unsigned integer
+ * @param {number} mask - Mask value
+ * @returns {number} Result (32-bit)
+ */
+export function bitwiseAnd(value, mask) {
+ return ((value & mask) >>> 0);
+}
+
+/**
+ * Bitwise OR with mask
+ * @param {number} value - 32-bit unsigned integer
+ * @param {number} mask - Mask value
+ * @returns {number} Result (32-bit)
+ */
+export function bitwiseOr(value, mask) {
+ return ((value | mask) >>> 0);
+}
+
+/**
+ * Get byte value (0-255) at specific byte position
+ * @param {number} value - 32-bit unsigned integer
+ * @param {number} bytePos - Byte position (0-3, 0 = LSB)
+ * @returns {number} Byte value (0-255)
+ */
+export function getByte(value, bytePos) {
+ if (bytePos < 0 || bytePos > 3) {
+ return 0;
+ }
+ return ((value >>> (bytePos * 8)) & 0xFF);
+}
+
+/**
+ * Format byte as hex string
+ * @param {number} byteValue - Byte value (0-255)
+ * @returns {string} Hex string (2 digits, uppercase)
+ */
+export function formatByte(byteValue) {
+ return byteValue.toString(16).toUpperCase().padStart(2, '0');
+}
diff --git a/frontend/src/pages/RegExpTester.jsx b/frontend/src/pages/RegExpTester.jsx
index 01138a7..46bb312 100644
--- a/frontend/src/pages/RegExpTester.jsx
+++ b/frontend/src/pages/RegExpTester.jsx
@@ -1,5 +1,5 @@
import React, { useState, useEffect, useRef, useCallback } from 'react';
-import { TextInput, CopyButton } from '@carbon/react';
+import { Grid, Column, TextInput, CopyButton } from '@carbon/react';
import { ChevronDown } from '@carbon/icons-react';
import { ToolHeader, ToolSplitPane } from '../components/ToolUI';
import useLayoutToggle from '../hooks/useLayoutToggle';
@@ -857,16 +857,19 @@ export default function RegExpTester() {
};
return (
-
-
+
+
+
{/* Regex Input Row - Unified Input Group */}
+
+
{/* Error Display */}
{error && (
-
- {error}
-
+
+
+ {error}
+
+
)}
+
{/* Left Pane: Live Highlighted Input */}
-
+
+
);
}
diff --git a/frontend/src/pages/StringUtilities/index.jsx b/frontend/src/pages/StringUtilities/index.jsx
index 93015f2..81b289d 100644
--- a/frontend/src/pages/StringUtilities/index.jsx
+++ b/frontend/src/pages/StringUtilities/index.jsx
@@ -1,4 +1,5 @@
import React, { useState, useEffect } from 'react';
+import { Grid, Column } from '@carbon/react';
import { ToolHeader } from '../../components/ToolUI';
import useLayoutToggle from '../../hooks/useLayoutToggle';
import ToolLayoutToggle from '../../components/layout/ToolLayoutToggle';
@@ -48,41 +49,37 @@ export default function StringUtilities() {
};
return (
-
-
+
-
+
-
-
- {layout.showToggle && (
-
- )}
-
+
+
+
+ {layout.showToggle && (
+
+ )}
+
+
- {renderPane()}
-
+
+ {renderPane()}
+
+
);
}
diff --git a/frontend/src/pages/TextConverter/index.jsx b/frontend/src/pages/TextConverter/index.jsx
index dea7fe4..d6ff595 100644
--- a/frontend/src/pages/TextConverter/index.jsx
+++ b/frontend/src/pages/TextConverter/index.jsx
@@ -1,4 +1,5 @@
import React, { useState, useEffect, useCallback } from 'react';
+import { Grid, Column } from '@carbon/react';
import { ToolHeader, ToolPane, ToolSplitPane } from '../../components/ToolUI';
import useLayoutToggle from '../../hooks/useLayoutToggle';
import ConversionControls from './components/ConversionControls';
@@ -164,46 +165,55 @@ export default function TextBasedConverter() {
const isImageOutput = !isAllHashes && isBase64Image(output);
return (
-
-
+
+
+
- {
- setCategory(cat);
- setMethod(meth);
- }}
- customTags={customTags}
- />
+
+ {
+ setCategory(cat);
+ setMethod(meth);
+ }}
+ customTags={customTags}
+ />
+
- {
- setCategory(c);
- setMethod(CONVERTER_MAP[c][0]);
- }}
- method={method}
- setMethod={setMethod}
- subMode={subMode}
- setSubMode={setSubMode}
- layout={layout}
- autoRun={config.autoRun}
- setAutoRun={(val) => updateConfig({ autoRun: val })}
- onConvert={handleConvert}
- isAllHashes={isAllHashes}
- onAddQuickAction={addCurrentToQuickActions}
- isCurrentInQuickActions={isCurrentInQuickActions()}
- />
+
+ {
+ setCategory(c);
+ setMethod(CONVERTER_MAP[c][0]);
+ }}
+ method={method}
+ setMethod={setMethod}
+ subMode={subMode}
+ setSubMode={setSubMode}
+ layout={layout}
+ autoRun={config.autoRun}
+ setAutoRun={(val) => updateConfig({ autoRun: val })}
+ onConvert={handleConvert}
+ isAllHashes={isAllHashes}
+ onAddQuickAction={addCurrentToQuickActions}
+ isCurrentInQuickActions={isCurrentInQuickActions()}
+ />
+
{showConfig && (
-
+
+
+
)}
-
+
+
)}
-
+
+
);
}
diff --git a/frontend/src/pages/TextDiffChecker.jsx b/frontend/src/pages/TextDiffChecker.jsx
index c04342d..288bb02 100644
--- a/frontend/src/pages/TextDiffChecker.jsx
+++ b/frontend/src/pages/TextDiffChecker.jsx
@@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react';
import * as Diff from 'diff';
-import { Button, ContentSwitcher, Switch } from '@carbon/react';
+import { Grid, Column, Button, ContentSwitcher, Switch } from '@carbon/react';
import { Compare, Renew } from '@carbon/icons-react';
import { ToolHeader, ToolControls, ToolPane, ToolSplitPane } from '../components/ToolUI';
import useLayoutToggle from '../hooks/useLayoutToggle';
@@ -147,16 +147,19 @@ export default function TextDiffChecker() {
}, [oldText, newText, diffMode]);
return (
-
-
+
+
+
-
+
+
-
-
- setOldText(e.target.value)}
- placeholder="Paste original text..."
- />
- setNewText(e.target.value)}
- placeholder="Paste new text..."
- />
-
-
-
-
-
-
+
+
+
+
+
+
+ setOldText(e.target.value)}
+ placeholder="Paste original text..."
+ />
+ setNewText(e.target.value)}
+ placeholder="Paste new text..."
+ />
+
+
+
+
+
+
+
+
);
}
diff --git a/frontend/src/utils/storage.js b/frontend/src/utils/storage.js
new file mode 100644
index 0000000..80b6f34
--- /dev/null
+++ b/frontend/src/utils/storage.js
@@ -0,0 +1,77 @@
+/**
+ * Storage utility for persisting data in localStorage
+ * Works in both browser and Wails environments
+ */
+
+const storage = {
+ /**
+ * Gets a value from localStorage
+ * @param {string} key - The key to retrieve
+ * @returns {any|null} - The parsed value or null if not found or on error
+ */
+ get(key) {
+ try {
+ const item = window.localStorage.getItem(key);
+ if (item === null) return null;
+ return JSON.parse(item);
+ } catch (error) {
+ console.error(`Error getting item from localStorage: ${key}`, error);
+ return null;
+ }
+ },
+
+ /**
+ * Sets a value in localStorage
+ * @param {string} key - The key to set
+ * @param {any} value - The value to store (will be JSON stringified)
+ * @returns {boolean} - True if successful, false on error
+ */
+ set(key, value) {
+ try {
+ window.localStorage.setItem(key, JSON.stringify(value));
+ return true;
+ } catch (error) {
+ console.error(`Error setting item in localStorage: ${key}`, error);
+ return false;
+ }
+ },
+
+ /**
+ * Gets a value and parses it as a JSON array
+ * @param {string} key - The key to retrieve
+ * @returns {Array} - The parsed array or empty array if not found/invalid
+ */
+ getArray(key) {
+ try {
+ const item = window.localStorage.getItem(key);
+ if (item === null) return [];
+ const parsed = JSON.parse(item);
+ return Array.isArray(parsed) ? parsed : [];
+ } catch (error) {
+ console.error(`Error getting array from localStorage: ${key}`, error);
+ return [];
+ }
+ },
+
+ /**
+ * Stringifies and saves an array
+ * @param {string} key - The key to set
+ * @param {Array} array - The array to store
+ * @returns {boolean} - True if successful, false on error
+ */
+ setArray(key, array) {
+ try {
+ if (!Array.isArray(array)) {
+ console.error(`Value is not an array: ${key}`);
+ return false;
+ }
+ window.localStorage.setItem(key, JSON.stringify(array));
+ return true;
+ } catch (error) {
+ console.error(`Error setting array in localStorage: ${key}`, error);
+ return false;
+ }
+ }
+};
+
+export default storage;
diff --git a/internal/datetimeconverter/timezone.go b/internal/datetimeconverter/timezone.go
index f6e9001..c8dd871 100644
--- a/internal/datetimeconverter/timezone.go
+++ b/internal/datetimeconverter/timezone.go
@@ -1,7 +1,6 @@
package datetimeconverter
import (
- "fmt"
"os"
"strings"
)
@@ -35,9 +34,9 @@ func readTimezonesFromFile(path string, timezones *[]TimezoneInfo) {
tzAbbr := (path + "/" + f.Name())[1:]
label := tzAbbr
- if strings.Contains(tzAbbr, "/") {
- label = fmt.Sprintf("%s (%s)", f.Name(), tzAbbr)
- }
+ // if strings.Contains(tzAbbr, "/") {
+ // label = fmt.Sprintf("%s (%s)", f.Name(), tzAbbr)
+ // }
*timezones = append(*timezones, TimezoneInfo{
Label: label,
diff --git a/vite.config.js b/vite.config.js
index 6ce8f76..96bd637 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -6,6 +6,10 @@ import path from 'path';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), wails("./bindings")],
+ server: {
+ port: 3000,
+ historyApiFallback: true
+ },
css: {
preprocessorOptions: {
scss: {