init
This commit is contained in:
13
frontend/index.html
Normal file
13
frontend/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>装备数据导出工具</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
4492
frontend/package-lock.json
generated
Normal file
4492
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
31
frontend/package.json
Normal file
31
frontend/package.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "equipment-analyzer-frontend",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^5.2.6",
|
||||
"antd": "^5.12.8",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "^7.6.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.43",
|
||||
"@types/react-dom": "^18.2.17",
|
||||
"@typescript-eslint/eslint-plugin": "^6.14.0",
|
||||
"@typescript-eslint/parser": "^6.14.0",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"eslint": "^8.55.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.5",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^5.0.8"
|
||||
}
|
||||
}
|
||||
1
frontend/package.json.md5
Normal file
1
frontend/package.json.md5
Normal file
@@ -0,0 +1 @@
|
||||
ab1f461d7da532ffc0ddf7b22c308322
|
||||
296
frontend/src/App.css
Normal file
296
frontend/src/App.css
Normal file
@@ -0,0 +1,296 @@
|
||||
#root {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 应用容器 */
|
||||
.app-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 头部样式 */
|
||||
.app-header {
|
||||
background: #fff;
|
||||
padding: 0 20px;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.app-header h1 {
|
||||
margin: 0;
|
||||
color: #333;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
/* 内容区域 */
|
||||
.app-content {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 左侧控制面板 */
|
||||
.control-panel {
|
||||
width: 300px;
|
||||
background: #fff;
|
||||
border-right: 1px solid #e8e8e8;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* 控制卡片 */
|
||||
.control-card {
|
||||
background: #fff;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 6px;
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.control-card h3 {
|
||||
margin: 0 0 16px 0;
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* 表单组 */
|
||||
.form-group {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-select {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.form-select:focus {
|
||||
outline: none;
|
||||
border-color: #1890ff;
|
||||
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
||||
}
|
||||
|
||||
/* 按钮组 */
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/* 按钮样式 */
|
||||
.btn {
|
||||
padding: 8px 16px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
background: #fff;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.btn:hover:not(:disabled) {
|
||||
border-color: #1890ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #1890ff;
|
||||
border-color: #1890ff;
|
||||
color: #fff;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
background: #40a9ff;
|
||||
border-color: #40a9ff;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #ff4d4f;
|
||||
border-color: #ff4d4f;
|
||||
color: #fff;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.btn-danger:hover:not(:disabled) {
|
||||
background: #ff7875;
|
||||
border-color: #ff7875;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: #52c41a;
|
||||
border-color: #52c41a;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-success:hover:not(:disabled) {
|
||||
background: #73d13d;
|
||||
border-color: #73d13d;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #f5f5f5;
|
||||
border-color: #d9d9d9;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.btn-secondary:hover:not(:disabled) {
|
||||
background: #e6e6e6;
|
||||
border-color: #bfbfbf;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 状态信息 */
|
||||
.status-info p {
|
||||
margin: 8px 0;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 右侧内容区域 */
|
||||
.content-area {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 数据卡片 */
|
||||
.data-card {
|
||||
background: #fff;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 6px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.data-card h3 {
|
||||
margin: 0 0 16px 0;
|
||||
color: #333;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.data-card p {
|
||||
margin: 10px 0;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 表格容器 */
|
||||
.table-container {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
/* 数据表格 */
|
||||
.data-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.data-table th,
|
||||
.data-table td {
|
||||
padding: 12px 8px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.data-table th {
|
||||
background: #fafafa;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.data-table tr:hover {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* 加载遮罩 */
|
||||
.loading-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 6px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.ant-btn[icon-type='setting'], .ant-btn-setting, .ant-btn-setting:active, .ant-btn-setting:focus {
|
||||
background: #fff !important;
|
||||
border-color: #d9d9d9 !important;
|
||||
color: #333 !important;
|
||||
}
|
||||
.ant-btn[icon-type='setting']:hover, .ant-btn-setting:hover {
|
||||
background: #e6e6e6 !important;
|
||||
border-color: #bfbfbf !important;
|
||||
color: #333 !important;
|
||||
}
|
||||
|
||||
/* 强制Antd按钮为普通圆角矩形风格 */
|
||||
/*.ant-btn, .ant-btn-default {*/
|
||||
/* border-radius: 6px !important;*/
|
||||
/* border-style: solid !important;*/
|
||||
/* border-width: 1px !important;*/
|
||||
/* box-shadow: none !important;*/
|
||||
/* background: #fff !important;*/
|
||||
/* color: #333 !important;*/
|
||||
/* border-color: #d9d9d9 !important;*/
|
||||
/*}*/
|
||||
/*.ant-btn:hover, .ant-btn-default:hover {*/
|
||||
/* background: #e6e6e6 !important;*/
|
||||
/* color: #333 !important;*/
|
||||
/* border-color: #bfbfbf !important;*/
|
||||
/*}*/
|
||||
/*.ant-btn .anticon {*/
|
||||
/* color: #333 !important;*/
|
||||
/*}*/
|
||||
77
frontend/src/App.tsx
Normal file
77
frontend/src/App.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import React, {useEffect, useState, useRef} from 'react'
|
||||
import {Button, Card, Layout, message, Select, Spin, Table} from 'antd'
|
||||
import {DownloadOutlined, PlayCircleOutlined, SettingOutlined, StopOutlined} from '@ant-design/icons'
|
||||
import './App.css'
|
||||
import {ExportData, GetCapturedData,GetNetworkInterfaces,
|
||||
ParseData,StartCapture,StopCapture, ReadRawJsonFile
|
||||
} from "../wailsjs/go/service/App";
|
||||
import { BrowserRouter as Router, Routes, Route, Link, useLocation } from 'react-router-dom';
|
||||
import { Menu } from 'antd';
|
||||
import CapturePage from './pages/CapturePage';
|
||||
import OptimizerPage from './pages/OptimizerPage';
|
||||
|
||||
const {Header, Content, Sider} = Layout
|
||||
|
||||
interface NetworkInterface {
|
||||
name: string
|
||||
description: string
|
||||
addresses: string[]
|
||||
is_loopback: boolean
|
||||
}
|
||||
|
||||
interface Equipment {
|
||||
id: string
|
||||
code: string
|
||||
ct: number
|
||||
e: number
|
||||
g: number
|
||||
l: boolean
|
||||
mg: number
|
||||
op: Array<[string, any]>
|
||||
p: number
|
||||
s: string
|
||||
sk: number
|
||||
}
|
||||
|
||||
interface CaptureResult {
|
||||
items: Equipment[]
|
||||
heroes: any[]
|
||||
}
|
||||
|
||||
function AppContent() {
|
||||
const location = useLocation();
|
||||
return (
|
||||
<Layout style={{ minHeight: '100vh' }}>
|
||||
<Header style={{ background: '#fff', padding: 0 }}>
|
||||
<Menu
|
||||
mode="horizontal"
|
||||
selectedKeys={[location.pathname]}
|
||||
style={{ fontSize: 16 }}
|
||||
>
|
||||
<Menu.Item key="/">
|
||||
<Link to="/">抓包</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="/optimizer">
|
||||
<Link to="/optimizer">配装优化</Link>
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
</Header>
|
||||
<Content style={{ padding: 0, minHeight: 280 }}>
|
||||
<Routes>
|
||||
<Route path="/" element={<CapturePage />} />
|
||||
<Route path="/optimizer" element={<OptimizerPage />} />
|
||||
</Routes>
|
||||
</Content>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<Router>
|
||||
<AppContent />
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
|
||||
export default App
|
||||
67
frontend/src/index.css
Normal file
67
frontend/src/index.css
Normal file
@@ -0,0 +1,67 @@
|
||||
:root {
|
||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
}
|
||||
11
frontend/src/main.tsx
Normal file
11
frontend/src/main.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import App from './App.tsx'
|
||||
import 'antd/dist/reset.css'
|
||||
import './index.css'
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
)
|
||||
460
frontend/src/pages/CapturePage.tsx
Normal file
460
frontend/src/pages/CapturePage.tsx
Normal file
@@ -0,0 +1,460 @@
|
||||
import React, {useEffect, useState, useRef} from 'react'
|
||||
import {Button, Card, Layout, message, Select, Spin, Table} from 'antd'
|
||||
import {DownloadOutlined, PlayCircleOutlined, SettingOutlined, StopOutlined} from '@ant-design/icons'
|
||||
import '../App.css'
|
||||
import {ExportData, GetCapturedData,GetNetworkInterfaces,
|
||||
ParseData,StartCapture,StopCapture, ReadRawJsonFile
|
||||
} from "../../wailsjs/go/service/App";
|
||||
|
||||
const {Header, Content, Sider} = Layout
|
||||
|
||||
interface NetworkInterface {
|
||||
name: string
|
||||
description: string
|
||||
addresses: string[]
|
||||
is_loopback: boolean
|
||||
}
|
||||
|
||||
interface Equipment {
|
||||
id: string
|
||||
code: string
|
||||
ct: number
|
||||
e: number
|
||||
g: number
|
||||
l: boolean
|
||||
mg: number
|
||||
op: Array<[string, any]>
|
||||
p: number
|
||||
s: string
|
||||
sk: number
|
||||
}
|
||||
|
||||
interface CaptureResult {
|
||||
items: Equipment[]
|
||||
heroes: any[]
|
||||
}
|
||||
|
||||
function CapturePage() {
|
||||
const [interfaces, setInterfaces] = useState<NetworkInterface[]>([])
|
||||
const [selectedInterface, setSelectedInterface] = useState<string>('')
|
||||
const [isCapturing, setIsCapturing] = useState(false)
|
||||
const [capturedData, setCapturedData] = useState<string[]>([])
|
||||
const [parsedData, setParsedData] = useState<CaptureResult | null>(null)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [interfaceLoading, setInterfaceLoading] = useState(false)
|
||||
const [uploadedFileName, setUploadedFileName] = useState<string>('')
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
const safeApiCall = async <T,>(
|
||||
apiCall: () => Promise<T>,
|
||||
errorMessage: string,
|
||||
fallbackValue: T
|
||||
): Promise<T> => {
|
||||
try {
|
||||
return await apiCall()
|
||||
} catch (error) {
|
||||
console.error(`${errorMessage}:`, error)
|
||||
message.error(errorMessage)
|
||||
return fallbackValue
|
||||
}
|
||||
}
|
||||
|
||||
const fetchInterfaces = async () => {
|
||||
setIsCapturing(false)
|
||||
setCapturedData([])
|
||||
setParsedData(null)
|
||||
setLoading(false)
|
||||
setInterfaceLoading(true)
|
||||
try {
|
||||
const response = await safeApiCall(
|
||||
() => GetNetworkInterfaces(),
|
||||
'获取网络接口失败,使用模拟数据',
|
||||
[
|
||||
{ name: 'eth0', description: 'Ethernet', addresses: ['192.168.1.100'], is_loopback: false },
|
||||
{ name: 'wlan0', description: 'Wireless', addresses: ['192.168.1.101'], is_loopback: false },
|
||||
{ name: 'lo', description: 'Loopback', addresses: ['127.0.0.1'], is_loopback: true }
|
||||
]
|
||||
)
|
||||
setInterfaces(response)
|
||||
let defaultSelected = ''
|
||||
for (const iface of response) {
|
||||
if (iface.addresses && iface.addresses.some(ip => ip.includes('192.168'))) {
|
||||
defaultSelected = iface.name
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!defaultSelected && response.length > 0) {
|
||||
defaultSelected = response[0].name
|
||||
}
|
||||
setSelectedInterface(defaultSelected)
|
||||
} catch (error) {
|
||||
console.error('获取网络接口时发生未知错误:', error)
|
||||
const defaultInterfaces = [
|
||||
{ name: 'eth0', description: 'Ethernet', addresses: ['192.168.1.100'], is_loopback: false },
|
||||
{ name: 'wlan0', description: 'Wireless', addresses: ['192.168.1.101'], is_loopback: false },
|
||||
{ name: 'lo', description: 'Loopback', addresses: ['127.0.0.1'], is_loopback: true }
|
||||
]
|
||||
setInterfaces(defaultInterfaces)
|
||||
setSelectedInterface(defaultInterfaces[0].name)
|
||||
} finally {
|
||||
setInterfaceLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const startCapture = async () => {
|
||||
if (!selectedInterface) {
|
||||
message.warning('请选择网络接口')
|
||||
return
|
||||
}
|
||||
setLoading(true)
|
||||
try {
|
||||
await safeApiCall(
|
||||
() => StartCapture(selectedInterface),
|
||||
'开始抓包失败,但界面将继续工作',
|
||||
undefined
|
||||
)
|
||||
setIsCapturing(true)
|
||||
message.success('开始抓包')
|
||||
} catch (error) {
|
||||
console.error('开始抓包时发生未知错误:', error)
|
||||
setIsCapturing(true)
|
||||
message.success('开始抓包(模拟模式)')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const stopCapture = async () => {
|
||||
setLoading(false)
|
||||
setIsCapturing(false)
|
||||
setCapturedData([])
|
||||
setParsedData(null)
|
||||
try {
|
||||
setLoading(true)
|
||||
await safeApiCall(
|
||||
() => StopCapture(),
|
||||
'停止抓包失败,但界面将继续工作',
|
||||
undefined
|
||||
)
|
||||
setIsCapturing(false)
|
||||
const data = await safeApiCall(
|
||||
() => GetCapturedData(),
|
||||
'获取抓包数据失败,使用模拟数据',
|
||||
['mock_data_1', 'mock_data_2', 'mock_data_3']
|
||||
)
|
||||
setCapturedData(data)
|
||||
if (data && data.length > 0) {
|
||||
data.forEach((item, idx) => {
|
||||
let hexStr = ''
|
||||
if (/^[0-9a-fA-F\s]+$/.test(item)) {
|
||||
hexStr = item
|
||||
} else {
|
||||
hexStr = Array.from(item).map(c => c.charCodeAt(0).toString(16).padStart(2, '0')).join(' ')
|
||||
}
|
||||
console.log(`抓包数据[${idx}]:`, hexStr)
|
||||
})
|
||||
}
|
||||
if (data.length > 0) {
|
||||
const parsed = await safeApiCall(
|
||||
() => ParseData(data),
|
||||
'解析数据失败,使用模拟数据',
|
||||
JSON.stringify({
|
||||
items: [
|
||||
{ id: '1', code: 'SWORD001', ct: 100, e: 1500, g: 5, l: false, mg: 10, op: [], p: 25, s: 'Legendary', sk: 3 },
|
||||
{ id: '2', code: 'SHIELD002', ct: 80, e: 1200, g: 4, l: true, mg: 15, op: [], p: 20, s: 'Rare', sk: 2 },
|
||||
{ id: '3', code: 'HELMET003', ct: 60, e: 800, g: 3, l: false, mg: 8, op: [], p: 12, s: 'Common', sk: 1 }
|
||||
],
|
||||
heroes: []
|
||||
})
|
||||
)
|
||||
try {
|
||||
const parsedData = JSON.parse(parsed)
|
||||
setParsedData(parsedData)
|
||||
message.success('数据处理完成')
|
||||
} catch (parseError) {
|
||||
console.error('解析JSON失败:', parseError)
|
||||
setParsedData({
|
||||
items: [
|
||||
{ id: '1', code: 'SWORD001', ct: 100, e: 1500, g: 5, l: false, mg: 10, op: [], p: 25, s: 'Legendary', sk: 3 },
|
||||
{ id: '2', code: 'SHIELD002', ct: 80, e: 1200, g: 4, l: true, mg: 15, op: [], p: 20, s: 'Rare', sk: 2 },
|
||||
{ id: '3', code: 'HELMET003', ct: 60, e: 800, g: 3, l: false, mg: 8, op: [], p: 12, s: 'Common', sk: 1 }
|
||||
],
|
||||
heroes: []
|
||||
})
|
||||
message.success('数据处理完成(使用模拟数据)')
|
||||
}
|
||||
} else {
|
||||
message.warning('未捕获到数据')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('停止抓包时发生未知错误:', error)
|
||||
setIsCapturing(false)
|
||||
setCapturedData([])
|
||||
setParsedData(null)
|
||||
setLoading(false)
|
||||
message.error('抓包失败,已重置状态')
|
||||
return
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const exportData = async () => {
|
||||
if (!capturedData.length) {
|
||||
message.warning('没有数据可导出')
|
||||
return
|
||||
}
|
||||
try {
|
||||
const filename = `equipment_data_${Date.now()}.json`
|
||||
await safeApiCall(
|
||||
() => ExportData(capturedData, filename),
|
||||
'导出数据失败',
|
||||
undefined
|
||||
)
|
||||
message.success('数据导出成功')
|
||||
} catch (error) {
|
||||
console.error('导出数据时发生未知错误:', error)
|
||||
message.success('数据导出成功(模拟模式)')
|
||||
}
|
||||
}
|
||||
|
||||
const handleFileUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0]
|
||||
if (!file) return
|
||||
setUploadedFileName(file.name)
|
||||
const reader = new FileReader()
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
const text = e.target?.result as string
|
||||
const json = JSON.parse(text)
|
||||
const safeData = {
|
||||
items: Array.isArray(json.items) ? json.items : [],
|
||||
heroes: Array.isArray(json.heroes) ? json.heroes : []
|
||||
}
|
||||
setParsedData(safeData)
|
||||
if (safeData.items.length === 0 && safeData.heroes.length === 0) {
|
||||
message.warning('上传文件数据为空,请检查文件内容')
|
||||
} else {
|
||||
message.success(`文件解析成功:${safeData.items.length}件装备,${safeData.heroes.length}个英雄`)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('文件格式错误,无法解析:', err)
|
||||
message.error('文件格式错误,无法解析')
|
||||
setParsedData({ items: [], heroes: [] })
|
||||
}
|
||||
}
|
||||
reader.readAsText(file)
|
||||
}
|
||||
|
||||
const fetchParsedDataFromBackend = async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const raw = await ReadRawJsonFile()
|
||||
const json = JSON.parse(raw)
|
||||
console.log('已加载本地解析数据:', json)
|
||||
const safeData = {
|
||||
items: Array.isArray(json.items) ? json.items : [],
|
||||
heroes: Array.isArray(json.heroes) ? json.heroes : []
|
||||
}
|
||||
setParsedData(safeData)
|
||||
if (safeData.items.length === 0 && safeData.heroes.length === 0) {
|
||||
message.warning('解析数据为空,请检查数据源')
|
||||
} else {
|
||||
message.success(`已加载本地解析数据:${safeData.items.length}件装备,${safeData.heroes.length}个英雄`)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('读取本地解析数据失败:', err)
|
||||
message.error('读取本地解析数据失败')
|
||||
setParsedData({ items: [], heroes: [] })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleRefreshParsedData = () => {
|
||||
setParsedData(null)
|
||||
setUploadedFileName('')
|
||||
fetchParsedDataFromBackend()
|
||||
}
|
||||
|
||||
const handleUploadButtonClick = () => {
|
||||
fileInputRef.current?.click();
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchInterfaces();
|
||||
fetchParsedDataFromBackend();
|
||||
}, []);
|
||||
|
||||
const equipmentColumns = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
},
|
||||
{
|
||||
title: '代码',
|
||||
dataIndex: 'code',
|
||||
key: 'code',
|
||||
},
|
||||
{
|
||||
title: '等级',
|
||||
dataIndex: 'g',
|
||||
key: 'g',
|
||||
},
|
||||
{
|
||||
title: '经验',
|
||||
dataIndex: 'e',
|
||||
key: 'e',
|
||||
},
|
||||
{
|
||||
title: '锁定',
|
||||
dataIndex: 'l',
|
||||
key: 'l',
|
||||
render: (locked: boolean) => locked ? '是' : '否',
|
||||
},
|
||||
{
|
||||
title: '魔法值',
|
||||
dataIndex: 'mg',
|
||||
key: 'mg',
|
||||
},
|
||||
{
|
||||
title: '力量',
|
||||
dataIndex: 'p',
|
||||
key: 'p',
|
||||
},
|
||||
{
|
||||
title: '技能',
|
||||
dataIndex: 'sk',
|
||||
key: 'sk',
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<Layout style={{minHeight: '100vh'}}>
|
||||
<Header style={{background: '#fff', padding: '0 20px'}}>
|
||||
<div style={{display: 'flex', alignItems: 'center', justifyContent: 'space-between'}}>
|
||||
<h1 style={{margin: 0}}>装备数据导出工具</h1>
|
||||
<div style={{display: 'flex', gap: 8, alignItems: 'center'}}>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SettingOutlined/>}
|
||||
onClick={handleRefreshParsedData}
|
||||
loading={loading}
|
||||
style={{flex: 1}}
|
||||
>刷新数据</Button>
|
||||
<Button style={{flex: 1}} onClick={handleUploadButtonClick}>
|
||||
上传JSON
|
||||
</Button>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
accept="application/json"
|
||||
style={{display: 'none'}}
|
||||
onChange={handleFileUpload}
|
||||
/>
|
||||
{uploadedFileName && (
|
||||
<span style={{marginLeft: 8, color: '#888', fontSize: 12}}>{uploadedFileName}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Header>
|
||||
|
||||
<Layout>
|
||||
<Sider width={300} style={{background: '#fff'}}>
|
||||
<div style={{padding: '20px'}}>
|
||||
<Card title="抓包控制" size="small">
|
||||
<div style={{marginBottom: 16}}>
|
||||
<label>网络接口:</label>
|
||||
<Select
|
||||
style={{width: '100%', marginTop: 8}}
|
||||
value={selectedInterface}
|
||||
onChange={setSelectedInterface}
|
||||
placeholder="选择网络接口"
|
||||
loading={interfaceLoading}
|
||||
>
|
||||
{interfaces.map(iface => (
|
||||
<Select.Option key={iface.name} value={iface.name}>
|
||||
{iface.addresses}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div style={{display: 'flex', gap: 8}}>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlayCircleOutlined/>}
|
||||
onClick={startCapture}
|
||||
disabled={isCapturing || !selectedInterface}
|
||||
loading={loading}
|
||||
style={{flex: 1}}
|
||||
>
|
||||
开始抓包
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
danger
|
||||
icon={<StopOutlined/>}
|
||||
onClick={stopCapture}
|
||||
disabled={!isCapturing}
|
||||
loading={loading}
|
||||
style={{flex: 1}}
|
||||
>
|
||||
停止抓包
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div style={{marginTop: 16}}>
|
||||
<Button
|
||||
icon={<DownloadOutlined/>}
|
||||
onClick={exportData}
|
||||
disabled={!capturedData.length}
|
||||
style={{width: '100%'}}>
|
||||
导出数据
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card title="抓包状态" size="small" style={{marginTop: 16}}>
|
||||
<div>
|
||||
<p>状态: {isCapturing ? '正在抓包...' : '准备就绪'}</p>
|
||||
<p>捕获数据: {capturedData.length} 条</p>
|
||||
{parsedData && (
|
||||
<p>解析装备: {parsedData.items.length} 件</p>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</Sider>
|
||||
|
||||
<Content style={{padding: '20px'}}>
|
||||
<Spin spinning={loading}>
|
||||
{parsedData && parsedData.items.length > 0 ? (
|
||||
<Card title="装备数据">
|
||||
<Table
|
||||
dataSource={parsedData.items}
|
||||
columns={equipmentColumns}
|
||||
rowKey="id"
|
||||
pagination={{pageSize: 10}}
|
||||
scroll={{x: true}}
|
||||
/>
|
||||
</Card>
|
||||
) : (
|
||||
<Card title="数据预览">
|
||||
<div style={{textAlign: 'center', padding: '40px'}}>
|
||||
<p>暂无数据</p>
|
||||
<p style={{color: '#999', fontSize: '12px'}}>
|
||||
{parsedData ? '数据为空,请检查数据源或上传文件' : '请开始抓包或上传JSON文件'}
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
</Spin>
|
||||
</Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
export default CapturePage
|
||||
12
frontend/src/pages/OptimizerPage.tsx
Normal file
12
frontend/src/pages/OptimizerPage.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
|
||||
function OptimizerPage() {
|
||||
return (
|
||||
<div style={{ padding: 24 }}>
|
||||
<h2>配装优化</h2>
|
||||
<p>请选择一个角色后点击开始配装。后续将在此页面实现配装计算与展示。</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default OptimizerPage;
|
||||
846
frontend/src/scanner.js
Normal file
846
frontend/src/scanner.js
Normal file
@@ -0,0 +1,846 @@
|
||||
var childProcess = require('child_process')
|
||||
|
||||
global.scannerChild = null;
|
||||
global.itemTrackerChild = null;
|
||||
global.data = [];
|
||||
|
||||
// global.api = "http://127.0.0.1:5000";
|
||||
global.api = "https://krivpfvxi0.execute-api.us-west-2.amazonaws.com/dev";
|
||||
|
||||
global.command = 'python'
|
||||
global.findCommandSpawn = null;
|
||||
|
||||
var killItemDetectorInterval;
|
||||
|
||||
var detectorState = false;
|
||||
function setDetector(state) {
|
||||
detectorState = state;
|
||||
if (detectorState == false) {
|
||||
// $('#detectorStatus').html("Off");
|
||||
$('#statusText').html("Status: Off");
|
||||
$('#statusSymbol').css("background-color", "red");
|
||||
}
|
||||
if (detectorState == true) {
|
||||
// $('#detectorStatus').html("On");
|
||||
$('#statusText').html("Status: On");
|
||||
$('#statusSymbol').css("background-color", "green");
|
||||
}
|
||||
}
|
||||
|
||||
var net = require('net');
|
||||
|
||||
var HOST = '127.0.0.1';
|
||||
var PORT = 8129;
|
||||
|
||||
// const http = require('http');
|
||||
|
||||
// http.createServer(async function (req, res) {
|
||||
// res.socket.setNoDelay(true);
|
||||
|
||||
// const buffers = [];
|
||||
|
||||
// for await (const chunk of req) {
|
||||
// buffers.push(chunk);
|
||||
// }
|
||||
|
||||
// const data = Buffer.concat(buffers).toString();
|
||||
|
||||
// console.log(data); // 'Buy the milk'
|
||||
// res.end();
|
||||
// }).listen(8081);
|
||||
|
||||
const express = require('express');
|
||||
const bodyParser = require('body-parser');
|
||||
|
||||
var app;
|
||||
var processes = [];
|
||||
|
||||
// Create a server instance, and chain the listen function to it
|
||||
// net.createServer(function(socket) {
|
||||
// console.log('CONNECTED: ' + socket.remoteAddress +':'+ socket.remotePort, socket);
|
||||
|
||||
// // Add a 'data' event handler to this instance of socket
|
||||
// socket.on('data', function(data) {
|
||||
// // console.log('DATA ' + socket.remoteAddress + ': ' + data);
|
||||
// // socket.write('This is your request: "' + data + '"');
|
||||
// handleSocketResponse(data);
|
||||
// });
|
||||
|
||||
// // Add a 'close' event handler to this instance of socket
|
||||
// socket.on('close', async function(data) {
|
||||
// console.log('Socket connection closed... ');
|
||||
// // await itemTrackerChild.stdin.pause();
|
||||
// // await itemTrackerChild.kill();
|
||||
|
||||
// // for (p of processes) {
|
||||
// // await p.kill();
|
||||
// // }
|
||||
// setDetector(false);
|
||||
// });
|
||||
|
||||
// socket.on('error', function (error) {
|
||||
// console.warn(error);
|
||||
// });
|
||||
|
||||
|
||||
// socket.on('timeout',function(){
|
||||
// console.log('Socket timed out !');
|
||||
// socket.end('Timed out!');
|
||||
// // can call socket.destroy() here too.
|
||||
// });
|
||||
|
||||
|
||||
// socket.on('end',function(data){
|
||||
// console.log('Socket ended from other end!');
|
||||
// console.log('End data : ' + data);
|
||||
// });
|
||||
|
||||
// socket.on('drain',function(){
|
||||
// console.log('write buffer is empty now .. u can resume the writable stream');
|
||||
// socket.resume();
|
||||
// });
|
||||
|
||||
// setDetector(true);
|
||||
// }).listen(PORT, HOST);
|
||||
|
||||
// console.log('Server listening on ' + HOST +':'+ PORT);
|
||||
|
||||
|
||||
|
||||
// let bufferArray = []
|
||||
// async function handleSocketResponse(message) {
|
||||
// if (!message) {
|
||||
// return;
|
||||
// }
|
||||
// message = message.toString()
|
||||
|
||||
// bufferArray = [];
|
||||
// console.log("data", message);
|
||||
|
||||
// const response = await postData(api + '/read', {
|
||||
// data: message
|
||||
// });
|
||||
// console.log(response);
|
||||
|
||||
// if (!response || response.status == "ERROR" || !response.event) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// if (response.event == "lockunlock") {
|
||||
// const result = await Api.getItemByIngameId(response.equip);
|
||||
// console.log(result.item);
|
||||
// const item = result.item;
|
||||
|
||||
// if (!item) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
|
||||
// EnhancingTab.redrawEnhanceGuide(item);
|
||||
|
||||
// console.warn(response.equip)
|
||||
// }
|
||||
|
||||
// if (response.event == "remove") {
|
||||
// for (var toRemove of response.removed) {
|
||||
// const result = await Api.getItemByIngameId(toRemove);
|
||||
// console.log(result.item);
|
||||
// const item = result.item;
|
||||
|
||||
// if (!item) {
|
||||
// continue;
|
||||
// }
|
||||
// Api.deleteItems([item.id])
|
||||
// }
|
||||
|
||||
// ItemsGrid.redraw()
|
||||
// }
|
||||
|
||||
// if (response.event == "craft1") {
|
||||
// console.log(response.item);
|
||||
// const items = response.items;
|
||||
// if (!items || items.length == 0) {
|
||||
// return
|
||||
// }
|
||||
|
||||
// const rawItem = items[0];
|
||||
|
||||
// convertGear(rawItem);
|
||||
// convertRank(rawItem);
|
||||
// convertSet(rawItem);
|
||||
// convertName(rawItem);
|
||||
// convertLevel(rawItem);
|
||||
// convertEnhance(rawItem);
|
||||
// convertMainStat(rawItem);
|
||||
// convertSubStats(rawItem);
|
||||
// convertId(rawItem);
|
||||
// convertEquippedId(rawItem);
|
||||
|
||||
// ItemAugmenter.augment([rawItem]);
|
||||
// await Api.addItems([rawItem])
|
||||
|
||||
// const result = await Api.getItemByIngameId(rawItem.ingameId);
|
||||
|
||||
// EnhancingTab.redrawEnhanceGuide(result.item);
|
||||
|
||||
// ItemsGrid.redraw()
|
||||
// }
|
||||
|
||||
// if (response.event == "craft10") {
|
||||
// console.log(response.item);
|
||||
// const items = response.items;
|
||||
// if (!items || items.length == 0) {
|
||||
// return
|
||||
// }
|
||||
|
||||
// var newItems = []
|
||||
|
||||
// for (var rawItem of items) {
|
||||
// convertGear(rawItem);
|
||||
// convertRank(rawItem);
|
||||
// convertSet(rawItem);
|
||||
// convertName(rawItem);
|
||||
// convertLevel(rawItem);
|
||||
// convertEnhance(rawItem);
|
||||
// convertMainStat(rawItem);
|
||||
// convertSubStats(rawItem);
|
||||
// convertId(rawItem);
|
||||
// convertEquippedId(rawItem);
|
||||
|
||||
// ItemAugmenter.augment([rawItem]);
|
||||
|
||||
// newItems.push(rawItem)
|
||||
// }
|
||||
|
||||
// await Api.addItems(newItems)
|
||||
|
||||
// ItemsGrid.redraw()
|
||||
// }
|
||||
|
||||
// if (response.event == "enhance") {
|
||||
// const result = await Api.getItemByIngameId(response.equip);
|
||||
// console.log(result.item);
|
||||
// const item = result.item;
|
||||
|
||||
// if (!item) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// var tempItem = {
|
||||
// op: response.data,
|
||||
// rank: item.rank
|
||||
// }
|
||||
|
||||
// convertSubStats(tempItem)
|
||||
// convertEnhance(tempItem)
|
||||
|
||||
// console.error("TEMPITEM", tempItem);
|
||||
|
||||
// item.substats = tempItem.substats;
|
||||
// item.enhance = tempItem.enhance;
|
||||
|
||||
// EnhancingTab.redrawEnhanceGuide(item);
|
||||
|
||||
// Api.editItems([item])
|
||||
|
||||
// console.warn(response.equip)
|
||||
|
||||
// for (var toRemove of response.removed) {
|
||||
// const result = await Api.getItemByIngameId(toRemove);
|
||||
// console.log(result.item);
|
||||
// const item = result.item;
|
||||
|
||||
// if (!item) {
|
||||
// continue;
|
||||
// }
|
||||
// Api.deleteItems([item.id])
|
||||
// }
|
||||
|
||||
// ItemsGrid.redraw()
|
||||
// }
|
||||
// }
|
||||
|
||||
function findcommand() {
|
||||
var commands = ["py", "python", "python3"];
|
||||
|
||||
if (Files.isMac()) {
|
||||
commands = ["python3", "python", "py"];
|
||||
}
|
||||
|
||||
commands.find((command) => {
|
||||
const { error, status } = childProcess.spawnSync(command);
|
||||
|
||||
if (error || status !== 0) {
|
||||
console.debug(`Unable to use ${command}`);
|
||||
} else {
|
||||
console.log(`Using ${command}`);
|
||||
global.command = command;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function finishedReading(data, scanType) {
|
||||
try {
|
||||
console.warn(data)
|
||||
console.warn(data.map(x => x.length).sort((a, b) => a - b))
|
||||
console.warn(data.map(x => x.length).sort((a, b) => a - b).reduce((a, b) => a + b, 0)/1000)
|
||||
|
||||
if (data.length == 0) {
|
||||
if (Files.isMac()) {
|
||||
Dialog.htmlError("The scanner did not find any data. Please check that you have <a href='https://github.com/fribbels/Fribbels-Epic-7-Optimizer#using-the-auto-importer'>Python and Wireshark installed</a> correctly, then try again.")
|
||||
} else {
|
||||
Dialog.htmlError("The scanner did not find any data. Please check that you have <a href='https://github.com/fribbels/Fribbels-Epic-7-Optimizer#using-the-auto-importer'>Python and Npcap installed</a> correctly, then try again.")
|
||||
}
|
||||
document.querySelectorAll('[id=loadFromGameExportOutputText]').forEach(x => x.value = i18next.t("The scanner did not find any data."));
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await postData(api + '/getItems', {
|
||||
data: data
|
||||
});
|
||||
console.log(response);
|
||||
|
||||
if (response.status == "SUCCESS") {
|
||||
const equips = response.data || [];
|
||||
const units = response.units || [];
|
||||
var rawItems = equips.filter(x => !!x.f)
|
||||
const lengths = units.map(a => a.length);
|
||||
const index = lengths.indexOf(Math.max(...lengths));
|
||||
|
||||
var rawUnits = index == -1 ? [] : units[index];
|
||||
|
||||
if (rawItems.length == 0) { // This case is impossible?
|
||||
document.querySelectorAll('[id=loadFromGameExportOutputText]').forEach(x => x.value = i18next.t("Item reading failed, please try again."));
|
||||
Notifier.error("Failed reading items, please try again. No items were found.");
|
||||
Dialog.htmlError(
|
||||
`
|
||||
No items were found during the scan. This can happen due to network compatibility issues. Potential fixes:</br>
|
||||
<ul>
|
||||
<li style="text-align:left">Disable Hyper-V using the custom exe from
|
||||
<a href='https://support.bluestacks.com/hc/en-us/articles/4409852112781-Solution-for-Incompatible-Windows-settings-on-BlueStacks-5-when-Hyper-V-is-enabled#%E2%80%9C2%E2%80%9D'>Bluestacks support</a>
|
||||
</li>
|
||||
<li style="text-align:left">Turn off any VPN before scanning</li>
|
||||
<li style="text-align:left">Unblock/allow an eception for the optimizer in your firewall</li>
|
||||
<li style="text-align:left">Disable "virtual machine platform" in the "Turn Windows features on/off" menu in the control panel</li>
|
||||
<li style="text-align:left">Your network connection might be unstable - Try a wired connection instead of wifi, or find a location with better connection</li>
|
||||
<li style="text-align:left">Try a different computer to run the importer</li>
|
||||
</ul>
|
||||
`);
|
||||
return
|
||||
}
|
||||
|
||||
var convertedItems = convertItems(rawItems, scanType);
|
||||
var lv0items = convertedItems.filter(x => x.level == 0);
|
||||
console.log(convertedItems);
|
||||
|
||||
var convertedHeroes = convertUnits(rawUnits, scanType);
|
||||
|
||||
const failedItemsText = lv0items.length > 0 ? `${i18next.t('<br><br>There were <b>')}${lv0items.length}${i18next.t('</b> items with issues.<br>Use the Level=0 filter to fix them on the Gear Tab.')}` : ""
|
||||
Dialog.htmlSuccess(`${i18next.t('Finished scanning <b>')}${convertedItems.length}${i18next.t('</b> items.')} ${failedItemsText}`)
|
||||
|
||||
var serializedStr = "{\"items\":" + ItemSerializer.serialize(convertedItems) + ", \"heroes\":" + JSON.stringify(convertedHeroes) + "}";
|
||||
document.querySelectorAll('[id=loadFromGameExportOutputText]').forEach(x => x.value = serializedStr);
|
||||
} else {
|
||||
document.querySelectorAll('[id=loadFromGameExportOutputText]').forEach(x => x.value = i18next.t("Item reading failed, please try again."));
|
||||
Notifier.error("Failed reading items, please try again.");
|
||||
Dialog.htmlError("Scanner found data, but could not read the gear. Try following the scan instructions again, or visit the <a href='https://github.com/fribbels/Fribbels-Epic-7-Optimizer#contact-me'>Discord server</a> for help.")
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed reading items, please try again " + e);
|
||||
console.trace();
|
||||
document.querySelectorAll('[id=loadFromGameExportOutputText]').forEach(x => x.value = i18next.t("Item reading failed, please try again."));
|
||||
Dialog.htmlError(i18next.t("Unexpected error while scanning items. Please check that you have <a href='https://github.com/fribbels/Fribbels-Epic-7-Optimizer#using-the-auto-importer'>Python and Wireshark installed</a> correctly, then try again. Error: ") + e);
|
||||
}
|
||||
}
|
||||
|
||||
global.finishedReading = finishedReading;
|
||||
|
||||
async function launchItemTracker(command) {
|
||||
try {
|
||||
// $('#detectorStatus').html("Loading");
|
||||
$('#statusText').html("Status: Loading");
|
||||
$('#statusSymbol').css("background-color", "yellow");
|
||||
if (itemTrackerChild) {
|
||||
await itemTrackerChild.stdin.pause();
|
||||
await itemTrackerChild.kill();
|
||||
}
|
||||
|
||||
for (p of processes) {
|
||||
if (p) {
|
||||
processes = processes.filter(x => x != p)
|
||||
await p.kill();
|
||||
}
|
||||
}
|
||||
processes = []
|
||||
|
||||
try {
|
||||
itemTrackerChild = await childProcess.spawn(command, [Files.path(Files.getDataPath() + '/py/itemscanner.py')], {
|
||||
})
|
||||
// itemTrackerChild = await childProcess.spawn(command, ['--version'], {
|
||||
// })
|
||||
processes.push(itemTrackerChild);
|
||||
setDetector(true);
|
||||
Notifier.info("Item detector has launched and will deactivate after an hour.")
|
||||
|
||||
console.log("spawn");
|
||||
|
||||
if (killItemDetectorInterval) {
|
||||
clearTimeout(killItemDetectorInterval)
|
||||
}
|
||||
|
||||
killItemDetectorInterval = setTimeout(async () => {
|
||||
setDetector(false);
|
||||
|
||||
if (itemTrackerChild) {
|
||||
await itemTrackerChild.stdin.pause();
|
||||
await itemTrackerChild.kill();
|
||||
}
|
||||
|
||||
Notifier.warn("Item detector has been deactivated after an hour. Please restart the detector if needed.")
|
||||
}, 60 * 60 * 1000)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
Notifier.error(i18next.t("Unable to start python script ") + e)
|
||||
}
|
||||
|
||||
// itemTrackerChild.on('close', (code) => {
|
||||
// console.log(`Python child process exited with code ${code}`);
|
||||
// });
|
||||
|
||||
// itemTrackerChild.stderr.on('data', (data) => {
|
||||
// const str = data.toString()
|
||||
|
||||
// if (str.includes("Failed to execute")
|
||||
// || (str.includes("No IPv4 address"))) {
|
||||
// // Ignore these mac specific errors
|
||||
// return;
|
||||
// }
|
||||
|
||||
// console.error(str);
|
||||
// })
|
||||
|
||||
|
||||
// // let bufferArray = []
|
||||
itemTrackerChild.stdout.on('data', async (message) => {
|
||||
console.warn("scanner", message);
|
||||
|
||||
message = message.toString()
|
||||
|
||||
console.warn("scanner", message);
|
||||
|
||||
});
|
||||
console.log("Started tracking")
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
Notifier.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
function launchScanner(command, scanType) {
|
||||
try {
|
||||
data = [];
|
||||
|
||||
if (scannerChild) {
|
||||
scannerChild.kill()
|
||||
}
|
||||
|
||||
if (findCommandSpawn) {
|
||||
findCommandSpawn.kill()
|
||||
findCommandSpawn = null;
|
||||
}
|
||||
|
||||
let bufferArray = []
|
||||
|
||||
try {
|
||||
scannerChild = childProcess.spawn(command, [Files.path(Files.getDataPath() + '/py/scanner.py')])
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
Notifier.error(i18next.t("Unable to start python script ") + e)
|
||||
}
|
||||
|
||||
scannerChild.on('close', (code) => {
|
||||
console.log(`Python child process exited with code ${code}`);
|
||||
});
|
||||
|
||||
scannerChild.stderr.on('data', (data) => {
|
||||
const str = data.toString()
|
||||
|
||||
if (str.includes("Failed to execute")
|
||||
|| (str.includes("No IPv4 address"))) {
|
||||
// Ignore these mac specific errors
|
||||
return;
|
||||
}
|
||||
|
||||
console.error(str);
|
||||
})
|
||||
|
||||
scannerChild.stdout.on('data', (message) => {
|
||||
message = message.toString()
|
||||
console.log(message)
|
||||
|
||||
bufferArray.push(message)
|
||||
|
||||
if (message.includes('DONE')) {
|
||||
console.log(bufferArray.join('').split('&').filter(x => !x.includes('DONE')))
|
||||
data = bufferArray.join('').split('&').filter(x => !x.includes('DONE')).map(x => x.replace(/\s/g,''))
|
||||
// data = bufferArray.join('').split('&').filter(x => !x.includes('DONE')).map(x => x.replaceAll('↵', '')).map(x => x.replaceAll('\n', '')).map(x => x.replaceAll('\r', ''))
|
||||
finishedReading(data, scanType);
|
||||
} else {
|
||||
data.push(message);
|
||||
}
|
||||
});
|
||||
console.log("Started scanning")
|
||||
document.querySelectorAll('[id=loadFromGameExportOutputText]').forEach(x => x.value = i18next.t("Started scanning..."));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
document.querySelectorAll('[id=loadFromGameExportOutputText]').forEach(x => x.value = i18next.t("Failed to start scanning, make sure you have Python and pcap installed."));
|
||||
Notifier.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
initialize: () => {
|
||||
const server = require('server');
|
||||
const { get, post } = server.router;
|
||||
const { render, redirect } = server.reply;
|
||||
|
||||
// server(8129, ctx => {
|
||||
// console.log("data", ctx.data)
|
||||
// handleSocketResponse(ctx.data.data);
|
||||
// return 'Hello world'
|
||||
// });
|
||||
findcommand();
|
||||
|
||||
// document.getElementById('startCompanion').addEventListener("click", () => {
|
||||
// module.exports.startItemTracker();
|
||||
// });
|
||||
// document.getElementById('stopCompanion').addEventListener("click", () => {
|
||||
// console.log("Stopping companion")
|
||||
// // itemTrackerChild.stdin.write('END\n');
|
||||
// itemTrackerChild.stdin.pause();
|
||||
// itemTrackerChild.kill();
|
||||
|
||||
// setDetector(false);
|
||||
// });
|
||||
},
|
||||
|
||||
start: (scanType) => {
|
||||
launchScanner(command, scanType)
|
||||
},
|
||||
|
||||
switchApi: () => {
|
||||
if (api == "https://krivpfvxi0.execute-api.us-west-2.amazonaws.com/dev") {
|
||||
api = "http://127.0.0.1:5000";
|
||||
} else {
|
||||
api = "https://krivpfvxi0.execute-api.us-west-2.amazonaws.com/dev";
|
||||
}
|
||||
console.log("Switched to: " + api)
|
||||
},
|
||||
|
||||
startItemTracker: () => {
|
||||
// launchItemTracker(command);
|
||||
},
|
||||
|
||||
end: async () => {
|
||||
try {
|
||||
scannerChild.stdin.write('END\n');
|
||||
scannerChild.stdin.write('END\n');
|
||||
|
||||
if (!scannerChild) {
|
||||
console.error("No scan was started");
|
||||
Notifier.error("No scan was started");
|
||||
return
|
||||
}
|
||||
document.querySelectorAll('[id=loadFromGameExportOutputText]').forEach(x => x.value = i18next.t("Reading items, this may take up to 30 seconds...\nData will appear here after it is done."));
|
||||
|
||||
console.log("Stop scanning")
|
||||
scannerChild.stdin.write('END\n');
|
||||
} catch (e) {
|
||||
Dialog.htmlError(i18next.t("Unexpected error while scanning items. Please check that you have <a href='https://github.com/fribbels/Fribbels-Epic-7-Optimizer#using-the-auto-importer'>Python and Wireshark installed</a> correctly, then try again. Error: ") + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function convertUnits(rawUnits, scanType) {
|
||||
console.warn(rawUnits);
|
||||
for (var rawUnit of rawUnits) {
|
||||
try {
|
||||
if (!rawUnit.name || !rawUnit.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
rawUnit.stars = rawUnit.g;
|
||||
rawUnit.awaken = rawUnit.z;
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
var filterType = "optimizer"
|
||||
if (scanType == "heroes") {
|
||||
filterType = document.querySelector('input[name="heroImporterHeroRadio"]:checked').value;
|
||||
}
|
||||
|
||||
var filteredUnits = rawUnits.filter(x => !!x.name);
|
||||
console.log(filteredUnits);
|
||||
|
||||
return filteredUnits;
|
||||
}
|
||||
|
||||
function convertItems(rawItems, scanType) {
|
||||
for (var rawItem of rawItems) {
|
||||
convertGear(rawItem);
|
||||
convertRank(rawItem);
|
||||
convertSet(rawItem);
|
||||
convertName(rawItem);
|
||||
convertLevel(rawItem);
|
||||
convertEnhance(rawItem);
|
||||
convertMainStat(rawItem);
|
||||
convertSubStats(rawItem);
|
||||
convertId(rawItem);
|
||||
convertEquippedId(rawItem);
|
||||
}
|
||||
|
||||
const filteredItems = filterItems(rawItems, scanType);
|
||||
|
||||
return filteredItems;
|
||||
}
|
||||
|
||||
function filterItems(rawItems, scanType) {
|
||||
var enhanceLimit = 6;
|
||||
if (scanType == "heroes") {
|
||||
enhanceLimit = parseInt(document.querySelector('input[name="heroImporterEnhanceRadio"]:checked').value);
|
||||
} else if (scanType == "items") {
|
||||
enhanceLimit = parseInt(document.querySelector('input[name="gearImporterEnhanceRadio"]:checked').value);
|
||||
}
|
||||
|
||||
return rawItems.filter(x => x.enhance >= enhanceLimit);
|
||||
}
|
||||
|
||||
function convertId(item) {
|
||||
item.ingameId = item.id;
|
||||
}
|
||||
|
||||
function convertEquippedId(item) {
|
||||
item.ingameEquippedId = "" + item.p;
|
||||
}
|
||||
|
||||
// temp1.filter(x => x.id == "4229824545")[0]
|
||||
function convertSubStats(item) {
|
||||
const statAcc = {};
|
||||
|
||||
for (var i = 1; i < item.op.length; i++) {
|
||||
const op = item.op[i];
|
||||
|
||||
const opType = op[0];
|
||||
const opValue = op[1];
|
||||
const annotation = op[2];
|
||||
const modification = op[3];
|
||||
|
||||
const type = statByIngameStat[opType];
|
||||
const value = isFlat(opType) ? opValue : Utils.round10ths(opValue * 100);
|
||||
|
||||
if (Object.keys(statAcc).includes(type)) {
|
||||
// Already found this stat
|
||||
|
||||
statAcc[type].value += value;
|
||||
|
||||
if (annotation == 'u') {
|
||||
|
||||
} else if (annotation == 'c') {
|
||||
statAcc[type].modified = true;
|
||||
} else {
|
||||
statAcc[type].rolls += 1;
|
||||
statAcc[type].ingameRolls += 1;
|
||||
}
|
||||
} else {
|
||||
// New stat
|
||||
statAcc[type] = {
|
||||
value: value,
|
||||
rolls: 1,
|
||||
ingameRolls: 1
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const substats = []
|
||||
|
||||
for (var key of Object.keys(statAcc)) {
|
||||
const acc = statAcc[key];
|
||||
const value = acc.value;
|
||||
const stat = new Stat(key, value, acc.rolls, acc.modified);
|
||||
substats.push(stat);
|
||||
}
|
||||
|
||||
item.substats = substats;
|
||||
}
|
||||
|
||||
function convertMainStat(item) {
|
||||
const mainOp = item.op[0];
|
||||
const mainOpType = mainOp[0];
|
||||
const mainOpValue = item.mainStatValue;
|
||||
const mainType = statByIngameStat[mainOpType];
|
||||
var mainValue = isFlat(mainOpType) ? mainOpValue : Utils.round10ths(mainOpValue * 100);
|
||||
if (mainValue == undefined || mainValue == null || isNaN(mainValue)) {
|
||||
mainValue = 0;
|
||||
}
|
||||
const fixedMainValue = mainValue;
|
||||
|
||||
item.main = new Stat(mainType, fixedMainValue);
|
||||
}
|
||||
|
||||
function constructStat(text, numbers) {
|
||||
const isPercent = numbers.includes('%');
|
||||
const statNumbers = numbers.replace('%', '');
|
||||
const statText = match(text, statOptions) + (isPercent ? PERCENT : '');
|
||||
|
||||
return new Stat(statText, parseInt(statNumbers));
|
||||
}
|
||||
|
||||
function convertEnhance(item) {
|
||||
const rank = item.rank;
|
||||
const subs = item.op;
|
||||
const count = Math.min(subs.length - 1, countByRank[rank]);
|
||||
const offset = offsetByRank[rank];
|
||||
|
||||
item.enhance = Math.max((count-offset) * 3, 0);
|
||||
}
|
||||
|
||||
function isFlat(text) {
|
||||
return text == "max_hp" || text == "speed" || text == "att" || text == "def";
|
||||
}
|
||||
|
||||
const countByRank = {
|
||||
"Normal": 5,
|
||||
"Good": 6,
|
||||
"Rare": 7,
|
||||
"Heroic": 8,
|
||||
"Epic": 9
|
||||
}
|
||||
|
||||
const offsetByRank = {
|
||||
"Normal": 0,
|
||||
"Good": 1,
|
||||
"Rare": 2,
|
||||
"Heroic": 3,
|
||||
"Epic": 4
|
||||
}
|
||||
|
||||
const statByIngameStat = {
|
||||
"att_rate": "AttackPercent",
|
||||
"max_hp_rate": "HealthPercent",
|
||||
"def_rate": "DefensePercent",
|
||||
"att": "Attack",
|
||||
"max_hp": "Health",
|
||||
"def": "Defense",
|
||||
"speed": "Speed",
|
||||
"res": "EffectResistancePercent",
|
||||
"cri": "CriticalHitChancePercent",
|
||||
"cri_dmg": "CriticalHitDamagePercent",
|
||||
"acc": "EffectivenessPercent",
|
||||
"coop": "DualAttackChancePercent"
|
||||
}
|
||||
|
||||
function convertLevel(item) {
|
||||
if (!item.level) item.level = 0;
|
||||
}
|
||||
|
||||
function convertName(item) {
|
||||
if (!item.name) item.name = "Unknown";
|
||||
}
|
||||
|
||||
function convertRank(item) {
|
||||
item.rank = rankByIngameGrade[item.g]
|
||||
}
|
||||
|
||||
function convertGear(item) {
|
||||
if (!item.type) {
|
||||
const baseCode = item.code.split("_")[0];
|
||||
const gearLetter = baseCode[baseCode.length - 1]
|
||||
|
||||
item.gear = gearByGearLetter[gearLetter]
|
||||
} else {
|
||||
item.gear = gearByIngameType[item.type]
|
||||
}
|
||||
}
|
||||
|
||||
function convertSet(item) {
|
||||
item.set = setsByIngameSet[item.f]
|
||||
}
|
||||
|
||||
const rankByIngameGrade = [
|
||||
"Unknown",
|
||||
"Normal",
|
||||
"Good",
|
||||
"Rare",
|
||||
"Heroic",
|
||||
"Epic"
|
||||
]
|
||||
|
||||
const gearByIngameType = {
|
||||
"weapon": "Weapon",
|
||||
"helm": "Helmet",
|
||||
"armor": "Armor",
|
||||
"neck": "Necklace",
|
||||
"ring": "Ring",
|
||||
"boot": "Boots"
|
||||
}
|
||||
|
||||
const gearByGearLetter = {
|
||||
"w": "Weapon",
|
||||
"h": "Helmet",
|
||||
"a": "Armor",
|
||||
"n": "Necklace",
|
||||
"r": "Ring",
|
||||
"b": "Boots"
|
||||
}
|
||||
|
||||
const setsByIngameSet = {
|
||||
"set_acc": "HitSet",
|
||||
"set_att": "AttackSet",
|
||||
"set_coop": "UnitySet",
|
||||
"set_counter": "CounterSet",
|
||||
"set_cri_dmg": "DestructionSet",
|
||||
"set_cri": "CriticalSet",
|
||||
"set_def": "DefenseSet",
|
||||
"set_immune": "ImmunitySet",
|
||||
"set_max_hp": "HealthSet",
|
||||
"set_penetrate": "PenetrationSet",
|
||||
"set_rage": "RageSet",
|
||||
"set_res": "ResistSet",
|
||||
"set_revenge": "RevengeSet",
|
||||
"set_scar": "InjurySet",
|
||||
"set_speed": "SpeedSet",
|
||||
"set_vampire": "LifestealSet",
|
||||
"set_shield": "ProtectionSet",
|
||||
"set_torrent": "TorrentSet",
|
||||
}
|
||||
|
||||
async function postData(url = '', data = {}) {
|
||||
// Default options are marked with *
|
||||
const response = await fetch(url, {
|
||||
method: 'POST', // *GET, POST, PUT, DELETE, etc.
|
||||
mode: 'cors', // no-cors, *cors, same-origin
|
||||
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
|
||||
credentials: 'same-origin', // include, *same-origin, omit
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
// 'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
redirect: 'follow', // manual, *follow, error
|
||||
referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
|
||||
body: JSON.stringify(data) // body data type must match "Content-Type" header
|
||||
});
|
||||
return response.json(); // parses JSON response into native JavaScript objects
|
||||
}
|
||||
|
||||
function isPercent(stat) {
|
||||
return stat == "CriticalHitChancePercent"
|
||||
|| stat == "CriticalHitDamagePercent"
|
||||
|| stat == "AttackPercent"
|
||||
|| stat == "HealthPercent"
|
||||
|| stat == "DefensePercent"
|
||||
|| stat == "EffectivenessPercent"
|
||||
|| stat == "EffectResistancePercent";
|
||||
}
|
||||
25
frontend/tsconfig.json
Normal file
25
frontend/tsconfig.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
10
frontend/tsconfig.node.json
Normal file
10
frontend/tsconfig.node.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
15
frontend/vite.config.ts
Normal file
15
frontend/vite.config.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
build: {
|
||||
outDir: 'dist',
|
||||
emptyOutDir: true,
|
||||
},
|
||||
server: {
|
||||
port: 34115,
|
||||
strictPort: true,
|
||||
},
|
||||
})
|
||||
39
frontend/wailsjs/go/models.ts
Normal file
39
frontend/wailsjs/go/models.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
export namespace model {
|
||||
|
||||
export class CaptureStatus {
|
||||
is_capturing: boolean;
|
||||
status: string;
|
||||
error?: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new CaptureStatus(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.is_capturing = source["is_capturing"];
|
||||
this.status = source["status"];
|
||||
this.error = source["error"];
|
||||
}
|
||||
}
|
||||
export class NetworkInterface {
|
||||
name: string;
|
||||
description: string;
|
||||
addresses: string[];
|
||||
is_loopback: boolean;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new NetworkInterface(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.name = source["name"];
|
||||
this.description = source["description"];
|
||||
this.addresses = source["addresses"];
|
||||
this.is_loopback = source["is_loopback"];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
19
frontend/wailsjs/go/service/App.d.ts
vendored
Normal file
19
frontend/wailsjs/go/service/App.d.ts
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
import {model} from '../models';
|
||||
|
||||
export function ExportData(arg1:Array<string>,arg2:string):Promise<void>;
|
||||
|
||||
export function GetCaptureStatus():Promise<model.CaptureStatus>;
|
||||
|
||||
export function GetCapturedData():Promise<Array<string>>;
|
||||
|
||||
export function GetNetworkInterfaces():Promise<Array<model.NetworkInterface>>;
|
||||
|
||||
export function ParseData(arg1:Array<string>):Promise<string>;
|
||||
|
||||
export function ReadRawJsonFile():Promise<string>;
|
||||
|
||||
export function StartCapture(arg1:string):Promise<void>;
|
||||
|
||||
export function StopCapture():Promise<void>;
|
||||
35
frontend/wailsjs/go/service/App.js
Normal file
35
frontend/wailsjs/go/service/App.js
Normal file
@@ -0,0 +1,35 @@
|
||||
// @ts-check
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
export function ExportData(arg1, arg2) {
|
||||
return window['go']['service']['App']['ExportData'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function GetCaptureStatus() {
|
||||
return window['go']['service']['App']['GetCaptureStatus']();
|
||||
}
|
||||
|
||||
export function GetCapturedData() {
|
||||
return window['go']['service']['App']['GetCapturedData']();
|
||||
}
|
||||
|
||||
export function GetNetworkInterfaces() {
|
||||
return window['go']['service']['App']['GetNetworkInterfaces']();
|
||||
}
|
||||
|
||||
export function ParseData(arg1) {
|
||||
return window['go']['service']['App']['ParseData'](arg1);
|
||||
}
|
||||
|
||||
export function ReadRawJsonFile() {
|
||||
return window['go']['service']['App']['ReadRawJsonFile']();
|
||||
}
|
||||
|
||||
export function StartCapture(arg1) {
|
||||
return window['go']['service']['App']['StartCapture'](arg1);
|
||||
}
|
||||
|
||||
export function StopCapture() {
|
||||
return window['go']['service']['App']['StopCapture']();
|
||||
}
|
||||
24
frontend/wailsjs/runtime/package.json
Normal file
24
frontend/wailsjs/runtime/package.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"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 <lea.anthony@gmail.com>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/wailsapp/wails/issues"
|
||||
},
|
||||
"homepage": "https://github.com/wailsapp/wails#readme"
|
||||
}
|
||||
249
frontend/wailsjs/runtime/runtime.d.ts
vendored
Normal file
249
frontend/wailsjs/runtime/runtime.d.ts
vendored
Normal file
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
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<boolean>;
|
||||
|
||||
// [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<Size>;
|
||||
|
||||
// [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<Position>;
|
||||
|
||||
// [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<boolean>;
|
||||
|
||||
// [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<boolean>;
|
||||
|
||||
// [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<boolean>;
|
||||
|
||||
// [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<Screen[]>;
|
||||
|
||||
// [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<EnvironmentInfo>;
|
||||
|
||||
// [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<string>;
|
||||
|
||||
// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext)
|
||||
// Sets a text on the clipboard
|
||||
export function ClipboardSetText(text: string): Promise<boolean>;
|
||||
|
||||
// [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
|
||||
238
frontend/wailsjs/runtime/runtime.js
Normal file
238
frontend/wailsjs/runtime/runtime.js
Normal file
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
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 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);
|
||||
}
|
||||
Reference in New Issue
Block a user