-
diff --git a/frontend/src/pages/DateTimeConverter/index.jsx b/frontend/src/pages/DateTimeConverter/index.jsx
new file mode 100644
index 0000000..61ab2a7
--- /dev/null
+++ b/frontend/src/pages/DateTimeConverter/index.jsx
@@ -0,0 +1,353 @@
+import React, { useState, useEffect } from 'react';
+import { Button, TextInput, Tag, Tile, CopyButton, ComboBox } from '@carbon/react';
+import {
+ Time, Sun, Moon,
+ Sunrise,
+ SendToBack,
+ Calendar,
+ Clean
+} from '@carbon/icons-react';
+import { ToolHeader, ToolControls } from '../../components/ToolUI';
+
+// Output formats
+const OUTPUT_FORMATS = [
+ { id: 'iso', label: 'ISO 8601' },
+ { id: 'rfc2822', label: 'RFC 2822' },
+ { id: 'sql', label: 'SQL DateTime' },
+ { id: 'us', label: 'US Format' },
+ { id: 'eu', label: 'EU Format' },
+ { id: 'compact', label: 'Compact' },
+];
+
+// Timezones
+const TIMEZONES = [
+ { id: 'local', label: 'Local Time' },
+ { id: 'UTC', label: 'UTC' },
+ { id: 'America/New_York', label: 'New York' },
+ { id: 'America/Chicago', label: 'Chicago' },
+ { id: 'America/Denver', label: 'Denver' },
+ { id: 'America/Los_Angeles', label: 'Los Angeles' },
+ { id: 'Europe/London', label: 'London' },
+ { id: 'Europe/Paris', label: 'Paris' },
+ { id: 'Asia/Kolkata', label: 'India' },
+ { id: 'Asia/Tokyo', label: 'Tokyo' },
+ { id: 'Australia/Sydney', label: 'Sydney' },
+];
+
+// Helper for presets
+const toSQLFormat = (d) => {
+ const pad = (n) => n.toString().padStart(2, '0');
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
+};
+
+// Presets
+const PRESETS = [
+ { id: 'now', label: 'Now', getValue: () => toSQLFormat(new Date()), icon: Time },
+ {
+ id: 'startofday', label: 'Start of Day', getValue: () => {
+ const d = new Date();
+ d.setHours(0, 0, 0, 0);
+ return toSQLFormat(d);
+ },
+ icon: Sun,
+ },
+ {
+ id: 'endofday', label: 'End of Day', getValue: () => {
+ const d = new Date();
+ d.setHours(23, 59, 59, 0);
+ return toSQLFormat(d);
+ },
+ icon: Moon
+ },
+ { id: 'tomorrow', label: 'Tomorrow', getValue: () => toSQLFormat(new Date(Date.now() + 86400000)), icon: Sunrise },
+ { id: 'yesterday', label: 'Yesterday', getValue: () => toSQLFormat(new Date(Date.now() - 86400000)), icon: SendToBack },
+ { id: 'nextweek', label: 'Next Week', getValue: () => toSQLFormat(new Date(Date.now() + 604800000)), icon: Calendar },
+];
+
+// Parse input to Date object
+function parseInput(input) {
+ if (!input || !input.trim()) return null;
+
+ const trimmed = input.trim();
+
+ // Try as timestamp (numeric)
+ if (/^\d+$/.test(trimmed)) {
+ const ts = parseInt(trimmed, 10);
+ const len = trimmed.length;
+
+ if (len === 10) {
+ return new Date(ts * 1000);
+ } else if (len === 13) {
+ return new Date(ts);
+ } else if (len === 16) {
+ return new Date(ts / 1000);
+ } else if (len === 19) {
+ return new Date(ts / 1000000);
+ } else if (ts > 1000000000) {
+ return new Date(ts * 1000);
+ }
+ }
+
+ // Try as date string
+ const date = new Date(trimmed);
+ if (!isNaN(date.getTime())) {
+ return date;
+ }
+
+ return null;
+}
+
+// Helper to get date object shifted to target timezone (so UTC getters return target time)
+function getShiftedDate(date, timezone) {
+ let year, month, day, hour, minute, second;
+
+ if (timezone === 'local') {
+ year = date.getFullYear();
+ month = date.getMonth();
+ day = date.getDate();
+ hour = date.getHours();
+ minute = date.getMinutes();
+ second = date.getSeconds();
+ } else {
+ try {
+ const parts = new Intl.DateTimeFormat('en-US', {
+ timeZone: timezone,
+ year: 'numeric', month: 'numeric', day: 'numeric',
+ hour: 'numeric', minute: 'numeric', second: 'numeric',
+ hour12: false
+ }).formatToParts(date);
+ const p = {};
+ parts.forEach(({ type, value }) => p[type] = value);
+ year = parseInt(p.year, 10);
+ month = parseInt(p.month, 10) - 1;
+ day = parseInt(p.day, 10);
+ hour = parseInt(p.hour, 10) % 24;
+ minute = parseInt(p.minute, 10);
+ second = parseInt(p.second, 10);
+ } catch (e) {
+ return date;
+ }
+ }
+
+ return new Date(Date.UTC(year, month, day, hour, minute, second, date.getMilliseconds()));
+}
+
+// Format date according to format
+function formatDate(date, formatId, timezone) {
+ if (!date || isNaN(date.getTime())) return '';
+
+ const d = getShiftedDate(date, timezone);
+ const pad = (n) => n.toString().padStart(2, '0');
+ const year = d.getUTCFullYear();
+ const month = pad(d.getUTCMonth() + 1);
+ const day = pad(d.getUTCDate());
+ const hours = pad(d.getUTCHours());
+ const minutes = pad(d.getUTCMinutes());
+ const seconds = pad(d.getUTCSeconds());
+
+ switch (formatId) {
+ case 'iso':
+ return d.toISOString();
+ case 'rfc2822':
+ return d.toUTCString();
+ case 'sql':
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
+ case 'us':
+ return `${month}/${day}/${year} ${hours}:${minutes}:${seconds}`;
+ case 'eu':
+ return `${day}/${month}/${year} ${hours}:${minutes}:${seconds}`;
+ case 'compact':
+ return `${year}${month}${day}-${hours}${minutes}${seconds}`;
+ default:
+ return d.toISOString();
+ }
+}
+
+// Calculate relative time
+function getRelativeTime(date) {
+ if (!date || isNaN(date.getTime())) return '';
+
+ const now = new Date();
+ const diff = date.getTime() - now.getTime();
+ const absDiff = Math.abs(diff);
+ const isFuture = diff > 0;
+
+ const seconds = Math.floor(absDiff / 1000);
+ const minutes = Math.floor(seconds / 60);
+ const hours = Math.floor(minutes / 60);
+ const days = Math.floor(hours / 24);
+
+ let result = '';
+ if (days > 0) result += `${days} day${days > 1 ? 's' : ''} `;
+ else if (hours > 0) result += `${hours} hour${hours > 1 ? 's' : ''} `;
+ else if (minutes > 0) result += `${minutes} minute${minutes > 1 ? 's' : ''} `;
+ else result += `${seconds} second${seconds > 1 ? 's' : ''} `;
+
+ return isFuture ? `in ${result}` : `${result}ago`;
+}
+
+export default function DateTimeConverter() {
+ // Main input
+ const [input, setInput] = useState('');
+ const [outputFormat, setOutputFormat] = useState('iso');
+ const [outputTimezone, setOutputTimezone] = useState('local');
+ const [timezone, setTimezone] = useState('local');
+
+ // Parsed result
+ const [parsedDate, setParsedDate] = useState(null);
+ const [error, setError] = useState(null);
+
+ // Parse input
+ useEffect(() => {
+ const date = parseInput(input);
+ if (date && !isNaN(date.getTime())) {
+ setParsedDate(date);
+ setError(null);
+ } else if (input.trim()) {
+ setError('Invalid date or timestamp');
+ setParsedDate(null);
+ } else {
+ setError(null);
+ setParsedDate(null);
+ }
+ }, [input]);
+
+ const copyToClipboard = (text) => {
+ if (text) navigator.clipboard.writeText(text);
+ };
+
+ const handlePreset = (preset) => {
+ setInput(preset.getValue().toString());
+ };
+
+ return (
+
+
+
+ {/* Main Input Section */}
+
+ {/* Quick Presets */}
+
+
+ {PRESETS.map((preset) => (
+
+ ))}
+
+
+ setInput(e.target.value)}
+ placeholder="e.g., 1738412345, 2026-02-01T12:24:05Z, 02/01/2026..."
+ style={{ fontFamily: "'IBM Plex Mono', monospace", minWidth: '350px' }}
+ />
+
+ item ? item.label : ''}
+ selectedItem={TIMEZONES.find(t => t.id === timezone)}
+ onChange={({ selectedItem }) => selectedItem && setTimezone(selectedItem.id)}
+ style={{ minWidth: '250px' }}
+ />
+
+
+
+
+
+
+ {/* Settings */}
+
+ item ? item.label : ''}
+ selectedItem={OUTPUT_FORMATS.find(f => f.id === outputFormat)}
+ onChange={({ selectedItem }) => selectedItem && setOutputFormat(selectedItem.id)}
+ style={{ minWidth: '250px' }}
+ />
+ item ? item.label : ''}
+ selectedItem={TIMEZONES.find(t => t.id === timezone)}
+ onChange={({ selectedItem }) => selectedItem && setOutputTimezone(selectedItem.id)}
+ style={{ minWidth: '250px' }}
+ defaultValue={'local'}
+ />
+
+
+ {/* Error */}
+ {error &&
{error}}
+
+ {/* Main Result */}
+ {parsedDate && (
+
+
+
+
+ {formatDate(parsedDate, outputFormat, outputTimezone)}
+
+ copyToClipboard(formatDate(parsedDate, outputFormat, outputTimezone))} size="sm" />
+
+
+
+ {getRelativeTime(parsedDate)}
+ Unix: {Math.floor(parsedDate.getTime() / 1000)}
+ âĒ
+ Unix (ms): {parsedDate.getTime()}
+
+
+
+ )}
+
+ {/* All Formats */}
+ {parsedDate && (
+
+ {OUTPUT_FORMATS.map((fmt) => (
+
+
+ {fmt.label}
+
+
+
+ {formatDate(parsedDate, fmt.id, timezone)}
+
+ copyToClipboard(formatDate(parsedDate, fmt.id, timezone))} size="sm" />
+
+
+ ))}
+
+ )}
+
+ );
+}
diff --git a/src/pages/JwtDebugger/components/JwtDecode.jsx b/frontend/src/pages/JwtDebugger/components/JwtDecode.jsx
similarity index 96%
rename from src/pages/JwtDebugger/components/JwtDecode.jsx
rename to frontend/src/pages/JwtDebugger/components/JwtDecode.jsx
index a90e373..132661a 100644
--- a/src/pages/JwtDebugger/components/JwtDecode.jsx
+++ b/frontend/src/pages/JwtDebugger/components/JwtDecode.jsx
@@ -1,3 +1,4 @@
+import React, { useCallback } from 'react';
import { ToolLayout, ToolTextArea, ToolInputGroup } from '../../../components/ToolUI';
import { actions } from '../jwtReducer';
import { ErrorMessage, SuccessMessage } from './StatusMessages';
@@ -5,8 +6,7 @@ import SignatureVerification from './SignatureVerification';
import { Button } from '@carbon/react';
import { MagicWand } from '@carbon/icons-react';
import { EXAMPLE_SECRET } from '../jwtUtils';
-import { useCallback } from 'react';
-import { Backend } from '../../../utils/backendBridge';
+import { JWTService } from '../../../../bindings/devtoolbox/service';
export default function JwtDecode({ state, dispatch, layout, verifySignature }) {
// Tab change handlers
@@ -23,7 +23,7 @@ export default function JwtDecode({ state, dispatch, layout, verifySignature })
};
try {
- const response = await Backend.JWTService.Encode(
+ const response = await JWTService.Encode(
JSON.stringify(header),
JSON.stringify(payload),
'HS256',
diff --git a/src/pages/JwtDebugger/components/JwtEncode.jsx b/frontend/src/pages/JwtDebugger/components/JwtEncode.jsx
similarity index 100%
rename from src/pages/JwtDebugger/components/JwtEncode.jsx
rename to frontend/src/pages/JwtDebugger/components/JwtEncode.jsx
diff --git a/src/pages/JwtDebugger/components/ModeTabBar.jsx b/frontend/src/pages/JwtDebugger/components/ModeTabBar.jsx
similarity index 100%
rename from src/pages/JwtDebugger/components/ModeTabBar.jsx
rename to frontend/src/pages/JwtDebugger/components/ModeTabBar.jsx
diff --git a/src/pages/JwtDebugger/components/SignatureVerification.jsx b/frontend/src/pages/JwtDebugger/components/SignatureVerification.jsx
similarity index 100%
rename from src/pages/JwtDebugger/components/SignatureVerification.jsx
rename to frontend/src/pages/JwtDebugger/components/SignatureVerification.jsx
diff --git a/src/pages/JwtDebugger/components/StatusMessages.jsx b/frontend/src/pages/JwtDebugger/components/StatusMessages.jsx
similarity index 100%
rename from src/pages/JwtDebugger/components/StatusMessages.jsx
rename to frontend/src/pages/JwtDebugger/components/StatusMessages.jsx
diff --git a/src/pages/JwtDebugger/index.jsx b/frontend/src/pages/JwtDebugger/index.jsx
similarity index 93%
rename from src/pages/JwtDebugger/index.jsx
rename to frontend/src/pages/JwtDebugger/index.jsx
index d66e4e8..b848c43 100644
--- a/src/pages/JwtDebugger/index.jsx
+++ b/frontend/src/pages/JwtDebugger/index.jsx
@@ -1,4 +1,4 @@
-import React, { useReducer, useCallback } from 'react';
+import React, { useEffect, useReducer, useCallback } from 'react';
import { ToolHeader } from '../../components/ToolUI';
import useLayoutToggle from '../../hooks/useLayoutToggle';
import ToolLayoutToggle from '../../components/layout/ToolLayoutToggle';
@@ -6,7 +6,7 @@ import { jwtReducer, initialState, actions } from './jwtReducer';
import ModeTabBar from './components/ModeTabBar';
import JwtDecode from './components/JwtDecode';
import JwtEncode from './components/JwtEncode';
-import { Backend } from '../../utils/backendBridge';
+import { JWTService } from '../../../bindings/devtoolbox/service';
export default function JwtDebugger() {
const [state, dispatch] = useReducer(jwtReducer, initialState);
@@ -20,7 +20,7 @@ export default function JwtDebugger() {
});
// Decode JWT when token changes (using Go backend)
- React.useEffect(() => {
+ useEffect(() => {
if (!state.token.trim()) {
dispatch(actions.setDecoded({ header: null, payload: null, signature: '', error: '', isValid: null }));
return;
@@ -29,7 +29,7 @@ export default function JwtDebugger() {
// Call Go backend for decoding
const decodeToken = async () => {
try {
- const response = await Backend.JWTService.Decode(state.token);
+ const response = await JWTService.Decode(state.token);
dispatch(actions.setDecoded({
header: response.header,
@@ -66,7 +66,7 @@ export default function JwtDebugger() {
try {
// Call Go backend for verification
- const response = await Backend.JWTService.Verify(state.token, state.secret, state.encoding);
+ const response = await JWTService.Verify(state.token, state.secret, state.encoding);
dispatch(actions.setValidation(
response.error ? response.error : response.validationMessage,
@@ -85,7 +85,7 @@ export default function JwtDebugger() {
}
try {
- const response = await Backend.JWTService.Encode(
+ const response = await JWTService.Encode(
state.headerInput,
state.payloadInput,
state.algorithm,
diff --git a/src/pages/JwtDebugger/jwtReducer.js b/frontend/src/pages/JwtDebugger/jwtReducer.js
similarity index 100%
rename from src/pages/JwtDebugger/jwtReducer.js
rename to frontend/src/pages/JwtDebugger/jwtReducer.js
diff --git a/src/pages/JwtDebugger/jwtUtils.js b/frontend/src/pages/JwtDebugger/jwtUtils.js
similarity index 100%
rename from src/pages/JwtDebugger/jwtUtils.js
rename to frontend/src/pages/JwtDebugger/jwtUtils.js
diff --git a/src/pages/NumberConverter/index.jsx b/frontend/src/pages/NumberConverter/index.jsx
similarity index 100%
rename from src/pages/NumberConverter/index.jsx
rename to frontend/src/pages/NumberConverter/index.jsx
diff --git a/src/pages/RegExpTester.jsx b/frontend/src/pages/RegExpTester.jsx
similarity index 100%
rename from src/pages/RegExpTester.jsx
rename to frontend/src/pages/RegExpTester.jsx
diff --git a/src/pages/StringUtilities/components/CaseConverterPane.jsx b/frontend/src/pages/StringUtilities/components/CaseConverterPane.jsx
similarity index 100%
rename from src/pages/StringUtilities/components/CaseConverterPane.jsx
rename to frontend/src/pages/StringUtilities/components/CaseConverterPane.jsx
diff --git a/src/pages/StringUtilities/components/InspectorPane.jsx b/frontend/src/pages/StringUtilities/components/InspectorPane.jsx
similarity index 100%
rename from src/pages/StringUtilities/components/InspectorPane.jsx
rename to frontend/src/pages/StringUtilities/components/InspectorPane.jsx
diff --git a/src/pages/StringUtilities/components/ModeTabBar.jsx b/frontend/src/pages/StringUtilities/components/ModeTabBar.jsx
similarity index 100%
rename from src/pages/StringUtilities/components/ModeTabBar.jsx
rename to frontend/src/pages/StringUtilities/components/ModeTabBar.jsx
diff --git a/src/pages/StringUtilities/components/SortDedupePane.jsx b/frontend/src/pages/StringUtilities/components/SortDedupePane.jsx
similarity index 100%
rename from src/pages/StringUtilities/components/SortDedupePane.jsx
rename to frontend/src/pages/StringUtilities/components/SortDedupePane.jsx
diff --git a/src/pages/StringUtilities/index.jsx b/frontend/src/pages/StringUtilities/index.jsx
similarity index 100%
rename from src/pages/StringUtilities/index.jsx
rename to frontend/src/pages/StringUtilities/index.jsx
diff --git a/src/pages/StringUtilities/strings.js b/frontend/src/pages/StringUtilities/strings.js
similarity index 100%
rename from src/pages/StringUtilities/strings.js
rename to frontend/src/pages/StringUtilities/strings.js
diff --git a/src/pages/TextConverter/components/CommonTags.jsx b/frontend/src/pages/TextConverter/components/CommonTags.jsx
similarity index 100%
rename from src/pages/TextConverter/components/CommonTags.jsx
rename to frontend/src/pages/TextConverter/components/CommonTags.jsx
diff --git a/src/pages/TextConverter/components/ConfigurationPane.jsx b/frontend/src/pages/TextConverter/components/ConfigurationPane.jsx
similarity index 100%
rename from src/pages/TextConverter/components/ConfigurationPane.jsx
rename to frontend/src/pages/TextConverter/components/ConfigurationPane.jsx
diff --git a/src/pages/TextConverter/components/ConversionControls.jsx b/frontend/src/pages/TextConverter/components/ConversionControls.jsx
similarity index 99%
rename from src/pages/TextConverter/components/ConversionControls.jsx
rename to frontend/src/pages/TextConverter/components/ConversionControls.jsx
index 7b717a2..f107dd0 100644
--- a/src/pages/TextConverter/components/ConversionControls.jsx
+++ b/frontend/src/pages/TextConverter/components/ConversionControls.jsx
@@ -1,3 +1,4 @@
+import React from 'react';
import { Button, Dropdown, Toggle, RadioButtonGroup, RadioButton } from '@carbon/react';
import { ToolLayoutToggle } from '../../../components/ToolUI';
import { ArrowsHorizontal, Play, Add, Checkmark } from '@carbon/icons-react';
diff --git a/src/pages/TextConverter/components/ImageOutput.jsx b/frontend/src/pages/TextConverter/components/ImageOutput.jsx
similarity index 100%
rename from src/pages/TextConverter/components/ImageOutput.jsx
rename to frontend/src/pages/TextConverter/components/ImageOutput.jsx
diff --git a/src/pages/TextConverter/components/MultiHashOutput.jsx b/frontend/src/pages/TextConverter/components/MultiHashOutput.jsx
similarity index 100%
rename from src/pages/TextConverter/components/MultiHashOutput.jsx
rename to frontend/src/pages/TextConverter/components/MultiHashOutput.jsx
diff --git a/src/pages/TextConverter/constants.js b/frontend/src/pages/TextConverter/constants.js
similarity index 100%
rename from src/pages/TextConverter/constants.js
rename to frontend/src/pages/TextConverter/constants.js
diff --git a/src/pages/TextConverter/index.jsx b/frontend/src/pages/TextConverter/index.jsx
similarity index 97%
rename from src/pages/TextConverter/index.jsx
rename to frontend/src/pages/TextConverter/index.jsx
index 4fef414..d3e48d2 100644
--- a/src/pages/TextConverter/index.jsx
+++ b/frontend/src/pages/TextConverter/index.jsx
@@ -1,9 +1,6 @@
import React, { useState, useEffect, useCallback } from 'react';
-import { Button } from '@carbon/react';
-import { ArrowsHorizontal } from '@carbon/icons-react';
import { ToolHeader, ToolPane, ToolSplitPane } from '../../components/ToolUI';
import useLayoutToggle from '../../hooks/useLayoutToggle';
-import { Backend } from '../../utils/backendBridge';
import ConversionControls from './components/ConversionControls';
import ConfigurationPane from './components/ConfigurationPane';
import MultiHashOutput from './components/MultiHashOutput';
@@ -20,6 +17,7 @@ import {
PLACEHOLDERS,
LAYOUT
} from './strings';
+import { ConversionService } from '../../../bindings/devtoolbox/service';
export default function TextBasedConverter() {
// Persistent state initialization
@@ -93,14 +91,14 @@ export default function TextBasedConverter() {
// Add current selection to quick actions
const addCurrentToQuickActions = useCallback(() => {
if (isCurrentInQuickActions()) return;
-
+
const newTag = {
id: `${category}-${method}`.toLowerCase().replace(/[^a-z0-9]/g, '-'),
category,
method,
label: `${category} - ${method}`
};
-
+
setCustomTags(prev => [...prev, newTag]);
}, [category, method, isCurrentInQuickActions]);
@@ -128,7 +126,7 @@ export default function TextBasedConverter() {
try {
// Include subMode in backend request
const backendConfig = { ...cfg, subMode: sub };
- const result = await Backend.ConversionService.Convert(text, cat, meth, backendConfig);
+ const result = await ConversionService.Convert(text, cat, meth, backendConfig);
setOutput(result);
setError('');
} catch (err) {
diff --git a/src/pages/TextConverter/strings.js b/frontend/src/pages/TextConverter/strings.js
similarity index 100%
rename from src/pages/TextConverter/strings.js
rename to frontend/src/pages/TextConverter/strings.js
diff --git a/src/pages/TextDiffChecker.jsx b/frontend/src/pages/TextDiffChecker.jsx
similarity index 100%
rename from src/pages/TextDiffChecker.jsx
rename to frontend/src/pages/TextDiffChecker.jsx
diff --git a/src/style.css b/frontend/src/style.css
similarity index 100%
rename from src/style.css
rename to frontend/src/style.css
diff --git a/src/utils/inputUtils.js b/frontend/src/utils/inputUtils.js
similarity index 100%
rename from src/utils/inputUtils.js
rename to frontend/src/utils/inputUtils.js
diff --git a/src/utils/layoutUtils.js b/frontend/src/utils/layoutUtils.js
similarity index 100%
rename from src/utils/layoutUtils.js
rename to frontend/src/utils/layoutUtils.js
diff --git a/go.mod b/go.mod
index 9c44fae..7633a9e 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
-module dev-toolbox
+module devtoolbox
-go 1.24.0
+go 1.25
require (
github.com/boombuler/barcode v1.1.0
@@ -11,37 +11,77 @@ require (
github.com/itchyny/gojq v0.12.18
github.com/pelletier/go-toml/v2 v2.2.4
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
- github.com/wailsapp/wails/v2 v2.11.0
+ github.com/wailsapp/wails/v3 v3.0.0-alpha.68
golang.org/x/crypto v0.47.0
golang.org/x/net v0.49.0
gopkg.in/yaml.v3 v3.0.1
)
+require (
+ dario.cat/mergo v1.0.2 // indirect
+ github.com/Microsoft/go-winio v0.6.2 // indirect
+ github.com/ProtonMail/go-crypto v1.3.0 // indirect
+ github.com/adrg/xdg v0.5.3 // indirect
+ github.com/bytedance/sonic v1.14.0 // indirect
+ github.com/bytedance/sonic/loader v0.3.0 // indirect
+ github.com/cloudflare/circl v1.6.3 // indirect
+ github.com/cloudwego/base64x v0.1.6 // indirect
+ github.com/coder/websocket v1.8.14 // indirect
+ github.com/cyphar/filepath-securejoin v0.6.1 // indirect
+ github.com/ebitengine/purego v0.9.1 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/gabriel-vasile/mimetype v1.4.8 // indirect
+ github.com/gin-contrib/sse v1.1.0 // indirect
+ github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
+ github.com/go-git/go-billy/v5 v5.7.0 // indirect
+ github.com/go-git/go-git/v5 v5.16.4 // indirect
+ github.com/go-playground/locales v0.14.1 // indirect
+ github.com/go-playground/universal-translator v0.18.1 // indirect
+ github.com/go-playground/validator/v10 v10.27.0 // indirect
+ github.com/goccy/go-json v0.10.2 // indirect
+ github.com/goccy/go-yaml v1.18.0 // indirect
+ github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
+ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
+ github.com/json-iterator/go v1.1.12 // indirect
+ github.com/kevinburke/ssh_config v1.4.0 // indirect
+ github.com/klauspost/cpuid/v2 v2.3.0 // indirect
+ github.com/leodido/go-urn v1.4.0 // indirect
+ github.com/lmittmann/tint v1.1.2 // indirect
+ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
+ github.com/modern-go/reflect2 v1.0.2 // indirect
+ github.com/pjbgf/sha1cd v0.5.0 // indirect
+ github.com/quic-go/qpack v0.5.1 // indirect
+ github.com/quic-go/quic-go v0.54.0 // indirect
+ github.com/sergi/go-diff v1.4.0 // indirect
+ github.com/skeema/knownhosts v1.3.2 // indirect
+ github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
+ github.com/ugorji/go/codec v1.3.0 // indirect
+ github.com/xanzy/ssh-agent v0.3.3 // indirect
+ go.uber.org/mock v0.5.0 // indirect
+ golang.org/x/arch v0.20.0 // indirect
+ golang.org/x/mod v0.32.0 // indirect
+ golang.org/x/sync v0.19.0 // indirect
+ golang.org/x/tools v0.41.0 // indirect
+ google.golang.org/protobuf v1.36.9 // indirect
+ gopkg.in/warnings.v0 v0.1.2 // indirect
+)
+
require (
github.com/bep/debounce v1.2.1 // indirect
+ github.com/gin-gonic/gin v1.11.0
github.com/go-ole/go-ole v1.3.0 // indirect
- github.com/godbus/dbus/v5 v5.1.0 // indirect
+ github.com/godbus/dbus/v5 v5.2.2 // indirect
github.com/google/uuid v1.6.0 // indirect
- github.com/gorilla/websocket v1.5.3 // indirect
github.com/itchyny/timefmt-go v0.1.7 // indirect
- github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
- github.com/labstack/echo/v4 v4.13.3 // indirect
- github.com/labstack/gommon v0.4.2 // indirect
+ github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
- github.com/leaanthony/gosod v1.0.4 // indirect
- github.com/leaanthony/slicer v1.6.0 // indirect
github.com/leaanthony/u v1.1.1 // indirect
- github.com/mattn/go-colorable v0.1.13 // indirect
+ github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
- github.com/pkg/errors v0.9.1 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
- github.com/samber/lo v1.49.1 // indirect
- github.com/tkrajina/go-reflector v0.5.8 // indirect
- github.com/valyala/bytebufferpool v1.0.0 // indirect
- github.com/valyala/fasttemplate v1.2.2 // indirect
- github.com/wailsapp/go-webview2 v1.0.22 // indirect
- github.com/wailsapp/mimetype v1.4.1 // indirect
+ github.com/samber/lo v1.52.0 // indirect
+ github.com/wailsapp/go-webview2 v1.0.23 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/text v0.33.0 // indirect
)
diff --git a/go.sum b/go.sum
index eccf171..f905058 100644
--- a/go.sum
+++ b/go.sum
@@ -1,4 +1,17 @@
+dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
+dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
+github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
+github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
+github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
+github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
+github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
+github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
+github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
+github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
+github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
+github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
+github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
github.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVkHo=
@@ -15,124 +28,232 @@ github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVa
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
+github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
+github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
+github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
+github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
+github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
+github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
+github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
+github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
+github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
+github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
+github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
+github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
+github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
+github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
+github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
+github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
+github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
+github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
+github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
+github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
+github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
+github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
+github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
+github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
+github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM=
+github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E=
+github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
+github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
+github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y=
+github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
+github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e h1:Lf/gRkoycfOBPa42vU2bbgPurFong6zXeFtPoxholzU=
+github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
-github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
-github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
+github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
+github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
+github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
+github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
+github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
+github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
+github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
+github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
+github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
+github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
+github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
+github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/gomarkdown/markdown v0.0.0-20250810172220-2e2c11897d1a h1:l7A0loSszR5zHd/qK53ZIHMO8b3bBSmENnQ6eKnUT0A=
github.com/gomarkdown/markdown v0.0.0-20250810172220-2e2c11897d1a/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
-github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/itchyny/gojq v0.12.18 h1:gFGHyt/MLbG9n6dqnvlliiya2TaMMh6FFaR2b1H6Drc=
github.com/itchyny/gojq v0.12.18/go.mod h1:4hPoZ/3lN9fDL1D+aK7DY1f39XZpY9+1Xpjz8atrEkg=
github.com/itchyny/timefmt-go v0.1.7 h1:xyftit9Tbw+Dc/huSSPJaEmX1TVL8lw5vxjJLK4GMMA=
github.com/itchyny/timefmt-go v0.1.7/go.mod h1:5E46Q+zj7vbTgWY8o5YkMeYb4I6GeWLFnetPy5oBrAI=
-github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=
-github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
+github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
+github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
+github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 h1:njuLRcjAuMKr7kI3D85AXWkw6/+v9PwtV6M6o11sWHQ=
+github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ=
+github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
+github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
+github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY=
-github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g=
-github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
-github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
-github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc=
-github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A=
github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
-github.com/leaanthony/gosod v1.0.4 h1:YLAbVyd591MRffDgxUOU1NwLhT9T1/YiwjKZpkNFeaI=
-github.com/leaanthony/gosod v1.0.4/go.mod h1:GKuIL0zzPj3O1SdWQOdgURSuhkF+Urizzxh26t9f1cw=
-github.com/leaanthony/slicer v1.6.0 h1:1RFP5uiPJvT93TAHi+ipd3NACobkW53yUiBqZheE/Js=
-github.com/leaanthony/slicer v1.6.0/go.mod h1:o/Iz29g7LN0GqH3aMjWAe90381nyZlDNquK+mtH2Fj8=
github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M=
github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
+github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
+github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
+github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w=
+github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
-github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
-github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
-github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
+github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
+github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
+github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0=
+github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
+github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
+github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
+github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
-github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
-github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
+github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
+github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
+github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
+github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
+github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
+github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
+github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg=
+github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
-github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
-github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
-github.com/tkrajina/go-reflector v0.5.8 h1:yPADHrwmUbMq4RGEyaOUpz2H90sRsETNVpjzo3DLVQQ=
-github.com/tkrajina/go-reflector v0.5.8/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
-github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
-github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
-github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
-github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
-github.com/wailsapp/go-webview2 v1.0.22 h1:YT61F5lj+GGaat5OB96Aa3b4QA+mybD0Ggq6NZijQ58=
-github.com/wailsapp/go-webview2 v1.0.22/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
-github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
-github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
-github.com/wailsapp/wails/v2 v2.11.0 h1:seLacV8pqupq32IjS4Y7V8ucab0WZwtK6VvUVxSBtqQ=
-github.com/wailsapp/wails/v2 v2.11.0/go.mod h1:jrf0ZaM6+GBc1wRmXsM8cIvzlg0karYin3erahI4+0k=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
+github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
+github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
+github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
+github.com/wailsapp/go-webview2 v1.0.23 h1:jmv8qhz1lHibCc79bMM/a/FqOnnzOGEisLav+a0b9P0=
+github.com/wailsapp/go-webview2 v1.0.23/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
+github.com/wailsapp/wails/v3 v3.0.0-alpha.68 h1:CBSP9rOISKiFv6hmqVj2HsU6f4bSMQmsmuSzPQMUxSE=
+github.com/wailsapp/wails/v3 v3.0.0-alpha.68/go.mod h1:zvgNL/mlFcX8aRGu6KOz9AHrMmTBD+4hJRQIONqF/Yw=
+github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
+github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
+go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
+go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
+golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
+golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
+golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU=
+golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU=
+golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
+golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
+golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
+golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
+golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
+google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
+google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
+gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/index.html b/index.html
deleted file mode 100644
index 1b78207..0000000
--- a/index.html
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
dev-toolbox
-
-
-
-
-
-
-
diff --git a/internal/wails/barcode_service.go b/internal/barcode/service.go
similarity index 84%
rename from internal/wails/barcode_service.go
rename to internal/barcode/service.go
index 0b0de2f..a1f5772 100644
--- a/internal/wails/barcode_service.go
+++ b/internal/barcode/service.go
@@ -1,8 +1,7 @@
-package wails
+package barcode
import (
"bytes"
- "context"
"encoding/base64"
"fmt"
"image/png"
@@ -14,16 +13,10 @@ import (
"github.com/skip2/go-qrcode"
)
-type BarcodeService struct {
- ctx context.Context
-}
-
-func NewBarcodeService() *BarcodeService {
- return &BarcodeService{}
-}
+type BarcodeService struct{}
-func (s *BarcodeService) Startup(ctx context.Context) {
- s.ctx = ctx
+func NewBarcodeService() BarcodeService {
+ return BarcodeService{}
}
// GenerateBarcodeRequest represents the request to generate a barcode
@@ -89,21 +82,28 @@ func (s *BarcodeService) GenerateBarcode(req GenerateBarcodeRequest) GenerateBar
// For 1D barcodes, we maintain aspect ratio
if standard != "QR" {
bounds := img.Bounds()
- width := bounds.Dx()
- height := bounds.Dy()
+ origWidth := bounds.Dx()
+ origHeight := bounds.Dy()
- // Scale width to size, maintain aspect ratio for height
- scaleFactor := float64(size) / float64(width)
- newHeight := int(float64(height) * scaleFactor)
+ // Calculate target dimensions while maintaining aspect ratio
+ scaleFactor := float64(size) / float64(origWidth)
+ targetWidth := size
+ targetHeight := int(float64(origHeight) * scaleFactor)
- // Ensure minimum height for visibility
- if newHeight < 100 {
- newHeight = 100
+ // Ensure minimum dimensions for visibility and library requirements
+ if targetWidth < 259 {
+ targetWidth = 259
+ }
+ if targetHeight < 100 {
+ targetHeight = 100
}
- img, err = barcode.Scale(img, size, newHeight)
- if err != nil {
- return GenerateBarcodeResponse{Error: fmt.Sprintf("Failed to scale barcode: %v", err)}
+ // Only scale if the target is different from original
+ if targetWidth != origWidth || targetHeight != origHeight {
+ img, err = barcode.Scale(img, targetWidth, targetHeight)
+ if err != nil {
+ return GenerateBarcodeResponse{Error: fmt.Sprintf("Failed to scale barcode: %v", err)}
+ }
}
}
@@ -187,14 +187,27 @@ func (s *BarcodeService) GetBarcodeSizes() []map[string]interface{} {
}
// calculateEANChecksum calculates the EAN checksum digit
+// For EAN-13: weights alternate starting with 1 (1,3,1,3...)
+// For EAN-8: weights alternate starting with 3 (3,1,3,1...)
func calculateEANChecksum(code string) int {
sum := 0
+ isEAN8 := len(code) == 7
for i, c := range code {
digit := int(c - '0')
- if i%2 == 0 {
- sum += digit * 1
+ if isEAN8 {
+ // EAN-8: weights start with 3 at position 0
+ if i%2 == 0 {
+ sum += digit * 3
+ } else {
+ sum += digit * 1
+ }
} else {
- sum += digit * 3
+ // EAN-13: weights start with 1 at position 0
+ if i%2 == 0 {
+ sum += digit * 1
+ } else {
+ sum += digit * 3
+ }
}
}
checksum := (10 - (sum % 10)) % 10
diff --git a/internal/barcode/service_test.go b/internal/barcode/service_test.go
new file mode 100644
index 0000000..c75bbc9
--- /dev/null
+++ b/internal/barcode/service_test.go
@@ -0,0 +1,473 @@
+package barcode
+
+import (
+ "strings"
+ "testing"
+)
+
+func TestGenerateBarcode(t *testing.T) {
+ service := NewBarcodeService()
+
+ tests := []struct {
+ name string
+ content string
+ standard string
+ size int
+ level string
+ expectErr bool
+ errorMsg string
+ }{
+ // QR Code tests
+ {
+ name: "QR code - valid content",
+ content: "https://example.com",
+ standard: "QR",
+ size: 256,
+ level: "M",
+ expectErr: false,
+ },
+ {
+ name: "QR code - empty content should error",
+ content: "",
+ standard: "QR",
+ size: 256,
+ level: "M",
+ expectErr: true,
+ errorMsg: "Content cannot be empty",
+ },
+ {
+ name: "QR code - custom error level H",
+ content: "QR test",
+ standard: "QR",
+ size: 128,
+ level: "H",
+ expectErr: false,
+ },
+ {
+ name: "QR code - default size (less than 64)",
+ content: "test",
+ standard: "QR",
+ size: 32,
+ level: "M",
+ expectErr: false,
+ },
+
+ // EAN-13 tests
+ {
+ name: "EAN-13 - 12 digits (will be auto-calculated)",
+ content: "123456789012",
+ standard: "EAN-13",
+ size: 256,
+ expectErr: false,
+ },
+ {
+ name: "EAN-13 - 13 digits with correct checksum",
+ content: "1234567890128", // Last digit 8 is checksum for 123456789012
+ standard: "EAN-13",
+ size: 256,
+ expectErr: false,
+ },
+ {
+ name: "EAN-13 - invalid length",
+ content: "12345",
+ standard: "EAN-13",
+ size: 256,
+ expectErr: true,
+ },
+
+ // EAN-8 tests
+ {
+ name: "EAN-8 - 7 digits (will be auto-calculated)",
+ content: "1234567",
+ standard: "EAN-8",
+ size: 256,
+ expectErr: false,
+ },
+ {
+ name: "EAN-8 - 8 digits with correct checksum",
+ content: "96385074", // Checksum 4 for 9638507
+ standard: "EAN-8",
+ size: 256,
+ expectErr: false,
+ },
+
+ // Code128 tests
+ {
+ name: "Code128 - alphanumeric content",
+ content: "ABC123",
+ standard: "Code128",
+ size: 256,
+ expectErr: false,
+ },
+ {
+ name: "Code128 - special characters",
+ content: "Test-123_456",
+ standard: "Code128",
+ size: 256,
+ expectErr: false,
+ },
+
+ // Code39 tests
+ {
+ name: "Code39 - valid alphanumeric",
+ content: "ABC123",
+ standard: "Code39",
+ size: 256,
+ expectErr: false,
+ },
+ {
+ name: "Code39 - with spaces and valid special chars",
+ content: "TEST-123 $/+%",
+ standard: "Code39",
+ size: 256,
+ expectErr: false,
+ },
+
+ // Edge cases
+ {
+ name: "Default standard (empty should default to QR)",
+ content: "default test",
+ standard: "",
+ size: 256,
+ level: "M",
+ expectErr: false,
+ },
+ {
+ name: "Invalid standard",
+ content: "test",
+ standard: "INVALID",
+ size: 256,
+ expectErr: true,
+ errorMsg: "Unsupported barcode standard",
+ },
+ {
+ name: "Large size (should cap at 1024)",
+ content: "test",
+ standard: "QR",
+ size: 2048,
+ level: "M",
+ expectErr: false,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ req := GenerateBarcodeRequest{
+ Content: tt.content,
+ Standard: tt.standard,
+ Size: tt.size,
+ Level: tt.level,
+ Format: "base64",
+ }
+
+ resp := service.GenerateBarcode(req)
+
+ if tt.expectErr {
+ if resp.Error == "" {
+ t.Errorf("Expected error but got none")
+ }
+ if tt.errorMsg != "" && !strings.Contains(resp.Error, tt.errorMsg) {
+ t.Errorf("Expected error containing '%s', got '%s'", tt.errorMsg, resp.Error)
+ }
+ return
+ }
+
+ if resp.Error != "" {
+ t.Errorf("Unexpected error: %s", resp.Error)
+ return
+ }
+
+ if resp.DataURL == "" {
+ t.Error("Expected DataURL but got empty")
+ return
+ }
+
+ // Verify it's a valid data URL
+ if !strings.HasPrefix(resp.DataURL, "data:image/png;base64,") {
+ t.Errorf("Expected data URL starting with 'data:image/png;base64,', got: %s", resp.DataURL[:50])
+ }
+
+ // Verify base64 data is present
+ base64Data := strings.TrimPrefix(resp.DataURL, "data:image/png;base64,")
+ if len(base64Data) == 0 {
+ t.Error("Base64 data is empty")
+ }
+ })
+ }
+}
+
+func TestGetBarcodeStandards(t *testing.T) {
+ service := NewBarcodeService()
+ standards := service.GetBarcodeStandards()
+
+ expectedStandards := []struct {
+ value string
+ label string
+ }{
+ {"QR", "QR Code (2D)"},
+ {"EAN-13", "EAN-13 (Retail - 13 digits)"},
+ {"EAN-8", "EAN-8 (Small Retail - 8 digits)"},
+ {"Code128", "Code 128 (High Density)"},
+ {"Code39", "Code 39 (Alphanumeric)"},
+ }
+
+ if len(standards) != len(expectedStandards) {
+ t.Errorf("Expected %d standards, got %d", len(expectedStandards), len(standards))
+ }
+
+ for i, expected := range expectedStandards {
+ if i >= len(standards) {
+ break
+ }
+ if standards[i]["value"] != expected.value {
+ t.Errorf("Standard %d: expected value '%s', got '%s'", i, expected.value, standards[i]["value"])
+ }
+ if standards[i]["label"] != expected.label {
+ t.Errorf("Standard %d: expected label '%s', got '%s'", i, expected.label, standards[i]["label"])
+ }
+ }
+}
+
+func TestGetQRErrorLevels(t *testing.T) {
+ service := NewBarcodeService()
+ levels := service.GetQRErrorLevels()
+
+ expectedLevels := []struct {
+ value string
+ label string
+ }{
+ {"L", "Low (~7%)"},
+ {"M", "Medium (~15%)"},
+ {"Q", "Quartile (~25%)"},
+ {"H", "High (~30%)"},
+ }
+
+ if len(levels) != len(expectedLevels) {
+ t.Errorf("Expected %d error levels, got %d", len(expectedLevels), len(levels))
+ }
+
+ for i, expected := range expectedLevels {
+ if i >= len(levels) {
+ break
+ }
+ if levels[i]["value"] != expected.value {
+ t.Errorf("Level %d: expected value '%s', got '%s'", i, expected.value, levels[i]["value"])
+ }
+ if levels[i]["label"] != expected.label {
+ t.Errorf("Level %d: expected label '%s', got '%s'", i, expected.label, levels[i]["label"])
+ }
+ }
+}
+
+func TestGetBarcodeSizes(t *testing.T) {
+ service := NewBarcodeService()
+ sizes := service.GetBarcodeSizes()
+
+ expectedSizes := []struct {
+ value int
+ label string
+ }{
+ {128, "Small (128px)"},
+ {256, "Medium (256px)"},
+ {512, "Large (512px)"},
+ {1024, "Extra Large (1024px)"},
+ }
+
+ if len(sizes) != len(expectedSizes) {
+ t.Errorf("Expected %d size options, got %d", len(expectedSizes), len(sizes))
+ }
+
+ for i, expected := range expectedSizes {
+ if i >= len(sizes) {
+ break
+ }
+ // Type assertion for interface{}
+ value, ok := sizes[i]["value"].(int)
+ if !ok {
+ t.Errorf("Size %d: value is not an int", i)
+ continue
+ }
+ if value != expected.value {
+ t.Errorf("Size %d: expected value %d, got %d", i, expected.value, value)
+ }
+ label, ok := sizes[i]["label"].(string)
+ if !ok {
+ t.Errorf("Size %d: label is not a string", i)
+ continue
+ }
+ if label != expected.label {
+ t.Errorf("Size %d: expected label '%s', got '%s'", i, expected.label, label)
+ }
+ }
+}
+
+func TestValidateContent(t *testing.T) {
+ service := NewBarcodeService()
+
+ tests := []struct {
+ name string
+ content string
+ standard string
+ wantValid bool
+ wantMsg string
+ }{
+ // EAN-13 validation tests
+ {
+ name: "EAN-13 valid 12 digits",
+ content: "123456789012",
+ standard: "EAN-13",
+ wantValid: true,
+ },
+ {
+ name: "EAN-13 valid 13 digits with correct checksum",
+ content: "1234567890128", // Checksum for 123456789012 is 8
+ standard: "EAN-13",
+ wantValid: true,
+ },
+ {
+ name: "EAN-13 invalid length",
+ content: "12345",
+ standard: "EAN-13",
+ wantValid: false,
+ wantMsg: "EAN-13 requires 12 or 13 digits",
+ },
+ {
+ name: "EAN-13 contains non-digits",
+ content: "12345678901A",
+ standard: "EAN-13",
+ wantValid: false,
+ wantMsg: "EAN-13 can only contain digits",
+ },
+ {
+ name: "EAN-13 13 digits with wrong checksum",
+ content: "1234567890129", // Wrong checksum (should be 8)
+ standard: "EAN-13",
+ wantValid: false,
+ wantMsg: "Invalid checksum",
+ },
+
+ // EAN-8 validation tests
+ {
+ name: "EAN-8 valid 7 digits",
+ content: "1234567",
+ standard: "EAN-8",
+ wantValid: true,
+ },
+ {
+ name: "EAN-8 valid 8 digits with correct checksum",
+ content: "96385074", // Checksum for 9638507 is 4
+ standard: "EAN-8",
+ wantValid: true,
+ },
+ {
+ name: "EAN-8 invalid length",
+ content: "12345",
+ standard: "EAN-8",
+ wantValid: false,
+ wantMsg: "EAN-8 requires 7 or 8 digits",
+ },
+ {
+ name: "EAN-8 contains non-digits",
+ content: "12345A7",
+ standard: "EAN-8",
+ wantValid: false,
+ wantMsg: "EAN-8 can only contain digits",
+ },
+
+ // Code39 validation tests
+ {
+ name: "Code39 valid alphanumeric",
+ content: "ABC123",
+ standard: "Code39",
+ wantValid: true,
+ },
+ {
+ name: "Code39 valid with special chars",
+ content: "TEST-123 $/+%",
+ standard: "Code39",
+ wantValid: true,
+ },
+ {
+ name: "Code39 invalid character (lowercase)",
+ content: "abc123",
+ standard: "Code39",
+ wantValid: false,
+ wantMsg: "Code 39 only supports: 0-9, A-Z, and - . $ / + % space",
+ },
+ {
+ name: "Code39 invalid character (unsupported)",
+ content: "TEST@123",
+ standard: "Code39",
+ wantValid: false,
+ wantMsg: "Code 39 only supports: 0-9, A-Z, and - . $ / + % space",
+ },
+
+ // Other standards (should return valid by default)
+ {
+ name: "QR code always valid",
+ content: "any content",
+ standard: "QR",
+ wantValid: true,
+ },
+ {
+ name: "Code128 always valid",
+ content: "any content",
+ standard: "Code128",
+ wantValid: true,
+ },
+ {
+ name: "Unknown standard",
+ content: "test",
+ standard: "UNKNOWN",
+ wantValid: true, // Unknown standards don't have validation
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := service.ValidateContent(tt.content, tt.standard)
+
+ valid, ok := result["valid"].(bool)
+ if !ok {
+ t.Fatal("Result 'valid' field is not a bool")
+ }
+
+ message, ok := result["message"].(string)
+ if !ok {
+ t.Fatal("Result 'message' field is not a string")
+ }
+
+ if valid != tt.wantValid {
+ t.Errorf("Expected valid=%v, got valid=%v", tt.wantValid, valid)
+ }
+
+ if tt.wantMsg != "" && !strings.Contains(message, tt.wantMsg) {
+ t.Errorf("Expected message containing '%s', got '%s'", tt.wantMsg, message)
+ }
+ })
+ }
+}
+
+func TestCalculateEANChecksum(t *testing.T) {
+ tests := []struct {
+ name string
+ code string
+ expected int
+ }{
+ {"EAN-13 example 1", "123456789012", 8}, // Known checksum
+ {"EAN-13 example 2", "400638133393", 1}, // Known checksum
+ {"EAN-8 example 1", "1234567", 0}, // 1*3+2*1+3*3+4*1+5*3+6*1+7*3=60, 60%10=0, (10-0)%10=0
+ {"EAN-8 example 2", "9638507", 4}, // Known checksum
+ {"All zeros", "000000000000", 0}, // Edge case
+ {"All ones", "111111111111", 6}, // Sum=24, 24%10=4, (10-4)%10=6
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := calculateEANChecksum(tt.code)
+ if result != tt.expected {
+ t.Errorf("Expected checksum %d for code %s, got %d", tt.expected, tt.code, result)
+ }
+ })
+ }
+}
diff --git a/internal/datetimeconverter/dto.go b/internal/datetimeconverter/dto.go
new file mode 100644
index 0000000..9076b1e
--- /dev/null
+++ b/internal/datetimeconverter/dto.go
@@ -0,0 +1,92 @@
+package datetimeconverter
+
+// ConvertRequest represents a conversion request from the frontend
+type ConvertRequest struct {
+ Input string `json:"input"`
+ Precision string `json:"precision"` // "auto", "seconds", "millis", "micros", "nanos"
+ Timezone string `json:"timezone"` // "local", "UTC", or IANA timezone name
+ OutputFormat string `json:"outputFormat"` // "iso", "rfc2822", "sql", "us", "eu", "compact", "custom"
+ CustomFormat string `json:"customFormat"`
+}
+
+// ConvertResponse represents the conversion response
+type ConvertResponse struct {
+ Result *TimeResult `json:"result,omitempty"`
+ DetectedType string `json:"detectedType"` // "timestamp", "iso", "date", "unknown"
+ DetectedPrec string `json:"detectedPrec"` // "seconds", "millis", "micros", "nanos"
+ Error string `json:"error,omitempty"`
+}
+
+// PresetsResponse returns all available presets
+type PresetsResponse struct {
+ Presets []Preset `json:"presets"`
+}
+
+// DeltaRequest represents a time delta calculation request
+type DeltaRequest struct {
+ DateA string `json:"dateA"`
+ DateB string `json:"dateB"`
+}
+
+// DeltaResponse represents a time delta calculation response
+type DeltaResponse struct {
+ Delta *TimeDelta `json:"delta,omitempty"`
+ Error string `json:"error,omitempty"`
+}
+
+// ArithmeticRequest represents a date arithmetic request
+type ArithmeticRequest struct {
+ BaseDate string `json:"baseDate"`
+ Operation string `json:"operation"` // "add" or "subtract"
+ Value int `json:"value"`
+ Unit string `json:"unit"` // "seconds", "minutes", "hours", "days", "weeks", "months", "years"
+}
+
+// ArithmeticResponse represents a date arithmetic response
+type ArithmeticResponse struct {
+ Result *TimeResult `json:"result,omitempty"`
+ Error string `json:"error,omitempty"`
+}
+
+// BatchRequest represents a batch conversion request
+type BatchRequest struct {
+ Inputs []string `json:"inputs"`
+ Timezone string `json:"timezone"`
+}
+
+// BatchResponse represents a batch conversion response
+type BatchResponse struct {
+ Results []BatchResult `json:"results"`
+}
+
+// TimezoneComparisonRequest represents a timezone comparison request
+type TimezoneComparisonRequest struct {
+ Timestamp int64 `json:"timestamp"`
+ Timezones []string `json:"timezones"`
+}
+
+// TimezoneComparisonResponse represents a timezone comparison response
+type TimezoneComparisonResponse struct {
+ Timezones []TimezoneInfo `json:"timezones"`
+ Error string `json:"error,omitempty"`
+}
+
+type AvailableTimezonesResponse struct {
+ Timezones []TimezoneInfo `json:"timezones"`
+}
+
+// FromTimeResult creates a ConvertResponse from a TimeResult
+func FromTimeResult(result *TimeResult, detectedType, detectedPrec string) ConvertResponse {
+ return ConvertResponse{
+ Result: result,
+ DetectedType: detectedType,
+ DetectedPrec: detectedPrec,
+ }
+}
+
+// ErrorResponse creates an error response
+func ErrorResponse(err error) ConvertResponse {
+ return ConvertResponse{
+ Error: err.Error(),
+ }
+}
diff --git a/internal/datetimeconverter/errors.go b/internal/datetimeconverter/errors.go
new file mode 100644
index 0000000..63e6625
--- /dev/null
+++ b/internal/datetimeconverter/errors.go
@@ -0,0 +1,12 @@
+package datetimeconverter
+
+import "fmt"
+
+// Domain errors for unixtime package
+var (
+ ErrInvalidTimestamp = fmt.Errorf("invalid timestamp")
+ ErrInvalidDate = fmt.Errorf("invalid date format")
+ ErrInvalidPrecision = fmt.Errorf("invalid precision")
+ ErrInvalidTimezone = fmt.Errorf("invalid timezone")
+ ErrInvalidUnit = fmt.Errorf("invalid time unit")
+)
diff --git a/internal/datetimeconverter/models.go b/internal/datetimeconverter/models.go
new file mode 100644
index 0000000..c935a61
--- /dev/null
+++ b/internal/datetimeconverter/models.go
@@ -0,0 +1,302 @@
+package datetimeconverter
+
+import (
+ "fmt"
+ "time"
+)
+
+// Precision represents the timestamp precision type
+type Precision string
+
+const (
+ PrecisionAuto Precision = "auto"
+ PrecisionSeconds Precision = "seconds"
+ PrecisionMillis Precision = "millis"
+ PrecisionMicros Precision = "micros"
+ PrecisionNanos Precision = "nanos"
+)
+
+// InputType represents the detected input type
+type InputType string
+
+const (
+ InputTypeTimestamp InputType = "timestamp"
+ InputTypeISO InputType = "iso"
+ InputTypeDate InputType = "date"
+ InputTypeUnknown InputType = "unknown"
+)
+
+// TimeResult contains all conversion results for a given time
+type TimeResult struct {
+ UnixSeconds int64 `json:"unixSeconds"`
+ UnixMillis int64 `json:"unixMillis"`
+ UnixMicros int64 `json:"unixMicros"`
+ UnixNanos int64 `json:"unixNanos"`
+ UTC string `json:"utc"`
+ Local string `json:"local"`
+ Relative string `json:"relative"`
+ RelativeDetails RelativeBreakdown `json:"relativeDetails"`
+}
+
+// RelativeBreakdown provides detailed relative time information
+type RelativeBreakdown struct {
+ Days int `json:"days"`
+ Hours int `json:"hours"`
+ Minutes int `json:"minutes"`
+ Seconds int `json:"seconds"`
+ TotalHours int `json:"totalHours"`
+ TotalMinutes int `json:"totalMinutes"`
+ TotalSeconds int `json:"totalSeconds"`
+ DaysSinceEpoch int `json:"daysSinceEpoch"`
+}
+
+// TimeDelta represents the difference between two times
+type TimeDelta struct {
+ Days int `json:"days"`
+ Hours int `json:"hours"`
+ Minutes int `json:"minutes"`
+ Seconds int `json:"seconds"`
+ TotalHours float64 `json:"totalHours"`
+ TotalMinutes float64 `json:"totalMinutes"`
+ TotalSeconds float64 `json:"totalSeconds"`
+ BusinessDays int `json:"businessDays"`
+ IsFuture bool `json:"isFuture"`
+}
+
+// BatchResult represents a single batch conversion result
+type BatchResult struct {
+ Input string `json:"input"`
+ Success bool `json:"success"`
+ Error string `json:"error,omitempty"`
+ Result *TimeResult `json:"result,omitempty"`
+}
+
+// TimezoneInfo represents time in a specific timezone
+type TimezoneInfo struct {
+ Label string `json:"label"`
+ Timezone string `json:"timezone"`
+}
+
+// Preset represents a quick preset option
+type Preset struct {
+ ID string `json:"id"`
+ Label string `json:"label"`
+ Description string `json:"description"`
+ Timestamp int64 `json:"timestamp"`
+}
+
+// FormatType represents output format types
+type FormatType string
+
+const (
+ FormatISO FormatType = "iso"
+ FormatRFC2822 FormatType = "rfc2822"
+ FormatRFC3339 FormatType = "rfc3339"
+ FormatSQL FormatType = "sql"
+ FormatUS FormatType = "us"
+ FormatEU FormatType = "eu"
+ FormatCompact FormatType = "compact"
+ FormatCustom FormatType = "custom"
+)
+
+// TimeUnit represents time units for arithmetic operations
+type TimeUnit string
+
+const (
+ UnitSeconds TimeUnit = "seconds"
+ UnitMinutes TimeUnit = "minutes"
+ UnitHours TimeUnit = "hours"
+ UnitDays TimeUnit = "days"
+ UnitWeeks TimeUnit = "weeks"
+ UnitMonths TimeUnit = "months"
+ UnitYears TimeUnit = "years"
+)
+
+// DetectPrecision determines the precision from timestamp string length
+func DetectPrecision(input string) Precision {
+ if len(input) == 0 {
+ return PrecisionAuto
+ }
+
+ // Remove any non-digit characters for length calculation
+ clean := ""
+ for _, r := range input {
+ if r >= '0' && r <= '9' {
+ clean += string(r)
+ }
+ }
+
+ switch len(clean) {
+ case 10:
+ return PrecisionSeconds
+ case 13:
+ return PrecisionMillis
+ case 16:
+ return PrecisionMicros
+ case 19:
+ return PrecisionNanos
+ default:
+ return PrecisionAuto
+ }
+}
+
+// DetectInputType determines the type of input
+func DetectInputType(input string) InputType {
+ if len(input) == 0 {
+ return InputTypeUnknown
+ }
+
+ // Check for ISO format (contains T or Z)
+ if len(input) >= 10 && (contains(input, "T") || contains(input, "Z")) {
+ return InputTypeISO
+ }
+
+ // Check if it's a pure number (timestamp)
+ isNumber := true
+ for _, r := range input {
+ if r < '0' || r > '9' {
+ isNumber = false
+ break
+ }
+ }
+
+ if isNumber && len(input) >= 10 {
+ return InputTypeTimestamp
+ }
+
+ // Check for date separators
+ if contains(input, "-") || contains(input, "/") {
+ return InputTypeDate
+ }
+
+ return InputTypeUnknown
+}
+
+func contains(s, substr string) bool {
+ return len(s) >= len(substr) && (s == substr || len(s) > 0 && containsHelper(s, substr))
+}
+
+func containsHelper(s, substr string) bool {
+ for i := 0; i <= len(s)-len(substr); i++ {
+ if s[i:i+len(substr)] == substr {
+ return true
+ }
+ }
+ return false
+}
+
+// UnixEpoch returns the Unix epoch time (January 1, 1970)
+func UnixEpoch() time.Time {
+ return time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)
+}
+
+// ParseTimestamp parses a timestamp string with given precision
+func ParseTimestamp(input string, precision Precision) (time.Time, error) {
+ if precision == PrecisionAuto {
+ precision = DetectPrecision(input)
+ }
+
+ // Extract numeric part
+ var numericStr string
+ for _, r := range input {
+ if r >= '0' && r <= '9' {
+ numericStr += string(r)
+ }
+ }
+
+ if len(numericStr) == 0 {
+ return time.Time{}, ErrInvalidTimestamp
+ }
+
+ var ts int64
+ _, err := fmt.Sscanf(numericStr, "%d", &ts)
+ if err != nil {
+ return time.Time{}, ErrInvalidTimestamp
+ }
+
+ var nanos int64
+ switch precision {
+ case PrecisionSeconds:
+ nanos = ts * 1e9
+ case PrecisionMillis:
+ nanos = ts * 1e6
+ case PrecisionMicros:
+ nanos = ts * 1e3
+ case PrecisionNanos:
+ nanos = ts
+ default:
+ // Default to seconds if unknown
+ nanos = ts * 1e9
+ }
+
+ return time.Unix(0, nanos).UTC(), nil
+}
+
+// FormatTime formats a time according to the specified format
+func FormatTime(t time.Time, format FormatType, customFormat string) string {
+ switch format {
+ case FormatISO:
+ return t.Format(time.RFC3339Nano)
+ case FormatRFC2822:
+ return t.Format(time.RFC1123)
+ case FormatRFC3339:
+ return t.Format(time.RFC3339)
+ case FormatSQL:
+ return t.Format("2006-01-02 15:04:05")
+ case FormatUS:
+ return t.Format("01/02/2006 15:04:05")
+ case FormatEU:
+ return t.Format("02/01/2006 15:04:05")
+ case FormatCompact:
+ return t.Format("20060102-150405")
+ case FormatCustom:
+ if customFormat != "" {
+ return formatCustom(t, customFormat)
+ }
+ return t.Format(time.RFC3339)
+ default:
+ return t.Format(time.RFC3339)
+ }
+}
+
+// formatCustom applies custom format tokens
+func formatCustom(t time.Time, format string) string {
+ replacements := map[string]string{
+ "YYYY": t.Format("2006"),
+ "MM": t.Format("01"),
+ "DD": t.Format("02"),
+ "HH": t.Format("15"),
+ "hh": t.Format("03"),
+ "mm": t.Format("04"),
+ "ss": t.Format("05"),
+ "sss": fmt.Sprintf("%03d", t.Nanosecond()/1e6),
+ "A": t.Format("PM"),
+ "ddd": t.Format("Mon"),
+ "dddd": t.Format("Monday"),
+ "ZZ": t.Format("-0700"),
+ }
+
+ result := format
+ for token, value := range replacements {
+ result = replaceAll(result, token, value)
+ }
+ return result
+}
+
+func replaceAll(s, old, new string) string {
+ result := s
+ for {
+ idx := -1
+ for i := 0; i <= len(result)-len(old); i++ {
+ if result[i:i+len(old)] == old {
+ idx = i
+ break
+ }
+ }
+ if idx == -1 {
+ break
+ }
+ result = result[:idx] + new + result[idx+len(old):]
+ }
+ return result
+}
diff --git a/internal/datetimeconverter/service.go b/internal/datetimeconverter/service.go
new file mode 100644
index 0000000..f6c7222
--- /dev/null
+++ b/internal/datetimeconverter/service.go
@@ -0,0 +1,459 @@
+package datetimeconverter
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+ "time"
+)
+
+// Service defines the Unix Time service interface
+type Service interface {
+ Convert(req ConvertRequest) ConvertResponse
+ GetPresets() PresetsResponse
+ CalculateDelta(req DeltaRequest) DeltaResponse
+ GetAvailableTimezones() AvailableTimezonesResponse
+}
+
+// service implements the Service interface
+type service struct{}
+
+// NewService creates a new Unix Time service
+func NewService() Service {
+ return &service{}
+}
+
+// Convert converts a timestamp or date string to all formats
+func (s *service) Convert(req ConvertRequest) ConvertResponse {
+ if req.Input == "" {
+ return ErrorResponse(ErrInvalidTimestamp)
+ }
+
+ // Detect input type
+ inputType := DetectInputType(req.Input)
+
+ var t time.Time
+ var err error
+
+ switch inputType {
+ case InputTypeTimestamp:
+ precision := Precision(req.Precision)
+ if precision == PrecisionAuto {
+ precision = DetectPrecision(req.Input)
+ }
+ t, err = ParseTimestamp(req.Input, precision)
+ if err != nil {
+ return ErrorResponse(err)
+ }
+
+ case InputTypeISO, InputTypeDate:
+ t, err = parseDateString(req.Input)
+ if err != nil {
+ return ErrorResponse(err)
+ }
+
+ default:
+ // Try to parse as timestamp anyway
+ t, err = ParseTimestamp(req.Input, PrecisionAuto)
+ if err != nil {
+ return ErrorResponse(ErrInvalidTimestamp)
+ }
+ inputType = InputTypeTimestamp
+ }
+
+ // Apply timezone if specified
+ if req.Timezone != "" && req.Timezone != "local" && req.Timezone != "UTC" {
+ loc, err := time.LoadLocation(req.Timezone)
+ if err == nil {
+ t = t.In(loc)
+ }
+ }
+
+ // Build result
+ result := buildTimeResult(t)
+
+ // Format the output
+ format := FormatType(req.OutputFormat)
+ if format == "" {
+ format = FormatISO
+ }
+ result.Local = FormatTime(t, format, req.CustomFormat)
+
+ detectedPrec := string(DetectPrecision(req.Input))
+ if detectedPrec == "auto" {
+ detectedPrec = "seconds"
+ }
+
+ return FromTimeResult(result, string(inputType), detectedPrec)
+}
+
+// GetPresets returns all available quick presets
+func (s *service) GetPresets() PresetsResponse {
+ now := time.Now()
+
+ presets := []Preset{
+ {
+ ID: "now",
+ Label: "Now",
+ Description: "Current timestamp",
+ Timestamp: now.Unix(),
+ },
+ {
+ ID: "plus1hour",
+ Label: "+1 Hour",
+ Description: "One hour from now",
+ Timestamp: now.Add(time.Hour).Unix(),
+ },
+ {
+ ID: "plus1day",
+ Label: "+1 Day",
+ Description: "One day from now",
+ Timestamp: now.Add(24 * time.Hour).Unix(),
+ },
+ {
+ ID: "tomorrow9am",
+ Label: "Tomorrow 9am",
+ Description: "Tomorrow at 9:00 AM",
+ Timestamp: time.Date(now.Year(), now.Month(), now.Day()+1, 9, 0, 0, 0, now.Location()).Unix(),
+ },
+ {
+ ID: "nextweek",
+ Label: "Next Week",
+ Description: "Same time next week",
+ Timestamp: now.Add(7 * 24 * time.Hour).Unix(),
+ },
+ {
+ ID: "startofday",
+ Label: "Start of Day",
+ Description: "Today at 00:00:00",
+ Timestamp: time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()).Unix(),
+ },
+ {
+ ID: "endofday",
+ Label: "End of Day",
+ Description: "Today at 23:59:59",
+ Timestamp: time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, now.Location()).Unix(),
+ },
+ {
+ ID: "startofweek",
+ Label: "Start of Week",
+ Description: "Monday 00:00:00",
+ Timestamp: getStartOfWeek(now).Unix(),
+ },
+ {
+ ID: "endofweek",
+ Label: "End of Week",
+ Description: "Sunday 23:59:59",
+ Timestamp: getEndOfWeek(now).Unix(),
+ },
+ {
+ ID: "epoch",
+ Label: "Unix Epoch",
+ Description: "January 1, 1970 00:00:00 UTC",
+ Timestamp: 0,
+ },
+ }
+
+ return PresetsResponse{Presets: presets}
+}
+
+// CalculateDelta calculates the difference between two dates
+func (s *service) CalculateDelta(req DeltaRequest) DeltaResponse {
+ dateA, err := parseFlexibleDate(req.DateA)
+ if err != nil {
+ return DeltaResponse{Error: err.Error()}
+ }
+
+ dateB, err := parseFlexibleDate(req.DateB)
+ if err != nil {
+ return DeltaResponse{Error: err.Error()}
+ }
+
+ diff := dateB.Sub(dateA)
+ absDiff := diff
+ if absDiff < 0 {
+ absDiff = -absDiff
+ }
+
+ days := int(absDiff.Hours() / 24)
+ hours := int(absDiff.Hours()) % 24
+ minutes := int(absDiff.Minutes()) % 60
+ seconds := int(absDiff.Seconds()) % 60
+
+ delta := &TimeDelta{
+ Days: days,
+ Hours: hours,
+ Minutes: minutes,
+ Seconds: seconds,
+ TotalHours: absDiff.Hours(),
+ TotalMinutes: absDiff.Minutes(),
+ TotalSeconds: absDiff.Seconds(),
+ IsFuture: diff > 0,
+ }
+
+ return DeltaResponse{Delta: delta}
+}
+
+// AddTime performs date arithmetic
+func (s *service) AddTime(req ArithmeticRequest) ArithmeticResponse {
+ baseDate, err := parseFlexibleDate(req.BaseDate)
+ if err != nil {
+ return ArithmeticResponse{Error: err.Error()}
+ }
+
+ unit := TimeUnit(req.Unit)
+ duration := time.Duration(req.Value)
+
+ switch unit {
+ case UnitSeconds:
+ duration = duration * time.Second
+ case UnitMinutes:
+ duration = duration * time.Minute
+ case UnitHours:
+ duration = duration * time.Hour
+ case UnitDays:
+ duration = duration * 24 * time.Hour
+ case UnitWeeks:
+ duration = duration * 7 * 24 * time.Hour
+ case UnitMonths:
+ // Add months manually
+ baseDate = addMonths(baseDate, req.Value)
+ duration = 0
+ case UnitYears:
+ // Add years manually
+ baseDate = addYears(baseDate, req.Value)
+ duration = 0
+ default:
+ return ArithmeticResponse{Error: ErrInvalidUnit.Error()}
+ }
+
+ if req.Operation == "subtract" {
+ duration = -duration
+ }
+
+ result := baseDate.Add(duration)
+
+ return ArithmeticResponse{Result: buildTimeResult(result)}
+}
+
+func (s *service) GetAvailableTimezones() AvailableTimezonesResponse {
+ timezones := make([]TimezoneInfo, 0)
+ for _, zoneDir = range zoneDirs {
+ readTimezonesFromFile("", &timezones)
+ }
+
+ return AvailableTimezonesResponse{
+ Timezones: timezones,
+ }
+}
+
+// Helper functions
+
+func parseDateString(input string) (time.Time, error) {
+ // Try various date formats
+ formats := []string{
+ time.RFC3339,
+ time.RFC3339Nano,
+ time.RFC1123,
+ time.RFC1123Z,
+ time.RFC822,
+ time.RFC822Z,
+ "2006-01-02 15:04:05",
+ "2006-01-02 15:04",
+ "2006-01-02",
+ "01/02/2006 15:04:05",
+ "01/02/2006",
+ "02/01/2006 15:04:05",
+ "02/01/2006",
+ "20060102-150405",
+ "20060102",
+ }
+
+ for _, format := range formats {
+ if t, err := time.Parse(format, input); err == nil {
+ return t, nil
+ }
+ }
+
+ return time.Time{}, ErrInvalidDate
+}
+
+func parseFlexibleDate(input string) (time.Time, error) {
+ // Try parsing as timestamp first
+ if ts, err := strconv.ParseInt(input, 10, 64); err == nil {
+ if ts > 1e12 {
+ return time.Unix(0, ts*1e6), nil // Milliseconds
+ }
+ return time.Unix(ts, 0), nil
+ }
+
+ // Try parsing as date string
+ return parseDateString(input)
+}
+
+func buildTimeResult(t time.Time) *TimeResult {
+ nanos := t.UnixNano()
+
+ // Calculate relative time
+ now := time.Now()
+ diff := t.Sub(now)
+ relative := formatRelativeTime(diff)
+ relativeDetails := buildRelativeBreakdown(diff, t)
+
+ return &TimeResult{
+ UnixSeconds: t.Unix(),
+ UnixMillis: nanos / 1e6,
+ UnixMicros: nanos / 1e3,
+ UnixNanos: nanos,
+ UTC: t.UTC().Format(time.RFC3339),
+ Local: t.Local().Format("2006-01-02 15:04:05"),
+ Relative: relative,
+ RelativeDetails: relativeDetails,
+ }
+}
+
+func formatRelativeTime(diff time.Duration) string {
+ if diff == 0 {
+ return "now"
+ }
+
+ absDiff := diff
+ isFuture := diff > 0
+ if absDiff < 0 {
+ absDiff = -absDiff
+ }
+
+ days := int(absDiff.Hours() / 24)
+ hours := int(absDiff.Hours()) % 24
+ minutes := int(absDiff.Minutes()) % 60
+ seconds := int(absDiff.Seconds()) % 60
+
+ var parts []string
+ if days > 0 {
+ parts = append(parts, fmt.Sprintf("%d day%s", days, plural(days)))
+ }
+ if hours > 0 {
+ parts = append(parts, fmt.Sprintf("%d hour%s", hours, plural(hours)))
+ }
+ if minutes > 0 && days == 0 {
+ parts = append(parts, fmt.Sprintf("%d minute%s", minutes, plural(minutes)))
+ }
+ if seconds > 0 && days == 0 && hours == 0 {
+ parts = append(parts, fmt.Sprintf("%d second%s", seconds, plural(seconds)))
+ }
+
+ result := strings.Join(parts, ", ")
+ if isFuture {
+ return "in " + result
+ }
+ return result + " ago"
+}
+
+func buildRelativeBreakdown(diff time.Duration, t time.Time) RelativeBreakdown {
+ absDiff := diff
+ if absDiff < 0 {
+ absDiff = -absDiff
+ }
+
+ days := int(absDiff.Hours() / 24)
+ hours := int(absDiff.Hours()) % 24
+ minutes := int(absDiff.Minutes()) % 60
+ seconds := int(absDiff.Seconds()) % 60
+
+ // Days since epoch
+ epoch := UnixEpoch()
+ daysSinceEpoch := int(t.Sub(epoch).Hours() / 24)
+
+ return RelativeBreakdown{
+ Days: days,
+ Hours: hours,
+ Minutes: minutes,
+ Seconds: seconds,
+ TotalHours: int(absDiff.Hours()),
+ TotalMinutes: int(absDiff.Minutes()),
+ TotalSeconds: int(absDiff.Seconds()),
+ DaysSinceEpoch: daysSinceEpoch,
+ }
+}
+
+func plural(n int) string {
+ if n == 1 {
+ return ""
+ }
+ return "s"
+}
+
+func getStartOfWeek(t time.Time) time.Time {
+ // Monday is start of week
+ weekday := int(t.Weekday())
+ if weekday == 0 {
+ weekday = 7
+ }
+ daysSinceMonday := weekday - 1
+ return time.Date(t.Year(), t.Month(), t.Day()-daysSinceMonday, 0, 0, 0, 0, t.Location())
+}
+
+func getEndOfWeek(t time.Time) time.Time {
+ // Sunday is end of week
+ weekday := int(t.Weekday())
+ if weekday == 0 {
+ return time.Date(t.Year(), t.Month(), t.Day(), 23, 59, 59, 0, t.Location())
+ }
+ daysUntilSunday := 7 - weekday
+ return time.Date(t.Year(), t.Month(), t.Day()+daysUntilSunday, 23, 59, 59, 0, t.Location())
+}
+
+func addMonths(t time.Time, months int) time.Time {
+ newMonth := t.Month() + time.Month(months)
+ newYear := t.Year()
+
+ for newMonth > 12 {
+ newMonth -= 12
+ newYear++
+ }
+ for newMonth < 1 {
+ newMonth += 12
+ newYear--
+ }
+
+ // Handle day overflow (e.g., Jan 31 + 1 month = Feb 28/29)
+ daysInMonth := daysIn(newMonth, newYear)
+ newDay := t.Day()
+ if newDay > daysInMonth {
+ newDay = daysInMonth
+ }
+
+ return time.Date(newYear, newMonth, newDay, t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), t.Location())
+}
+
+func addYears(t time.Time, years int) time.Time {
+ newYear := t.Year() + years
+
+ // Handle Feb 29 on non-leap years
+ if t.Month() == time.February && t.Day() == 29 {
+ if !isLeapYear(newYear) {
+ return time.Date(newYear, time.February, 28, t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), t.Location())
+ }
+ }
+
+ return time.Date(newYear, t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), t.Location())
+}
+
+func daysIn(m time.Month, year int) int {
+ switch m {
+ case time.January, time.March, time.May, time.July, time.August, time.October, time.December:
+ return 31
+ case time.April, time.June, time.September, time.November:
+ return 30
+ case time.February:
+ if isLeapYear(year) {
+ return 29
+ }
+ return 28
+ }
+ return 0
+}
+
+func isLeapYear(year int) bool {
+ return year%4 == 0 && (year%100 != 0 || year%400 == 0)
+}
diff --git a/internal/datetimeconverter/service_test.go b/internal/datetimeconverter/service_test.go
new file mode 100644
index 0000000..4b655b4
--- /dev/null
+++ b/internal/datetimeconverter/service_test.go
@@ -0,0 +1,322 @@
+package datetimeconverter
+
+import (
+ "testing"
+ "time"
+)
+
+func TestDetectPrecision(t *testing.T) {
+ tests := []struct {
+ name string
+ input string
+ expected Precision
+ }{
+ {"Seconds - 10 digits", "1738412345", PrecisionSeconds},
+ {"Millis - 13 digits", "1738412345000", PrecisionMillis},
+ {"Micros - 16 digits", "1738412345000000", PrecisionMicros},
+ {"Nanos - 19 digits", "1738412345000000000", PrecisionNanos},
+ {"Empty string", "", PrecisionAuto},
+ {"Short number", "12345", PrecisionAuto},
+ {"With non-digits", "1738412345abc", PrecisionSeconds},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := DetectPrecision(tt.input)
+ if result != tt.expected {
+ t.Errorf("DetectPrecision(%q) = %v, want %v", tt.input, result, tt.expected)
+ }
+ })
+ }
+}
+
+func TestDetectInputType(t *testing.T) {
+ tests := []struct {
+ name string
+ input string
+ expected InputType
+ }{
+ {"ISO with T", "2026-02-01T12:24:05Z", InputTypeISO},
+ {"ISO with Z", "2026-02-01Z", InputTypeISO},
+ {"Timestamp seconds", "1738412345", InputTypeTimestamp},
+ {"Timestamp millis", "1738412345000", InputTypeTimestamp},
+ {"Date with dash", "2026-02-01", InputTypeDate},
+ {"Date with slash", "02/01/2026", InputTypeDate},
+ {"Empty string", "", InputTypeUnknown},
+ {"Mixed text", "hello world", InputTypeUnknown},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := DetectInputType(tt.input)
+ if result != tt.expected {
+ t.Errorf("DetectInputType(%q) = %v, want %v", tt.input, result, tt.expected)
+ }
+ })
+ }
+}
+
+func TestParseTimestamp(t *testing.T) {
+ tests := []struct {
+ name string
+ input string
+ precision Precision
+ wantErr bool
+ }{
+ {"Valid seconds", "1738412345", PrecisionSeconds, false},
+ {"Valid millis", "1738412345000", PrecisionMillis, false},
+ {"Valid micros", "1738412345000000", PrecisionMicros, false},
+ {"Valid nanos", "1738412345000000000", PrecisionNanos, false},
+ {"Auto detect seconds", "1738412345", PrecisionAuto, false},
+ {"Auto detect millis", "1738412345000", PrecisionAuto, false},
+ {"Empty string", "", PrecisionAuto, true},
+ {"Invalid input", "abc", PrecisionAuto, true},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result, err := ParseTimestamp(tt.input, tt.precision)
+ if tt.wantErr {
+ if err == nil {
+ t.Errorf("ParseTimestamp(%q, %v) expected error, got nil", tt.input, tt.precision)
+ }
+ return
+ }
+ if err != nil {
+ t.Errorf("ParseTimestamp(%q, %v) unexpected error: %v", tt.input, tt.precision, err)
+ return
+ }
+ if result.IsZero() {
+ t.Errorf("ParseTimestamp(%q, %v) returned zero time", tt.input, tt.precision)
+ }
+ })
+ }
+}
+
+func TestFormatTime(t *testing.T) {
+ testTime := time.Date(2026, 2, 1, 12, 24, 5, 0, time.UTC)
+
+ tests := []struct {
+ name string
+ format FormatType
+ customFormat string
+ wantContains string
+ }{
+ {"ISO format", FormatISO, "", "2026-02-01"},
+ {"RFC2822 format", FormatRFC2822, "", "Feb"},
+ {"RFC3339 format", FormatRFC3339, "", "2026-02-01"},
+ {"SQL format", FormatSQL, "", "2026-02-01 12:24:05"},
+ {"US format", FormatUS, "", "02/01/2026"},
+ {"EU format", FormatEU, "", "01/02/2026"},
+ {"Compact format", FormatCompact, "", "20260201-122405"},
+ {"Custom format", FormatCustom, "YYYY-MM-DD", "2026-02-01"},
+ {"Default format", "", "", "2026-02-01"},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := FormatTime(testTime, tt.format, tt.customFormat)
+ if result == "" {
+ t.Errorf("FormatTime() returned empty string")
+ }
+ if tt.wantContains != "" && !containsStr(result, tt.wantContains) {
+ t.Errorf("FormatTime() = %q, want to contain %q", result, tt.wantContains)
+ }
+ })
+ }
+}
+
+func TestServiceConvert(t *testing.T) {
+ svc := NewService()
+
+ tests := []struct {
+ name string
+ req ConvertRequest
+ wantErr bool
+ }{
+ {
+ name: "Convert timestamp seconds",
+ req: ConvertRequest{
+ Input: "1738412345",
+ Precision: "auto",
+ },
+ wantErr: false,
+ },
+ {
+ name: "Convert timestamp millis",
+ req: ConvertRequest{
+ Input: "1738412345000",
+ Precision: "auto",
+ },
+ wantErr: false,
+ },
+ {
+ name: "Convert ISO date",
+ req: ConvertRequest{
+ Input: "2026-02-01T12:24:05Z",
+ Precision: "auto",
+ },
+ wantErr: false,
+ },
+ {
+ name: "Convert SQL date",
+ req: ConvertRequest{
+ Input: "2026-02-01 12:24:05",
+ Precision: "auto",
+ },
+ wantErr: false,
+ },
+ {
+ name: "Empty input",
+ req: ConvertRequest{
+ Input: "",
+ Precision: "auto",
+ },
+ wantErr: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ resp := svc.Convert(tt.req)
+ if tt.wantErr {
+ if resp.Error == "" {
+ t.Errorf("Convert() expected error, got none")
+ }
+ return
+ }
+ if resp.Error != "" {
+ t.Errorf("Convert() unexpected error: %s", resp.Error)
+ return
+ }
+ if resp.Result == nil {
+ t.Errorf("Convert() result is nil")
+ return
+ }
+ if resp.Result.UnixSeconds == 0 {
+ t.Errorf("Convert() UnixSeconds is 0")
+ }
+ })
+ }
+}
+
+func TestServiceGetPresets(t *testing.T) {
+ svc := NewService()
+ resp := svc.GetPresets()
+
+ if len(resp.Presets) == 0 {
+ t.Errorf("GetPresets() returned empty presets")
+ }
+
+ expectedPresets := []string{"now", "plus1hour", "plus1day", "tomorrow9am", "nextweek", "startofday", "endofday", "startofweek", "endofweek", "epoch"}
+ for _, expected := range expectedPresets {
+ found := false
+ for _, preset := range resp.Presets {
+ if preset.ID == expected {
+ found = true
+ break
+ }
+ }
+ if !found {
+ t.Errorf("GetPresets() missing preset: %s", expected)
+ }
+ }
+}
+
+func TestServiceCalculateDelta(t *testing.T) {
+ svc := NewService()
+
+ tests := []struct {
+ name string
+ req DeltaRequest
+ wantErr bool
+ }{
+ {
+ name: "Calculate delta between timestamps",
+ req: DeltaRequest{
+ DateA: "1738412345",
+ DateB: "1738498745",
+ },
+ wantErr: false,
+ },
+ {
+ name: "Calculate delta between ISO dates",
+ req: DeltaRequest{
+ DateA: "2026-02-01T12:00:00Z",
+ DateB: "2026-02-02T12:00:00Z",
+ },
+ wantErr: false,
+ },
+ {
+ name: "Invalid date A",
+ req: DeltaRequest{
+ DateA: "invalid",
+ DateB: "1738412345",
+ },
+ wantErr: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ resp := svc.CalculateDelta(tt.req)
+ if tt.wantErr {
+ if resp.Error == "" {
+ t.Errorf("CalculateDelta() expected error, got none")
+ }
+ return
+ }
+ if resp.Error != "" {
+ t.Errorf("CalculateDelta() unexpected error: %s", resp.Error)
+ return
+ }
+ if resp.Delta == nil {
+ t.Errorf("CalculateDelta() delta is nil")
+ }
+ })
+ }
+}
+
+func TestFormatRelativeTime(t *testing.T) {
+ tests := []struct {
+ name string
+ diff time.Duration
+ expected string
+ }{
+ {"Now", 0, "now"},
+ {"1 second ago", -1 * time.Second, "1 second ago"},
+ {"2 seconds ago", -2 * time.Second, "2 seconds ago"},
+ {"1 minute ago", -1 * time.Minute, "1 minute ago"},
+ {"1 hour ago", -1 * time.Hour, "1 hour ago"},
+ {"1 day ago", -24 * time.Hour, "1 day ago"},
+ {"In 1 second", 1 * time.Second, "in 1 second"},
+ {"In 2 seconds", 2 * time.Second, "in 2 seconds"},
+ {"In 1 minute", 1 * time.Minute, "in 1 minute"},
+ {"In 1 hour", 1 * time.Hour, "in 1 hour"},
+ {"In 1 day", 24 * time.Hour, "in 1 day"},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := formatRelativeTime(tt.diff)
+ if result == "" {
+ t.Errorf("formatRelativeTime(%v) returned empty string", tt.diff)
+ }
+ // Just check it doesn't panic and returns something
+ t.Logf("formatRelativeTime(%v) = %q", tt.diff, result)
+ })
+ }
+}
+
+func containsStr(s, substr string) bool {
+ return len(s) >= len(substr) && (s == substr || containsHelperStr(s, substr))
+}
+
+func containsHelperStr(s, substr string) bool {
+ for i := 0; i <= len(s)-len(substr); i++ {
+ if s[i:i+len(substr)] == substr {
+ return true
+ }
+ }
+ return false
+}
diff --git a/internal/datetimeconverter/timezone.go b/internal/datetimeconverter/timezone.go
new file mode 100644
index 0000000..f6e9001
--- /dev/null
+++ b/internal/datetimeconverter/timezone.go
@@ -0,0 +1,48 @@
+package datetimeconverter
+
+import (
+ "fmt"
+ "os"
+ "strings"
+)
+
+var zoneDirs = []string{
+ "/usr/share/zoneinfo/",
+ "/usr/share/lib/zoneinfo/",
+ "/usr/lib/locale/TZ/",
+}
+
+var excludeTzAbbrs = map[string]struct{}{
+ "+VERSION": {},
+}
+
+var zoneDir string
+
+func readTimezonesFromFile(path string, timezones *[]TimezoneInfo) {
+ files, _ := os.ReadDir(zoneDir + path)
+ for _, f := range files {
+ if f.Name() != strings.ToUpper(f.Name()[:1])+f.Name()[1:] {
+ continue
+ }
+
+ if f.IsDir() {
+ readTimezonesFromFile(path+"/"+f.Name(), timezones)
+ } else {
+ // Exclude tz abbreviation if it's matched to any of the given list
+ if _, ok := excludeTzAbbrs[f.Name()]; ok {
+ continue
+ }
+
+ tzAbbr := (path + "/" + f.Name())[1:]
+ label := tzAbbr
+ if strings.Contains(tzAbbr, "/") {
+ label = fmt.Sprintf("%s (%s)", f.Name(), tzAbbr)
+ }
+
+ *timezones = append(*timezones, TimezoneInfo{
+ Label: label,
+ Timezone: tzAbbr,
+ })
+ }
+ }
+}
diff --git a/internal/jwt/errors.go b/internal/jwt/errors.go
index ed2a0a4..aaaf5c0 100644
--- a/internal/jwt/errors.go
+++ b/internal/jwt/errors.go
@@ -1,7 +1,7 @@
package jwt
import (
- sharedErrors "dev-toolbox/pkg/errors"
+ sharedErrors "devtoolbox/pkg/errors"
)
// JWTError represents a JWT-specific error
diff --git a/internal/jwt/parser.go b/internal/jwt/parser.go
index af0581f..6b46f02 100644
--- a/internal/jwt/parser.go
+++ b/internal/jwt/parser.go
@@ -1,10 +1,11 @@
package jwt
import (
- "dev-toolbox/pkg/encoding"
- "dev-toolbox/pkg/validation"
"strings"
+ "devtoolbox/pkg/encoding"
+ "devtoolbox/pkg/validation"
+
"github.com/golang-jwt/jwt/v5"
)
diff --git a/main.go b/main.go
index 38cbd28..4af1459 100644
--- a/main.go
+++ b/main.go
@@ -1,62 +1,124 @@
package main
import (
- "context"
+ "devtoolbox/service"
"embed"
+ "log"
+ "net/http"
+ "strings"
+ "time"
- "dev-toolbox/internal/wails"
-
- wails_runtime "github.com/wailsapp/wails/v2"
- "github.com/wailsapp/wails/v2/pkg/options"
- "github.com/wailsapp/wails/v2/pkg/options/assetserver"
+ "github.com/gin-gonic/gin"
+ "github.com/wailsapp/wails/v3/pkg/application"
)
-//go:embed all:dist
+//go:embed all:frontend/dist
var assets embed.FS
+func init() {
+ // Register a custom event whose associated data type is string.
+ // This is not required, but the binding generator will pick up registered events
+ // and provide a strongly typed JS/TS API for them.
+ application.RegisterEvent[string]("time")
+}
+
func main() {
- // Create instances of the app structures
- app := NewApp()
- jwtService := wails.NewJWTService()
- conversionService := wails.NewConversionService()
- barcodeService := wails.NewBarcodeService()
- dataGeneratorService := wails.NewDataGeneratorService()
- codeFormatterService := wails.NewCodeFormatterService()
-
- // Start HTTP server for Web Mode (port 8081)
- go func() {
- server := NewServer(jwtService, conversionService, barcodeService, dataGeneratorService, codeFormatterService)
- server.Start(8081)
- }()
+ ginEngine := gin.New()
+ ginEngine.Use(gin.Recovery())
+ ginEngine.Use(LoggingMiddleware())
+
+ ginEngine.StaticFS("/static", http.FS(assets))
+ ginEngine.GET("/", func(c *gin.Context) {
+ file, _ := assets.ReadFile("static/index.html")
+ c.Data(http.StatusOK, "text/html; charset=utf-8", file)
+ })
+
+ ginEngine.GET("/api/hello", func(c *gin.Context) {
+ c.JSON(http.StatusOK, gin.H{
+ "message": "Hello from Gin API!",
+ "time": time.Now().Format(time.RFC3339),
+ })
+ })
// Create application with options
- err := wails_runtime.Run(&options.App{
- Title: "dev-toolbox",
+ app := application.New(application.Options{
+ Name: "DevToolbox",
+ Description: "Set of tools for daily development",
+ Services: []application.Service{
+ application.NewService(&GreetService{}),
+ },
+ Mac: application.MacOptions{
+ ApplicationShouldTerminateAfterLastWindowClosed: true,
+ },
+ Assets: application.AssetOptions{
+ // Handler: ginEngine,
+ // Middleware: GinMiddleware(ginEngine),
+ Handler: application.AssetFileServerFS(assets),
+ },
+ })
+
+ // Register app services
+ app.RegisterService(application.NewService(service.NewJWTService(app)))
+ app.RegisterService(application.NewService(service.NewDateTimeService(app)))
+ app.RegisterService(application.NewService(service.NewConversionService(app)))
+ app.RegisterService(application.NewService(service.NewBarcodeService(app)))
+ app.RegisterService(application.NewService(service.NewDataGeneratorService(app)))
+ app.RegisterService(application.NewService(service.NewCodeFormatterService(app)))
+
+ app.Window.NewWithOptions(application.WebviewWindowOptions{
+ Title: "DevToolbox",
Width: 1024,
Height: 768,
- AssetServer: &assetserver.Options{
- Assets: assets,
- },
- BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
- OnStartup: func(ctx context.Context) {
- app.startup(ctx)
- jwtService.Startup(ctx)
- conversionService.Startup(ctx)
- barcodeService.Startup(ctx)
- dataGeneratorService.Startup(ctx)
- codeFormatterService.Startup(ctx)
+ BackgroundColour: application.RGBA{
+ Red: 27,
+ Green: 38,
+ Blue: 54,
+ Alpha: 1,
},
- Bind: []interface{}{
- app,
- jwtService,
- conversionService,
- barcodeService,
- dataGeneratorService,
- codeFormatterService,
+ Mac: application.MacWindow{
+ InvisibleTitleBarHeight: 50,
+ Backdrop: application.MacBackdropTranslucent,
+ TitleBar: application.MacTitleBarHiddenInset,
},
+ URL: "/",
})
- if err != nil {
- println("Error:", err.Error())
+ if err := app.Run(); err != nil {
+ panic(err)
+ }
+}
+
+func GinMiddleware(ginEngine *gin.Engine) application.Middleware {
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if strings.HasPrefix(r.URL.Path, "/wails") {
+ next.ServeHTTP(w, r)
+ return
+ }
+
+ ginEngine.ServeHTTP(w, r)
+ })
+ }
+}
+
+func LoggingMiddleware() gin.HandlerFunc {
+ return func(c *gin.Context) {
+ // Start timer
+ startTime := time.Now()
+
+ // Process request
+ c.Next()
+
+ // Calculate latency
+ latency := time.Since(startTime)
+
+ // Log request details
+ log.Printf("[GIN] %s | %s | %s | %d | %s",
+ c.Request.Method,
+ c.Request.URL.Path,
+ c.ClientIP(),
+ c.Writer.Status(),
+ latency,
+ )
}
}
diff --git a/server.go b/server.go
index aa9da98..8eb88e0 100644
--- a/server.go
+++ b/server.go
@@ -1,34 +1,46 @@
package main
import (
+ "devtoolbox/internal/barcode"
+ "devtoolbox/internal/codeformatter"
+ "devtoolbox/internal/datagenerator"
+ "devtoolbox/internal/datetimeconverter"
+ "devtoolbox/service"
"encoding/json"
"fmt"
"log"
"net/http"
"strings"
-
- "dev-toolbox/internal/codeformatter"
- "dev-toolbox/internal/datagenerator"
- "dev-toolbox/internal/wails"
)
+// TODO: Think of a way to expose this by Gin, so i can test it via Http client
+
// Server represents the HTTP server for Web Mode
type Server struct {
- jwtService *wails.JWTService
- conversionService *wails.ConversionService
- barcodeService *wails.BarcodeService
- dataGeneratorService *wails.DataGeneratorService
- codeFormatterService *wails.CodeFormatterService
+ jwtService *service.JWTService
+ conversionService *service.ConversionService
+ barcodeService *service.BarcodeService
+ dataGeneratorService *service.DataGeneratorService
+ codeFormatterService *service.CodeFormatterService
+ dateTimeService *service.DateTimeService
}
// NewServer creates a new Server instance
-func NewServer(jwtService *wails.JWTService, conversionService *wails.ConversionService, barcodeService *wails.BarcodeService, dataGeneratorService *wails.DataGeneratorService, codeFormatterService *wails.CodeFormatterService) *Server {
+func NewServer(
+ jwtService *service.JWTService,
+ conversionService *service.ConversionService,
+ barcodeService *service.BarcodeService,
+ dataGeneratorService *service.DataGeneratorService,
+ codeFormatterService *service.CodeFormatterService,
+ dateTimeService *service.DateTimeService,
+) *Server {
return &Server{
jwtService: jwtService,
conversionService: conversionService,
barcodeService: barcodeService,
dataGeneratorService: dataGeneratorService,
codeFormatterService: codeFormatterService,
+ dateTimeService: dateTimeService,
}
}
@@ -93,6 +105,11 @@ func (s *Server) handleAPI(w http.ResponseWriter, r *http.Request) {
return
}
+ if service == "DateTimeService" {
+ s.handleDateTimeService(method, w, r)
+ return
+ }
+
http.Error(w, fmt.Sprintf("Service not found: %s", service), http.StatusNotFound)
}
@@ -214,7 +231,7 @@ func (s *Server) handleBarcodeService(method string, w http.ResponseWriter, r *h
return
}
- req := wails.GenerateBarcodeRequest{
+ req := barcode.GenerateBarcodeRequest{
Content: getStringFromMap(reqData, "content"),
Standard: getStringFromMap(reqData, "standard"),
Size: getIntFromMap(reqData, "size"),
@@ -372,6 +389,73 @@ func getBoolFromMap(m map[string]interface{}, key string) bool {
return false
}
+func (s *Server) handleDateTimeService(method string, w http.ResponseWriter, r *http.Request) {
+ var payload struct {
+ Args []interface{} `json:"args"`
+ }
+
+ if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
+ http.Error(w, "Invalid request body", http.StatusBadRequest)
+ return
+ }
+
+ var result interface{}
+ var err error
+
+ switch method {
+ case "Convert":
+ if len(payload.Args) < 1 {
+ http.Error(w, "Missing arguments", http.StatusBadRequest)
+ return
+ }
+ reqData, ok := payload.Args[0].(map[string]interface{})
+ if !ok {
+ http.Error(w, "Invalid request format", http.StatusBadRequest)
+ return
+ }
+
+ req := datetimeconverter.ConvertRequest{
+ Input: getStringFromMap(reqData, "input"),
+ Precision: getStringFromMap(reqData, "precision"),
+ Timezone: getStringFromMap(reqData, "timezone"),
+ OutputFormat: getStringFromMap(reqData, "outputFormat"),
+ CustomFormat: getStringFromMap(reqData, "customFormat"),
+ }
+ result, err = s.dateTimeService.Convert(req)
+ case "GetPresets":
+ result, err = s.dateTimeService.GetPresets()
+ case "CalculateDelta":
+ if len(payload.Args) < 1 {
+ http.Error(w, "Missing arguments", http.StatusBadRequest)
+ return
+ }
+ reqData, ok := payload.Args[0].(map[string]interface{})
+ if !ok {
+ http.Error(w, "Invalid request format", http.StatusBadRequest)
+ return
+ }
+
+ req := datetimeconverter.DeltaRequest{
+ DateA: getStringFromMap(reqData, "dateA"),
+ DateB: getStringFromMap(reqData, "dateB"),
+ }
+ result, err = s.dateTimeService.CalculateDelta(req)
+ case "GetAvailableTimezones":
+ result, err = s.dateTimeService.GetAvailableTimezones()
+ default:
+ http.Error(w, fmt.Sprintf("Method not found: %s", method), http.StatusNotFound)
+ return
+ }
+
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(result)
+}
+
func corsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
diff --git a/service/barcode.go b/service/barcode.go
new file mode 100644
index 0000000..f13d1f4
--- /dev/null
+++ b/service/barcode.go
@@ -0,0 +1,49 @@
+package service
+
+import (
+ "context"
+ "devtoolbox/internal/barcode"
+
+ "github.com/wailsapp/wails/v3/pkg/application"
+)
+
+type BarcodeService struct {
+ app *application.App
+ svc barcode.BarcodeService
+}
+
+func NewBarcodeService(app *application.App) *BarcodeService {
+ return &BarcodeService{
+ app: app,
+ svc: barcode.NewBarcodeService(),
+ }
+}
+
+func (s *BarcodeService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
+ return nil
+}
+
+// GenerateBarcode generates a barcode based on the selected standard
+func (s *BarcodeService) GenerateBarcode(req barcode.GenerateBarcodeRequest) barcode.GenerateBarcodeResponse {
+ return s.svc.GenerateBarcode(req)
+}
+
+// GetBarcodeStandards returns available barcode standards
+func (s *BarcodeService) GetBarcodeStandards() []map[string]string {
+ return s.svc.GetBarcodeStandards()
+}
+
+// GetQRErrorLevels returns available error correction levels for QR codes
+func (s *BarcodeService) GetQRErrorLevels() []map[string]string {
+ return s.svc.GetQRErrorLevels()
+}
+
+// GetBarcodeSizes returns available barcode sizes
+func (s *BarcodeService) GetBarcodeSizes() []map[string]interface{} {
+ return s.svc.GetBarcodeSizes()
+}
+
+// ValidateContent validates content for specific barcode standards
+func (s *BarcodeService) ValidateContent(content string, standard string) map[string]interface{} {
+ return s.svc.ValidateContent(content, standard)
+}
diff --git a/internal/wails/codeformatter_service.go b/service/codeformatter.go
similarity index 65%
rename from internal/wails/codeformatter_service.go
rename to service/codeformatter.go
index 3eef29b..0e4e931 100644
--- a/internal/wails/codeformatter_service.go
+++ b/service/codeformatter.go
@@ -1,26 +1,29 @@
-package wails
+package service
import (
"context"
- "dev-toolbox/internal/codeformatter"
+ "devtoolbox/internal/codeformatter"
+
+ "github.com/wailsapp/wails/v3/pkg/application"
)
// CodeFormatterService is the Wails binding struct for code formatting operations
type CodeFormatterService struct {
- ctx context.Context
+ app *application.App
svc codeformatter.CodeFormatterService
}
// NewCodeFormatterService creates a new CodeFormatterService instance
-func NewCodeFormatterService() *CodeFormatterService {
+func NewCodeFormatterService(app *application.App) *CodeFormatterService {
return &CodeFormatterService{
svc: codeformatter.NewCodeFormatterService(),
+ app: app,
}
}
// Startup is called when the app starts (Wails lifecycle)
-func (c *CodeFormatterService) Startup(ctx context.Context) {
- c.ctx = ctx
+func (c *CodeFormatterService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
+ return nil
}
// Format formats code based on the request
diff --git a/internal/wails/conversion_service.go b/service/conversion.go
similarity index 57%
rename from internal/wails/conversion_service.go
rename to service/conversion.go
index 377ccd7..4cca3f2 100644
--- a/internal/wails/conversion_service.go
+++ b/service/conversion.go
@@ -1,23 +1,26 @@
-package wails
+package service
import (
"context"
- "dev-toolbox/internal/converter"
+ "devtoolbox/internal/converter"
+
+ "github.com/wailsapp/wails/v3/pkg/application"
)
type ConversionService struct {
- ctx context.Context
+ app *application.App
svc converter.ConverterService
}
-func NewConversionService() *ConversionService {
+func NewConversionService(app *application.App) *ConversionService {
return &ConversionService{
svc: converter.NewConverterService(),
+ app: app,
}
}
-func (s *ConversionService) Startup(ctx context.Context) {
- s.ctx = ctx
+func (s *ConversionService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
+ return nil
}
func (s *ConversionService) Convert(input, category, method string, config map[string]interface{}) (string, error) {
diff --git a/internal/wails/data_generator_service.go b/service/datagenerator.go
similarity index 79%
rename from internal/wails/data_generator_service.go
rename to service/datagenerator.go
index 7866ed9..ffe6d4d 100644
--- a/internal/wails/data_generator_service.go
+++ b/service/datagenerator.go
@@ -1,27 +1,29 @@
-package wails
+package service
import (
"context"
+ "devtoolbox/internal/datagenerator"
- "dev-toolbox/internal/datagenerator"
+ "github.com/wailsapp/wails/v3/pkg/application"
)
// DataGeneratorService provides data generation functionality via Wails
type DataGeneratorService struct {
- ctx context.Context
+ app *application.App
svc datagenerator.DataGeneratorService
}
// NewDataGeneratorService creates a new DataGeneratorService
-func NewDataGeneratorService() *DataGeneratorService {
+func NewDataGeneratorService(app *application.App) *DataGeneratorService {
return &DataGeneratorService{
svc: datagenerator.NewDataGeneratorService(),
+ app: app,
}
}
// Startup is called when the service starts
-func (d *DataGeneratorService) Startup(ctx context.Context) {
- d.ctx = ctx
+func (d *DataGeneratorService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
+ return nil
}
// Generate generates data based on the provided request
diff --git a/service/datetime.go b/service/datetime.go
new file mode 100644
index 0000000..e9fef8b
--- /dev/null
+++ b/service/datetime.go
@@ -0,0 +1,49 @@
+package service
+
+import (
+ "context"
+ "devtoolbox/internal/datetimeconverter"
+
+ "github.com/wailsapp/wails/v3/pkg/application"
+)
+
+// DateTimeService is the Wails binding struct for Unix Time operations
+type DateTimeService struct {
+ app *application.App
+ svc datetimeconverter.Service
+}
+
+// NewDateTimeService creates a new UnixTimeService instance
+func NewDateTimeService(app *application.App) *DateTimeService {
+ return &DateTimeService{
+ svc: datetimeconverter.NewService(),
+ app: app,
+ }
+}
+
+// ServiceStartup is called when the app starts (Wails lifecycle)
+func (u *DateTimeService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
+ return nil
+}
+
+// Convert converts a timestamp or date string to all formats
+// This method is exposed to the frontend via Wails
+func (u *DateTimeService) Convert(req datetimeconverter.ConvertRequest) (datetimeconverter.ConvertResponse, error) {
+ return u.svc.Convert(req), nil
+}
+
+// GetPresets returns all available quick presets
+// This method is exposed to the frontend via Wails
+func (u *DateTimeService) GetPresets() (datetimeconverter.PresetsResponse, error) {
+ return u.svc.GetPresets(), nil
+}
+
+// CalculateDelta calculates the difference between two dates
+// This method is exposed to the frontend via Wails
+func (u *DateTimeService) CalculateDelta(req datetimeconverter.DeltaRequest) (datetimeconverter.DeltaResponse, error) {
+ return u.svc.CalculateDelta(req), nil
+}
+
+func (u *DateTimeService) GetAvailableTimezones() (datetimeconverter.AvailableTimezonesResponse, error) {
+ return u.svc.GetAvailableTimezones(), nil
+}
diff --git a/internal/wails/jwt_service.go b/service/jwt.go
similarity index 83%
rename from internal/wails/jwt_service.go
rename to service/jwt.go
index ec3bd1c..e93b1f5 100644
--- a/internal/wails/jwt_service.go
+++ b/service/jwt.go
@@ -1,29 +1,30 @@
-package wails
+package service
import (
"context"
- "dev-toolbox/internal/jwt"
+ "devtoolbox/internal/jwt"
"encoding/json"
+
+ "github.com/wailsapp/wails/v3/pkg/application"
)
// JWTService is the Wails binding struct for JWT operations
type JWTService struct {
- ctx context.Context
svc jwt.JWTService
+ app *application.App
}
// NewJWTService creates a new JWTService instance
-func NewJWTService() *JWTService {
- parser := jwt.NewParser()
- svc := jwt.NewJWTService(parser)
+func NewJWTService(app *application.App) *JWTService {
return &JWTService{
- svc: svc,
+ svc: jwt.NewJWTService(jwt.NewParser()),
+ app: app,
}
}
-// Startup is called when the app starts (Wails lifecycle)
-func (j *JWTService) Startup(ctx context.Context) {
- j.ctx = ctx
+// ServiceStartup is called when the app starts (Wails lifecycle)
+func (j *JWTService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
+ return nil
}
// Decode decodes a JWT token
diff --git a/src/pages/UnixTimeConverter.jsx b/src/pages/UnixTimeConverter.jsx
deleted file mode 100644
index ddc907e..0000000
--- a/src/pages/UnixTimeConverter.jsx
+++ /dev/null
@@ -1,409 +0,0 @@
-import React, { useState, useEffect } from 'react';
-import { Button, TextInput, Tag, Dropdown, TextArea } from '@carbon/react';
-import { Time, Calendar } from '@carbon/icons-react';
-import { ToolHeader, ToolControls, ToolPane, ToolSplitPane } from '../components/ToolUI';
-import useLayoutToggle from '../hooks/useLayoutToggle';
-
-const DATE_FORMATS = [
- { id: 'iso', label: 'ISO 8601', format: 'YYYY-MM-DDTHH:mm:ss.sssZ' },
- { id: 'rfc2822', label: 'RFC 2822', format: 'ddd, DD MMM YYYY HH:mm:ss ZZ' },
- { id: 'sql', label: 'SQL DateTime', format: 'YYYY-MM-DD HH:mm:ss' },
- { id: 'us', label: 'US Format', format: 'MM/DD/YYYY HH:mm:ss' },
- { id: 'eu', label: 'EU Format', format: 'DD/MM/YYYY HH:mm:ss' },
- { id: 'compact', label: 'Compact', format: 'YYYYMMDD-HHmmss' },
- { id: 'custom', label: 'Custom', format: 'Custom format' },
-];
-
-const TIMEZONES = [
- { id: 'UTC', label: 'UTC', offset: 0 },
- { id: 'local', label: 'Local Time', offset: null },
- { id: 'EST', label: 'EST (New York)', offset: -5 },
- { id: 'CST', label: 'CST (Chicago)', offset: -6 },
- { id: 'MST', label: 'MST (Denver)', offset: -7 },
- { id: 'PST', label: 'PST (Los Angeles)', offset: -8 },
- { id: 'GMT', label: 'GMT (London)', offset: 0 },
- { id: 'CET', label: 'CET (Paris)', offset: 1 },
- { id: 'IST', label: 'IST (India)', offset: 5.5 },
- { id: 'JST', label: 'JST (Tokyo)', offset: 9 },
- { id: 'AEST', label: 'AEST (Sydney)', offset: 10 },
-];
-
-const parseDateWithFormat = (input, format) => {
- if (!input) return null;
-
- // Try parsing as ISO first
- let date = new Date(input);
- if (!isNaN(date.getTime())) return date;
-
- // Try parsing as timestamp
- if (!isNaN(input) && input.toString().length >= 10) {
- const ts = parseInt(input, 10);
- if (ts > 1000000000) {
- date = new Date(ts > 9999999999 ? ts : ts * 1000);
- if (!isNaN(date.getTime())) return date;
- }
- }
-
- return null;
-};
-
-const formatDate = (date, formatId, customFormat) => {
- if (!date || isNaN(date.getTime())) return '';
-
- const pad = (n) => n.toString().padStart(2, '0');
- const year = date.getFullYear();
- const month = pad(date.getMonth() + 1);
- const day = pad(date.getDate());
- const hours = pad(date.getHours());
- const minutes = pad(date.getMinutes());
- const seconds = pad(date.getSeconds());
- const ms = date.getMilliseconds().toString().padStart(3, '0');
-
- switch (formatId) {
- case 'iso':
- return date.toISOString();
- case 'rfc2822':
- return date.toUTCString();
- case 'sql':
- return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
- case 'us':
- return `${month}/${day}/${year} ${hours}:${minutes}:${seconds}`;
- case 'eu':
- return `${day}/${month}/${year} ${hours}:${minutes}:${seconds}`;
- case 'compact':
- return `${year}${month}${day}-${hours}${minutes}${seconds}`;
- case 'custom':
- if (customFormat) {
- // Simple format replacement
- return customFormat
- .replace('YYYY', year)
- .replace('MM', month)
- .replace('DD', day)
- .replace('HH', hours)
- .replace('mm', minutes)
- .replace('ss', seconds)
- .replace('sss', ms);
- }
- return date.toISOString();
- default:
- return date.toISOString();
- }
-};
-
-const convertToTimezone = (date, timezoneId) => {
- if (!date || timezoneId === 'local') return date;
-
- const tz = TIMEZONES.find(t => t.id === timezoneId);
- if (!tz || tz.offset === null) return date;
-
- const offsetMs = tz.offset * 60 * 60 * 1000;
- return new Date(date.getTime() + offsetMs);
-};
-
-export default function UnixTimeConverter() {
- const [timestamp, setTimestamp] = useState('');
- const [dateStr, setDateStr] = useState('');
- const [localDateStr, setLocalDateStr] = useState('');
- const [relativeTime, setRelativeTime] = useState('');
- const [inputFormat, setInputFormat] = useState('iso');
- const [outputFormat, setOutputFormat] = useState('iso');
- const [customInputFormat, setCustomInputFormat] = useState('YYYY-MM-DD HH:mm:ss');
- const [customOutputFormat, setCustomOutputFormat] = useState('YYYY-MM-DD HH:mm:ss');
- const [sourceTimezone, setSourceTimezone] = useState('local');
- const [targetTimezone, setTargetTimezone] = useState('local');
- const [parsedDate, setParsedDate] = useState(null);
-
- const layout = useLayoutToggle({
- toolKey: 'unix-time-layout',
- defaultDirection: 'horizontal',
- showToggle: true,
- persist: true
- });
-
- // Initialize with current time
- useEffect(() => {
- const now = Math.floor(Date.now() / 1000);
- handleTsChange(now.toString());
- }, []);
-
- const calculateRelativeTime = (date) => {
- if (!date || isNaN(date.getTime())) return '';
-
- const now = Date.now();
- const diff = date.getTime() - now;
- const absDiff = Math.abs(diff);
- const seconds = Math.floor(absDiff / 1000);
- const minutes = Math.floor(seconds / 60);
- const hours = Math.floor(minutes / 60);
- const days = Math.floor(hours / 24);
-
- let relative = '';
- if (diff > 0) {
- relative = 'in ';
- }
-
- if (days > 0) relative += `${days} day${days > 1 ? 's' : ''} `;
- else if (hours > 0) relative += `${hours} hour${hours > 1 ? 's' : ''} `;
- else if (minutes > 0) relative += `${minutes} minute${minutes > 1 ? 's' : ''} `;
- else relative += `${seconds} second${seconds > 1 ? 's' : ''} `;
-
- if (diff <= 0) {
- relative += 'ago';
- }
-
- return relative;
- };
-
- const handleTsChange = (val) => {
- setTimestamp(val);
-
- if (!val || val.trim() === '') {
- setDateStr('');
- setLocalDateStr('');
- setRelativeTime('');
- setParsedDate(null);
- return;
- }
-
- // Try parsing as timestamp first
- let date;
- const numVal = parseInt(val, 10);
-
- if (!isNaN(numVal) && val.length >= 10) {
- // Assume it's a Unix timestamp
- const ts = numVal > 9999999999 ? numVal : numVal * 1000;
- date = new Date(ts);
- } else {
- // Try parsing as date string
- date = parseDateWithFormat(val, inputFormat);
- }
-
- if (!date || isNaN(date.getTime())) {
- setDateStr('Invalid date');
- setLocalDateStr('Invalid date');
- setRelativeTime('');
- setParsedDate(null);
- return;
- }
-
- setParsedDate(date);
-
- // Convert to source timezone
- const sourceDate = sourceTimezone === 'local' ? date : convertToTimezone(date, sourceTimezone);
-
- // Convert to target timezone
- const targetDate = targetTimezone === 'local' ? sourceDate : convertToTimezone(sourceDate, targetTimezone);
-
- const formatted = formatDate(targetDate, outputFormat, customOutputFormat);
- setDateStr(formatted);
- setLocalDateStr(targetDate.toLocaleString());
- setRelativeTime(calculateRelativeTime(date));
- };
-
- const handleDateChange = (val) => {
- setDateStr(val);
-
- const date = parseDateWithFormat(val, outputFormat);
-
- if (date && !isNaN(date.getTime())) {
- setParsedDate(date);
- const ts = Math.floor(date.getTime() / 1000);
- setTimestamp(ts.toString());
- setLocalDateStr(date.toLocaleString());
- setRelativeTime(calculateRelativeTime(date));
- }
- };
-
- const setNow = () => {
- const now = Math.floor(Date.now() / 1000);
- handleTsChange(now.toString());
- };
-
- const isValidTimestamp = timestamp && !isNaN(timestamp) && timestamp.length > 0;
-
- return (
-
-
-
-
-
-
- handleTsChange(e.target.value)}
- placeholder="Enter timestamp or date..."
- style={{ fontFamily: "'IBM Plex Mono', monospace" }}
- />
-
-
-
-
-
-
-
-
-
-
- item ? item.label : ''}
- selectedItem={DATE_FORMATS.find(f => f.id === inputFormat)}
- onChange={({ selectedItem }) => {
- if (selectedItem) {
- setInputFormat(selectedItem.id);
- }
- }}
- size="sm"
- />
-
-
- {inputFormat === 'custom' && (
-
- setCustomInputFormat(e.target.value)}
- placeholder="YYYY-MM-DD HH:mm:ss"
- size="sm"
- />
-
- )}
-
-
-
- item ? item.label : ''}
- selectedItem={TIMEZONES.find(t => t.id === sourceTimezone)}
- onChange={({ selectedItem }) => {
- if (selectedItem) {
- setSourceTimezone(selectedItem.id);
- if (timestamp) handleTsChange(timestamp);
- }
- }}
- size="sm"
- />
-
-
-
-
-
-
-
-
- item ? item.label : ''}
- selectedItem={DATE_FORMATS.find(f => f.id === outputFormat)}
- onChange={({ selectedItem }) => {
- if (selectedItem) {
- setOutputFormat(selectedItem.id);
- if (timestamp) handleTsChange(timestamp);
- }
- }}
- size="sm"
- />
-
-
- {outputFormat === 'custom' && (
-
- setCustomOutputFormat(e.target.value)}
- placeholder="YYYY-MM-DD HH:mm:ss"
- size="sm"
- />
-
- )}
-
-
-
- item ? item.label : ''}
- selectedItem={TIMEZONES.find(t => t.id === targetTimezone)}
- onChange={({ selectedItem }) => {
- if (selectedItem) {
- setTargetTimezone(selectedItem.id);
- if (timestamp) handleTsChange(timestamp);
- }
- }}
- size="sm"
- />
-
-
-
-
- {relativeTime && isValidTimestamp && (
-
- {relativeTime}
-
- from now
-
-
- )}
-
-
- f.id === outputFormat)?.label || 'ISO'})`}
- value={dateStr}
- onChange={(e) => handleDateChange(e.target.value)}
- placeholder="Formatted date will appear here..."
- />
- t.id === targetTimezone)?.label || 'Local'})`}
- value={localDateStr}
- readOnly
- placeholder="Local date and time will appear here..."
- />
-
-
- );
-}
diff --git a/src/utils/backendBridge.js b/src/utils/backendBridge.js
deleted file mode 100644
index f29d4bf..0000000
--- a/src/utils/backendBridge.js
+++ /dev/null
@@ -1,76 +0,0 @@
-/**
- * Backend Bridge
- *
- * seamless communication bridge that detects if the app is running
- * in Wails (native) or Web Mode (browser) and routes requests accordingly.
- */
-
-// Detect Wails environment by checking for window.runtime
-const isWails = () => typeof window.runtime !== 'undefined';
-
-// Base URL for the local HTTP server in Web Mode
-const API_BASE_URL = 'http://localhost:8081';
-
-/**
- * Generic helper to call backend methods
- */
-async function callBackend(service, method, ...args) {
- if (isWails()) {
- // Direct Wails binding call
- // Wails generates bindings under window.go.wails (not window.go.main)
- try {
- return await window.go.wails[service][method](...args);
- } catch (err) {
- console.error(`Wails call failed: ${service}.${method}`, err);
- throw err;
- }
- } else {
- // Remote HTTP call for Web Mode
- try {
- const response = await fetch(`${API_BASE_URL}/api/${service}/${method}`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({ args }),
- });
-
- if (!response.ok) {
- const errorText = await response.text();
- throw new Error(`HTTP error ${response.status}: ${errorText}`);
- }
-
- return await response.json();
- } catch (err) {
- console.error(`Web API call failed: ${service}/${method}`, err);
- throw err;
- }
- }
-}
-
-// Exposed Backend Services
-export const Backend = {
- JWTService: {
- Decode: (token) => callBackend('JWTService', 'Decode', token),
- Encode: (header, payload, algo, secret) => callBackend('JWTService', 'Encode', header, payload, algo, secret),
- Verify: (token, secret, encoding) => callBackend('JWTService', 'Verify', token, secret, encoding)
- },
- ConversionService: {
- Convert: (input, category, method, config) => callBackend('ConversionService', 'Convert', input, category, method, config)
- },
- BarcodeService: {
- GenerateBarcode: (req) => callBackend('BarcodeService', 'GenerateBarcode', req),
- GetQRErrorLevels: () => callBackend('BarcodeService', 'GetQRErrorLevels'),
- GetBarcodeSizes: () => callBackend('BarcodeService', 'GetBarcodeSizes'),
- GetBarcodeStandards: () => callBackend('BarcodeService', 'GetBarcodeStandards'),
- ValidateContent: (content, standard) => callBackend('BarcodeService', 'ValidateContent', content, standard)
- },
- DataGeneratorService: {
- Generate: (req) => callBackend('DataGeneratorService', 'Generate', req),
- GetPresets: () => callBackend('DataGeneratorService', 'GetPresets'),
- ValidateTemplate: (template) => callBackend('DataGeneratorService', 'ValidateTemplate', template)
- },
- CodeFormatterService: {
- Format: (req) => callBackend('CodeFormatterService', 'Format', req)
- }
-};
diff --git a/vite.config.js b/vite.config.js
index 7b6a07b..0d0b508 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -1,9 +1,10 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
+import wails from "@wailsio/runtime/plugins/vite";
// https://vitejs.dev/config/
export default defineConfig({
- plugins: [react()],
+ plugins: [react(), wails("./bindings")],
css: {
preprocessorOptions: {
scss: {
@@ -13,4 +14,4 @@ export default defineConfig({
}
}
}
-})
+})
\ No newline at end of file
diff --git a/wails.json b/wails.json
index f108099..078d1f9 100644
--- a/wails.json
+++ b/wails.json
@@ -1,14 +1,10 @@
{
- "$schema": "https://wails.io/schemas/config.v2.json",
- "name": "dev-toolbox",
- "outputfilename": "dev-toolbox",
- "frontend:install": "bun install",
- "frontend:build": "bun run build",
- "frontend:dev:watcher": "bun run dev",
- "frontend:dev:serverUrl": "auto",
- "frontend:dir": ".",
- "author": {
- "name": "Vuong",
- "email": "3168632+vuon9@users.noreply.github.com"
+ "name": "DevToolbox",
+ "frontend": {
+ "dir": "./frontend",
+ "install": "bun install",
+ "build": "bun run build",
+ "dev": "bun run dev",
+ "devServerUrl": "http://localhost:5173"
}
-}
+}
\ No newline at end of file
diff --git a/wailsjs/go/main/App.d.ts b/wailsjs/go/main/App.d.ts
deleted file mode 100755
index 02a3bb9..0000000
--- a/wailsjs/go/main/App.d.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Ã MODIWL
-// This file is automatically generated. DO NOT EDIT
-
-export function Greet(arg1:string):Promise
;
diff --git a/wailsjs/go/main/App.js b/wailsjs/go/main/App.js
deleted file mode 100755
index c71ae77..0000000
--- a/wailsjs/go/main/App.js
+++ /dev/null
@@ -1,7 +0,0 @@
-// @ts-check
-// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Ã MODIWL
-// This file is automatically generated. DO NOT EDIT
-
-export function Greet(arg1) {
- return window['go']['main']['App']['Greet'](arg1);
-}
diff --git a/wailsjs/go/models.ts b/wailsjs/go/models.ts
deleted file mode 100755
index 2e2cbec..0000000
--- a/wailsjs/go/models.ts
+++ /dev/null
@@ -1,285 +0,0 @@
-export namespace codeformatter {
-
- export class FormatRequest {
- input: string;
- formatType: string;
- filter?: string;
- minify: boolean;
-
- static createFrom(source: any = {}) {
- return new FormatRequest(source);
- }
-
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.input = source["input"];
- this.formatType = source["formatType"];
- this.filter = source["filter"];
- this.minify = source["minify"];
- }
- }
- export class FormatResponse {
- output: string;
- error?: string;
-
- static createFrom(source: any = {}) {
- return new FormatResponse(source);
- }
-
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.output = source["output"];
- this.error = source["error"];
- }
- }
-
-}
-
-export namespace datagenerator {
-
- export class GenerateRequest {
- template: string;
- variables: Record;
- batchCount: number;
- outputFormat: string;
- separator: string;
-
- static createFrom(source: any = {}) {
- return new GenerateRequest(source);
- }
-
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.template = source["template"];
- this.variables = source["variables"];
- this.batchCount = source["batchCount"];
- this.outputFormat = source["outputFormat"];
- this.separator = source["separator"];
- }
- }
- export class GenerateResponse {
- output: string;
- count: number;
- error?: string;
- durationMs: number;
-
- static createFrom(source: any = {}) {
- return new GenerateResponse(source);
- }
-
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.output = source["output"];
- this.count = source["count"];
- this.error = source["error"];
- this.durationMs = source["durationMs"];
- }
- }
- export class Variable {
- name: string;
- type: string;
- default: any;
- options?: string[];
- min?: number;
- max?: number;
- description?: string;
-
- static createFrom(source: any = {}) {
- return new Variable(source);
- }
-
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.name = source["name"];
- this.type = source["type"];
- this.default = source["default"];
- this.options = source["options"];
- this.min = source["min"];
- this.max = source["max"];
- this.description = source["description"];
- }
- }
- export class TemplatePreset {
- id: string;
- name: string;
- description: string;
- template: string;
- variables: Variable[];
-
- static createFrom(source: any = {}) {
- return new TemplatePreset(source);
- }
-
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.id = source["id"];
- this.name = source["name"];
- this.description = source["description"];
- this.template = source["template"];
- this.variables = this.convertValues(source["variables"], Variable);
- }
-
- convertValues(a: any, classs: any, asMap: boolean = false): any {
- if (!a) {
- return a;
- }
- if (a.slice && a.map) {
- return (a as any[]).map(elem => this.convertValues(elem, classs));
- } else if ("object" === typeof a) {
- if (asMap) {
- for (const key of Object.keys(a)) {
- a[key] = new classs(a[key]);
- }
- return a;
- }
- return new classs(a);
- }
- return a;
- }
- }
- export class PresetsResponse {
- presets: TemplatePreset[];
- error?: string;
-
- static createFrom(source: any = {}) {
- return new PresetsResponse(source);
- }
-
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.presets = this.convertValues(source["presets"], TemplatePreset);
- this.error = source["error"];
- }
-
- convertValues(a: any, classs: any, asMap: boolean = false): any {
- if (!a) {
- return a;
- }
- if (a.slice && a.map) {
- return (a as any[]).map(elem => this.convertValues(elem, classs));
- } else if ("object" === typeof a) {
- if (asMap) {
- for (const key of Object.keys(a)) {
- a[key] = new classs(a[key]);
- }
- return a;
- }
- return new classs(a);
- }
- return a;
- }
- }
-
- export class ValidationResult {
- valid: boolean;
- error?: string;
- message?: string;
-
- static createFrom(source: any = {}) {
- return new ValidationResult(source);
- }
-
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.valid = source["valid"];
- this.error = source["error"];
- this.message = source["message"];
- }
- }
-
-}
-
-export namespace jwt {
-
- export class DecodeResponse {
- header: Record;
- payload: Record;
- signature: string;
- isValid: boolean;
- error: string;
-
- static createFrom(source: any = {}) {
- return new DecodeResponse(source);
- }
-
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.header = source["header"];
- this.payload = source["payload"];
- this.signature = source["signature"];
- this.isValid = source["isValid"];
- this.error = source["error"];
- }
- }
- export class EncodeResponse {
- token: string;
- error: string;
-
- static createFrom(source: any = {}) {
- return new EncodeResponse(source);
- }
-
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.token = source["token"];
- this.error = source["error"];
- }
- }
- export class VerifyResponse {
- isValid: boolean;
- validationMessage: string;
- error: string;
-
- static createFrom(source: any = {}) {
- return new VerifyResponse(source);
- }
-
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.isValid = source["isValid"];
- this.validationMessage = source["validationMessage"];
- this.error = source["error"];
- }
- }
-
-}
-
-export namespace wails {
-
- export class GenerateBarcodeRequest {
- content: string;
- standard: string;
- size: number;
- level: string;
- format: string;
-
- static createFrom(source: any = {}) {
- return new GenerateBarcodeRequest(source);
- }
-
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.content = source["content"];
- this.standard = source["standard"];
- this.size = source["size"];
- this.level = source["level"];
- this.format = source["format"];
- }
- }
- export class GenerateBarcodeResponse {
- dataUrl: string;
- error: string;
-
- static createFrom(source: any = {}) {
- return new GenerateBarcodeResponse(source);
- }
-
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.dataUrl = source["dataUrl"];
- this.error = source["error"];
- }
- }
-
-}
-
diff --git a/wailsjs/go/wails/BarcodeService.d.ts b/wailsjs/go/wails/BarcodeService.d.ts
deleted file mode 100755
index 130f5df..0000000
--- a/wailsjs/go/wails/BarcodeService.d.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Ã MODIWL
-// This file is automatically generated. DO NOT EDIT
-import {wails} from '../models';
-import {context} from '../models';
-
-export function GenerateBarcode(arg1:wails.GenerateBarcodeRequest):Promise;
-
-export function GetBarcodeSizes():Promise>>;
-
-export function GetBarcodeStandards():Promise>>;
-
-export function GetQRErrorLevels():Promise>>;
-
-export function Startup(arg1:context.Context):Promise;
-
-export function ValidateContent(arg1:string,arg2:string):Promise>;
diff --git a/wailsjs/go/wails/BarcodeService.js b/wailsjs/go/wails/BarcodeService.js
deleted file mode 100755
index b13f4d2..0000000
--- a/wailsjs/go/wails/BarcodeService.js
+++ /dev/null
@@ -1,27 +0,0 @@
-// @ts-check
-// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Ã MODIWL
-// This file is automatically generated. DO NOT EDIT
-
-export function GenerateBarcode(arg1) {
- return window['go']['wails']['BarcodeService']['GenerateBarcode'](arg1);
-}
-
-export function GetBarcodeSizes() {
- return window['go']['wails']['BarcodeService']['GetBarcodeSizes']();
-}
-
-export function GetBarcodeStandards() {
- return window['go']['wails']['BarcodeService']['GetBarcodeStandards']();
-}
-
-export function GetQRErrorLevels() {
- return window['go']['wails']['BarcodeService']['GetQRErrorLevels']();
-}
-
-export function Startup(arg1) {
- return window['go']['wails']['BarcodeService']['Startup'](arg1);
-}
-
-export function ValidateContent(arg1, arg2) {
- return window['go']['wails']['BarcodeService']['ValidateContent'](arg1, arg2);
-}
diff --git a/wailsjs/go/wails/CodeFormatterService.d.ts b/wailsjs/go/wails/CodeFormatterService.d.ts
deleted file mode 100755
index 132b94e..0000000
--- a/wailsjs/go/wails/CodeFormatterService.d.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Ã MODIWL
-// This file is automatically generated. DO NOT EDIT
-import {codeformatter} from '../models';
-import {context} from '../models';
-
-export function Format(arg1:codeformatter.FormatRequest):Promise;
-
-export function Startup(arg1:context.Context):Promise;
diff --git a/wailsjs/go/wails/CodeFormatterService.js b/wailsjs/go/wails/CodeFormatterService.js
deleted file mode 100755
index 3a10606..0000000
--- a/wailsjs/go/wails/CodeFormatterService.js
+++ /dev/null
@@ -1,11 +0,0 @@
-// @ts-check
-// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Ã MODIWL
-// This file is automatically generated. DO NOT EDIT
-
-export function Format(arg1) {
- return window['go']['wails']['CodeFormatterService']['Format'](arg1);
-}
-
-export function Startup(arg1) {
- return window['go']['wails']['CodeFormatterService']['Startup'](arg1);
-}
diff --git a/wailsjs/go/wails/ConversionService.d.ts b/wailsjs/go/wails/ConversionService.d.ts
deleted file mode 100755
index 087279d..0000000
--- a/wailsjs/go/wails/ConversionService.d.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Ã MODIWL
-// This file is automatically generated. DO NOT EDIT
-import {context} from '../models';
-
-export function Convert(arg1:string,arg2:string,arg3:string,arg4:Record):Promise;
-
-export function Startup(arg1:context.Context):Promise;
diff --git a/wailsjs/go/wails/ConversionService.js b/wailsjs/go/wails/ConversionService.js
deleted file mode 100755
index b9ba7a0..0000000
--- a/wailsjs/go/wails/ConversionService.js
+++ /dev/null
@@ -1,11 +0,0 @@
-// @ts-check
-// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Ã MODIWL
-// This file is automatically generated. DO NOT EDIT
-
-export function Convert(arg1, arg2, arg3, arg4) {
- return window['go']['wails']['ConversionService']['Convert'](arg1, arg2, arg3, arg4);
-}
-
-export function Startup(arg1) {
- return window['go']['wails']['ConversionService']['Startup'](arg1);
-}
diff --git a/wailsjs/go/wails/DataGeneratorService.d.ts b/wailsjs/go/wails/DataGeneratorService.d.ts
deleted file mode 100755
index 949e444..0000000
--- a/wailsjs/go/wails/DataGeneratorService.d.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Ã MODIWL
-// This file is automatically generated. DO NOT EDIT
-import {datagenerator} from '../models';
-import {context} from '../models';
-
-export function Generate(arg1:datagenerator.GenerateRequest):Promise;
-
-export function GetPresets():Promise;
-
-export function Startup(arg1:context.Context):Promise;
-
-export function ValidateTemplate(arg1:string):Promise;
diff --git a/wailsjs/go/wails/DataGeneratorService.js b/wailsjs/go/wails/DataGeneratorService.js
deleted file mode 100755
index f0324b3..0000000
--- a/wailsjs/go/wails/DataGeneratorService.js
+++ /dev/null
@@ -1,19 +0,0 @@
-// @ts-check
-// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Ã MODIWL
-// This file is automatically generated. DO NOT EDIT
-
-export function Generate(arg1) {
- return window['go']['wails']['DataGeneratorService']['Generate'](arg1);
-}
-
-export function GetPresets() {
- return window['go']['wails']['DataGeneratorService']['GetPresets']();
-}
-
-export function Startup(arg1) {
- return window['go']['wails']['DataGeneratorService']['Startup'](arg1);
-}
-
-export function ValidateTemplate(arg1) {
- return window['go']['wails']['DataGeneratorService']['ValidateTemplate'](arg1);
-}
diff --git a/wailsjs/go/wails/JWTService.d.ts b/wailsjs/go/wails/JWTService.d.ts
deleted file mode 100755
index 061901c..0000000
--- a/wailsjs/go/wails/JWTService.d.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Ã MODIWL
-// This file is automatically generated. DO NOT EDIT
-import {jwt} from '../models';
-import {context} from '../models';
-
-export function Decode(arg1:string):Promise;
-
-export function Encode(arg1:string,arg2:string,arg3:string,arg4:string):Promise;
-
-export function Startup(arg1:context.Context):Promise;
-
-export function Verify(arg1:string,arg2:string,arg3:string):Promise;
diff --git a/wailsjs/go/wails/JWTService.js b/wailsjs/go/wails/JWTService.js
deleted file mode 100755
index 7b05874..0000000
--- a/wailsjs/go/wails/JWTService.js
+++ /dev/null
@@ -1,19 +0,0 @@
-// @ts-check
-// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Ã MODIWL
-// This file is automatically generated. DO NOT EDIT
-
-export function Decode(arg1) {
- return window['go']['wails']['JWTService']['Decode'](arg1);
-}
-
-export function Encode(arg1, arg2, arg3, arg4) {
- return window['go']['wails']['JWTService']['Encode'](arg1, arg2, arg3, arg4);
-}
-
-export function Startup(arg1) {
- return window['go']['wails']['JWTService']['Startup'](arg1);
-}
-
-export function Verify(arg1, arg2, arg3) {
- return window['go']['wails']['JWTService']['Verify'](arg1, arg2, arg3);
-}
diff --git a/wailsjs/runtime/package.json b/wailsjs/runtime/package.json
deleted file mode 100644
index 1e7c8a5..0000000
--- a/wailsjs/runtime/package.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "name": "@wailsapp/runtime",
- "version": "2.0.0",
- "description": "Wails Javascript runtime library",
- "main": "runtime.js",
- "types": "runtime.d.ts",
- "scripts": {
- },
- "repository": {
- "type": "git",
- "url": "git+https://github.com/wailsapp/wails.git"
- },
- "keywords": [
- "Wails",
- "Javascript",
- "Go"
- ],
- "author": "Lea Anthony ",
- "license": "MIT",
- "bugs": {
- "url": "https://github.com/wailsapp/wails/issues"
- },
- "homepage": "https://github.com/wailsapp/wails#readme"
-}
diff --git a/wailsjs/runtime/runtime.d.ts b/wailsjs/runtime/runtime.d.ts
deleted file mode 100644
index 4445dac..0000000
--- a/wailsjs/runtime/runtime.d.ts
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- _ __ _ __
-| | / /___ _(_) /____
-| | /| / / __ `/ / / ___/
-| |/ |/ / /_/ / / (__ )
-|__/|__/\__,_/_/_/____/
-The electron alternative for Go
-(c) Lea Anthony 2019-present
-*/
-
-export interface Position {
- x: number;
- y: number;
-}
-
-export interface Size {
- w: number;
- h: number;
-}
-
-export interface Screen {
- isCurrent: boolean;
- isPrimary: boolean;
- width : number
- height : number
-}
-
-// Environment information such as platform, buildtype, ...
-export interface EnvironmentInfo {
- buildType: string;
- platform: string;
- arch: string;
-}
-
-// [EventsEmit](https://wails.io/docs/reference/runtime/events#eventsemit)
-// emits the given event. Optional data may be passed with the event.
-// This will trigger any event listeners.
-export function EventsEmit(eventName: string, ...data: any): void;
-
-// [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name.
-export function EventsOn(eventName: string, callback: (...data: any) => void): () => void;
-
-// [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple)
-// sets up a listener for the given event name, but will only trigger a given number times.
-export function EventsOnMultiple(eventName: string, callback: (...data: any) => void, maxCallbacks: number): () => void;
-
-// [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce)
-// sets up a listener for the given event name, but will only trigger once.
-export function EventsOnce(eventName: string, callback: (...data: any) => void): () => void;
-
-// [EventsOff](https://wails.io/docs/reference/runtime/events#eventsoff)
-// unregisters the listener for the given event name.
-export function EventsOff(eventName: string, ...additionalEventNames: string[]): void;
-
-// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall)
-// unregisters all listeners.
-export function EventsOffAll(): void;
-
-// [LogPrint](https://wails.io/docs/reference/runtime/log#logprint)
-// logs the given message as a raw message
-export function LogPrint(message: string): void;
-
-// [LogTrace](https://wails.io/docs/reference/runtime/log#logtrace)
-// logs the given message at the `trace` log level.
-export function LogTrace(message: string): void;
-
-// [LogDebug](https://wails.io/docs/reference/runtime/log#logdebug)
-// logs the given message at the `debug` log level.
-export function LogDebug(message: string): void;
-
-// [LogError](https://wails.io/docs/reference/runtime/log#logerror)
-// logs the given message at the `error` log level.
-export function LogError(message: string): void;
-
-// [LogFatal](https://wails.io/docs/reference/runtime/log#logfatal)
-// logs the given message at the `fatal` log level.
-// The application will quit after calling this method.
-export function LogFatal(message: string): void;
-
-// [LogInfo](https://wails.io/docs/reference/runtime/log#loginfo)
-// logs the given message at the `info` log level.
-export function LogInfo(message: string): void;
-
-// [LogWarning](https://wails.io/docs/reference/runtime/log#logwarning)
-// logs the given message at the `warning` log level.
-export function LogWarning(message: string): void;
-
-// [WindowReload](https://wails.io/docs/reference/runtime/window#windowreload)
-// Forces a reload by the main application as well as connected browsers.
-export function WindowReload(): void;
-
-// [WindowReloadApp](https://wails.io/docs/reference/runtime/window#windowreloadapp)
-// Reloads the application frontend.
-export function WindowReloadApp(): void;
-
-// [WindowSetAlwaysOnTop](https://wails.io/docs/reference/runtime/window#windowsetalwaysontop)
-// Sets the window AlwaysOnTop or not on top.
-export function WindowSetAlwaysOnTop(b: boolean): void;
-
-// [WindowSetSystemDefaultTheme](https://wails.io/docs/next/reference/runtime/window#windowsetsystemdefaulttheme)
-// *Windows only*
-// Sets window theme to system default (dark/light).
-export function WindowSetSystemDefaultTheme(): void;
-
-// [WindowSetLightTheme](https://wails.io/docs/next/reference/runtime/window#windowsetlighttheme)
-// *Windows only*
-// Sets window to light theme.
-export function WindowSetLightTheme(): void;
-
-// [WindowSetDarkTheme](https://wails.io/docs/next/reference/runtime/window#windowsetdarktheme)
-// *Windows only*
-// Sets window to dark theme.
-export function WindowSetDarkTheme(): void;
-
-// [WindowCenter](https://wails.io/docs/reference/runtime/window#windowcenter)
-// Centers the window on the monitor the window is currently on.
-export function WindowCenter(): void;
-
-// [WindowSetTitle](https://wails.io/docs/reference/runtime/window#windowsettitle)
-// Sets the text in the window title bar.
-export function WindowSetTitle(title: string): void;
-
-// [WindowFullscreen](https://wails.io/docs/reference/runtime/window#windowfullscreen)
-// Makes the window full screen.
-export function WindowFullscreen(): void;
-
-// [WindowUnfullscreen](https://wails.io/docs/reference/runtime/window#windowunfullscreen)
-// Restores the previous window dimensions and position prior to full screen.
-export function WindowUnfullscreen(): void;
-
-// [WindowIsFullscreen](https://wails.io/docs/reference/runtime/window#windowisfullscreen)
-// Returns the state of the window, i.e. whether the window is in full screen mode or not.
-export function WindowIsFullscreen(): Promise;
-
-// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize)
-// Sets the width and height of the window.
-export function WindowSetSize(width: number, height: number): void;
-
-// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize)
-// Gets the width and height of the window.
-export function WindowGetSize(): Promise;
-
-// [WindowSetMaxSize](https://wails.io/docs/reference/runtime/window#windowsetmaxsize)
-// Sets the maximum window size. Will resize the window if the window is currently larger than the given dimensions.
-// Setting a size of 0,0 will disable this constraint.
-export function WindowSetMaxSize(width: number, height: number): void;
-
-// [WindowSetMinSize](https://wails.io/docs/reference/runtime/window#windowsetminsize)
-// Sets the minimum window size. Will resize the window if the window is currently smaller than the given dimensions.
-// Setting a size of 0,0 will disable this constraint.
-export function WindowSetMinSize(width: number, height: number): void;
-
-// [WindowSetPosition](https://wails.io/docs/reference/runtime/window#windowsetposition)
-// Sets the window position relative to the monitor the window is currently on.
-export function WindowSetPosition(x: number, y: number): void;
-
-// [WindowGetPosition](https://wails.io/docs/reference/runtime/window#windowgetposition)
-// Gets the window position relative to the monitor the window is currently on.
-export function WindowGetPosition(): Promise;
-
-// [WindowHide](https://wails.io/docs/reference/runtime/window#windowhide)
-// Hides the window.
-export function WindowHide(): void;
-
-// [WindowShow](https://wails.io/docs/reference/runtime/window#windowshow)
-// Shows the window, if it is currently hidden.
-export function WindowShow(): void;
-
-// [WindowMaximise](https://wails.io/docs/reference/runtime/window#windowmaximise)
-// Maximises the window to fill the screen.
-export function WindowMaximise(): void;
-
-// [WindowToggleMaximise](https://wails.io/docs/reference/runtime/window#windowtogglemaximise)
-// Toggles between Maximised and UnMaximised.
-export function WindowToggleMaximise(): void;
-
-// [WindowUnmaximise](https://wails.io/docs/reference/runtime/window#windowunmaximise)
-// Restores the window to the dimensions and position prior to maximising.
-export function WindowUnmaximise(): void;
-
-// [WindowIsMaximised](https://wails.io/docs/reference/runtime/window#windowismaximised)
-// Returns the state of the window, i.e. whether the window is maximised or not.
-export function WindowIsMaximised(): Promise;
-
-// [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise)
-// Minimises the window.
-export function WindowMinimise(): void;
-
-// [WindowUnminimise](https://wails.io/docs/reference/runtime/window#windowunminimise)
-// Restores the window to the dimensions and position prior to minimising.
-export function WindowUnminimise(): void;
-
-// [WindowIsMinimised](https://wails.io/docs/reference/runtime/window#windowisminimised)
-// Returns the state of the window, i.e. whether the window is minimised or not.
-export function WindowIsMinimised(): Promise;
-
-// [WindowIsNormal](https://wails.io/docs/reference/runtime/window#windowisnormal)
-// Returns the state of the window, i.e. whether the window is normal or not.
-export function WindowIsNormal(): Promise;
-
-// [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour)
-// Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels.
-export function WindowSetBackgroundColour(R: number, G: number, B: number, A: number): void;
-
-// [ScreenGetAll](https://wails.io/docs/reference/runtime/window#screengetall)
-// Gets the all screens. Call this anew each time you want to refresh data from the underlying windowing system.
-export function ScreenGetAll(): Promise;
-
-// [BrowserOpenURL](https://wails.io/docs/reference/runtime/browser#browseropenurl)
-// Opens the given URL in the system browser.
-export function BrowserOpenURL(url: string): void;
-
-// [Environment](https://wails.io/docs/reference/runtime/intro#environment)
-// Returns information about the environment
-export function Environment(): Promise;
-
-// [Quit](https://wails.io/docs/reference/runtime/intro#quit)
-// Quits the application.
-export function Quit(): void;
-
-// [Hide](https://wails.io/docs/reference/runtime/intro#hide)
-// Hides the application.
-export function Hide(): void;
-
-// [Show](https://wails.io/docs/reference/runtime/intro#show)
-// Shows the application.
-export function Show(): void;
-
-// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext)
-// Returns the current text stored on clipboard
-export function ClipboardGetText(): Promise;
-
-// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext)
-// Sets a text on the clipboard
-export function ClipboardSetText(text: string): Promise;
-
-// [OnFileDrop](https://wails.io/docs/reference/runtime/draganddrop#onfiledrop)
-// OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
-export function OnFileDrop(callback: (x: number, y: number ,paths: string[]) => void, useDropTarget: boolean) :void
-
-// [OnFileDropOff](https://wails.io/docs/reference/runtime/draganddrop#dragandddropoff)
-// OnFileDropOff removes the drag and drop listeners and handlers.
-export function OnFileDropOff() :void
-
-// Check if the file path resolver is available
-export function CanResolveFilePaths(): boolean;
-
-// Resolves file paths for an array of files
-export function ResolveFilePaths(files: File[]): void
\ No newline at end of file
diff --git a/wailsjs/runtime/runtime.js b/wailsjs/runtime/runtime.js
deleted file mode 100644
index 7cb89d7..0000000
--- a/wailsjs/runtime/runtime.js
+++ /dev/null
@@ -1,242 +0,0 @@
-/*
- _ __ _ __
-| | / /___ _(_) /____
-| | /| / / __ `/ / / ___/
-| |/ |/ / /_/ / / (__ )
-|__/|__/\__,_/_/_/____/
-The electron alternative for Go
-(c) Lea Anthony 2019-present
-*/
-
-export function LogPrint(message) {
- window.runtime.LogPrint(message);
-}
-
-export function LogTrace(message) {
- window.runtime.LogTrace(message);
-}
-
-export function LogDebug(message) {
- window.runtime.LogDebug(message);
-}
-
-export function LogInfo(message) {
- window.runtime.LogInfo(message);
-}
-
-export function LogWarning(message) {
- window.runtime.LogWarning(message);
-}
-
-export function LogError(message) {
- window.runtime.LogError(message);
-}
-
-export function LogFatal(message) {
- window.runtime.LogFatal(message);
-}
-
-export function EventsOnMultiple(eventName, callback, maxCallbacks) {
- return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks);
-}
-
-export function EventsOn(eventName, callback) {
- return EventsOnMultiple(eventName, callback, -1);
-}
-
-export function EventsOff(eventName, ...additionalEventNames) {
- return window.runtime.EventsOff(eventName, ...additionalEventNames);
-}
-
-export function EventsOffAll() {
- return window.runtime.EventsOffAll();
-}
-
-export function EventsOnce(eventName, callback) {
- return EventsOnMultiple(eventName, callback, 1);
-}
-
-export function EventsEmit(eventName) {
- let args = [eventName].slice.call(arguments);
- return window.runtime.EventsEmit.apply(null, args);
-}
-
-export function WindowReload() {
- window.runtime.WindowReload();
-}
-
-export function WindowReloadApp() {
- window.runtime.WindowReloadApp();
-}
-
-export function WindowSetAlwaysOnTop(b) {
- window.runtime.WindowSetAlwaysOnTop(b);
-}
-
-export function WindowSetSystemDefaultTheme() {
- window.runtime.WindowSetSystemDefaultTheme();
-}
-
-export function WindowSetLightTheme() {
- window.runtime.WindowSetLightTheme();
-}
-
-export function WindowSetDarkTheme() {
- window.runtime.WindowSetDarkTheme();
-}
-
-export function WindowCenter() {
- window.runtime.WindowCenter();
-}
-
-export function WindowSetTitle(title) {
- window.runtime.WindowSetTitle(title);
-}
-
-export function WindowFullscreen() {
- window.runtime.WindowFullscreen();
-}
-
-export function WindowUnfullscreen() {
- window.runtime.WindowUnfullscreen();
-}
-
-export function WindowIsFullscreen() {
- return window.runtime.WindowIsFullscreen();
-}
-
-export function WindowGetSize() {
- return window.runtime.WindowGetSize();
-}
-
-export function WindowSetSize(width, height) {
- window.runtime.WindowSetSize(width, height);
-}
-
-export function WindowSetMaxSize(width, height) {
- window.runtime.WindowSetMaxSize(width, height);
-}
-
-export function WindowSetMinSize(width, height) {
- window.runtime.WindowSetMinSize(width, height);
-}
-
-export function WindowSetPosition(x, y) {
- window.runtime.WindowSetPosition(x, y);
-}
-
-export function WindowGetPosition() {
- return window.runtime.WindowGetPosition();
-}
-
-export function WindowHide() {
- window.runtime.WindowHide();
-}
-
-export function WindowShow() {
- window.runtime.WindowShow();
-}
-
-export function WindowMaximise() {
- window.runtime.WindowMaximise();
-}
-
-export function WindowToggleMaximise() {
- window.runtime.WindowToggleMaximise();
-}
-
-export function WindowUnmaximise() {
- window.runtime.WindowUnmaximise();
-}
-
-export function WindowIsMaximised() {
- return window.runtime.WindowIsMaximised();
-}
-
-export function WindowMinimise() {
- window.runtime.WindowMinimise();
-}
-
-export function WindowUnminimise() {
- window.runtime.WindowUnminimise();
-}
-
-export function WindowSetBackgroundColour(R, G, B, A) {
- window.runtime.WindowSetBackgroundColour(R, G, B, A);
-}
-
-export function ScreenGetAll() {
- return window.runtime.ScreenGetAll();
-}
-
-export function WindowIsMinimised() {
- return window.runtime.WindowIsMinimised();
-}
-
-export function WindowIsNormal() {
- return window.runtime.WindowIsNormal();
-}
-
-export function BrowserOpenURL(url) {
- window.runtime.BrowserOpenURL(url);
-}
-
-export function Environment() {
- return window.runtime.Environment();
-}
-
-export function Quit() {
- window.runtime.Quit();
-}
-
-export function Hide() {
- window.runtime.Hide();
-}
-
-export function Show() {
- window.runtime.Show();
-}
-
-export function ClipboardGetText() {
- return window.runtime.ClipboardGetText();
-}
-
-export function ClipboardSetText(text) {
- return window.runtime.ClipboardSetText(text);
-}
-
-/**
- * Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
- *
- * @export
- * @callback OnFileDropCallback
- * @param {number} x - x coordinate of the drop
- * @param {number} y - y coordinate of the drop
- * @param {string[]} paths - A list of file paths.
- */
-
-/**
- * OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
- *
- * @export
- * @param {OnFileDropCallback} callback - Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
- * @param {boolean} [useDropTarget=true] - Only call the callback when the drop finished on an element that has the drop target style. (--wails-drop-target)
- */
-export function OnFileDrop(callback, useDropTarget) {
- return window.runtime.OnFileDrop(callback, useDropTarget);
-}
-
-/**
- * OnFileDropOff removes the drag and drop listeners and handlers.
- */
-export function OnFileDropOff() {
- return window.runtime.OnFileDropOff();
-}
-
-export function CanResolveFilePaths() {
- return window.runtime.CanResolveFilePaths();
-}
-
-export function ResolveFilePaths(files) {
- return window.runtime.ResolveFilePaths(files);
-}
\ No newline at end of file