diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..5e31fff --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,31 @@ +name: Build/release + +on: push + +jobs: + release: + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [macos-latest, ubuntu-latest, windows-latest] + + steps: + - name: Check out Git repository + uses: actions/checkout@v1 + + - name: Install Node.js, NPM and Yarn + uses: actions/setup-node@v1 + with: + node-version: 10 + + - name: Build/release Electron app + uses: samuelmeuli/action-electron-builder@v1 + with: + # GitHub token, automatically provided to the action + # (No need to define this secret in the repo settings) + github_token: ${{ secrets.github_token }} + + # If the commit is tagged with a version (e.g. "v1.0.0"), + # release the app after building + release: ${{ startsWith(github.ref, 'refs/tags/v') }} diff --git a/forge.config.ts b/forge.config.ts index 1dcc889..1ec790d 100644 --- a/forge.config.ts +++ b/forge.config.ts @@ -14,6 +14,7 @@ import { rendererConfig } from './webpack.renderer.config'; const config: ForgeConfig = { packagerConfig: { asar: true, + icon: './images/icon', }, rebuildConfig: {}, makers: [new MakerSquirrel({}), new MakerZIP({}, ['darwin']), new MakerRpm({}), new MakerDeb({})], diff --git a/images/1024.png b/images/1024.png new file mode 100644 index 0000000..7a8034a Binary files /dev/null and b/images/1024.png differ diff --git a/images/icon.icns b/images/icon.icns new file mode 100644 index 0000000..7d17782 Binary files /dev/null and b/images/icon.icns differ diff --git a/images/icon.ico b/images/icon.ico new file mode 100644 index 0000000..ad71f6d Binary files /dev/null and b/images/icon.ico differ diff --git a/images/icon.png b/images/icon.png new file mode 100644 index 0000000..bcfa00a Binary files /dev/null and b/images/icon.png differ diff --git a/package.json b/package.json index b52f690..119957d 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", "electron-is-dev": "^3.0.1", + "electron-log": "^5.2.0", "electron-squirrel-startup": "^1.0.1", "framer-motion": "^11.5.6", "react": "^18.3.1", diff --git a/src/components/App.tsx b/src/components/App.tsx index f396f13..deae8c5 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -1,32 +1,66 @@ import { + Box, + Container, + Flex, Heading, Icon, IconButton, + List, + ListIcon, + ListItem, + Link, + Spacer, Table, TableContainer, Tbody, Td, Th, Thead, + Tooltip, Tr, + useToken, } from '@chakra-ui/react' -import { CheckIcon, DeleteIcon, StarIcon } from '@chakra-ui/icons' +import { + CheckIcon, + CopyIcon, + DeleteIcon, + StarIcon, + QuestionIcon, + RepeatIcon, + SettingsIcon, +} from '@chakra-ui/icons' +import log from 'electron-log/renderer' import React, { useEffect, useState } from 'react' import { ScatterChart, CartesianGrid, Legend, Scatter, - Tooltip, + Tooltip as RechartsTooltip, XAxis, YAxis, } from 'recharts' -import type { Corner } from '../types' +import type { Corner, Solution } from '../types' const lerp = (a: number, b: number, t: number) => a + (b - a) * t function App() { + const [logfilePath, setLogfilePath] = useState('') const [corners, setCorners] = useState([] as Corner[]) + const [solution, setSolution] = useState({} as Solution) + const [showHelp, setShowHelp] = useState(false) + + const [green500, yellow300] = useToken( + // the key within the theme, in this case `theme.colors` + 'colors', + // the subkey(s), resolving to `theme.colors.red.100` + ['green.500', 'yellow.300'] + // a single fallback or fallback array matching the length of the previous arg + ) + + useEffect(() => { + window.api.getLogfilePath().then(setLogfilePath) + }, [setLogfilePath]) useEffect(() => { window.api.onClipboardTextUpdated((text: string) => { @@ -42,47 +76,136 @@ function App() { position: { x, y, z }, } + log.info(`Added corner: ${x}, ${y}, ${z}`) setCorners((corners) => [...corners, corner]) }) return () => window.api.removeClipboardTextUpdatedListener() - }, []) + }, [setCorners]) - let minX, maxX, minZ, maxZ - for (const corner of corners) { - if (minX === undefined || corner.chunk.x < minX) minX = corner.chunk.x - if (maxX === undefined || corner.chunk.x > maxX) maxX = corner.chunk.x - if (minZ === undefined || corner.chunk.z < minZ) minZ = corner.chunk.z - if (maxZ === undefined || corner.chunk.z > maxZ) maxZ = corner.chunk.z - } + useEffect(() => { + let minX, maxX, minZ, maxZ + for (const corner of corners) { + if (minX === undefined || corner.chunk.x < minX) minX = corner.chunk.x + if (maxX === undefined || corner.chunk.x > maxX) maxX = corner.chunk.x + if (minZ === undefined || corner.chunk.z < minZ) minZ = corner.chunk.z + if (maxZ === undefined || corner.chunk.z > maxZ) maxZ = corner.chunk.z + } - const xSort = corners.sort((a, b) => a.chunk.x - b.chunk.x) - const zSort = corners.sort((a, b) => a.chunk.z - b.chunk.z) + const xSort = [...corners].sort((a, b) => a.chunk.x - b.chunk.x) + const zSort = [...corners].sort((a, b) => a.chunk.z - b.chunk.z) - let foundX, foundZ - for (let i = 0; i < xSort.length - 1; i++) { - if (xSort[i].chunk.x === xSort[i + 1].chunk.x) { - foundZ = lerp(xSort[i].chunk.z, xSort[i + 1].chunk.z, 0.5) - break + let foundX, foundZ + for (let i = 0; i < xSort.length - 1; i++) { + if (xSort[i].chunk.x === xSort[i + 1].chunk.x) { + foundZ = lerp(xSort[i].chunk.z, xSort[i + 1].chunk.z, 0.5) + break + } } - } - for (let i = 0; i < zSort.length - 1; i++) { - if (zSort[i].chunk.z === zSort[i + 1].chunk.z) { - foundX = lerp(zSort[i].chunk.x, zSort[i + 1].chunk.x, 0.5) - break + for (let i = 0; i < zSort.length - 1; i++) { + if (zSort[i].chunk.z === zSort[i + 1].chunk.z) { + foundX = lerp(zSort[i].chunk.x, zSort[i + 1].chunk.x, 0.5) + break + } } - } + + if (foundX && foundZ) log.info(`Found solution: ${foundX}, ${foundZ}`) + setSolution({ minX, maxX, minZ, maxZ, foundX, foundZ }) + }, [corners, setSolution]) + + const { minX, maxX, minZ, maxZ, foundX, foundZ } = solution return ( - <> - 🥧📡 Anti/PieRay Helper -

- Setup bind for Lunar Client{' '} - mod{' '} - - Coordinates - {' '} - to "Copy Coords to Clipboard". -

+ + + 🥧📡 Anti/PieRay Helper + + + + } + onClick={() => setShowHelp(!showHelp)} + /> + + + } + onClick={() => + window.api.openBrowserWindow( + `file://${logfilePath.replace(/(\s+)/g, '\\$1')}` + ) + } + /> + + + } + onClick={() => { + log.info(`Cleared`) + setCorners([]) + setSolution({}) + }} + /> + + + + {showHelp && ( + + + + + + Setup bind for{' '} + + window.api.openBrowserWindow('https://www.lunarclient.com/') + } + > + Lunar Client + {' '} + mod{' '} + + window.api.openBrowserWindow( + 'https://lunarclient.dev/apollo/developers/mods/coordinates' + ) + } + > + Coordinates + {' '} + to "Copy Coords to Clipboard". + + + + + + Move to a "corner" showing the entity in PieChart, but where the + entity is missing from PieChart in the adjacent chunks in the X and + Z directions. + + + + + + Copy the coordinates to the clipboard and the corner will be added + + + + + + Find two more corners: one with the same X chunk coordinate, one + with the same Z chunk coordinate. + + + + + + The solution is added. + + + )} - + x.chunk)} - fill="#8884d8" + fill={green500} /> {foundX && foundZ && ( )} @@ -137,19 +260,19 @@ function App() { {foundX && foundZ && ( - + - - {foundX * 16 + 8 * (foundX >= 0 ? 1 : -1)} - {foundZ * 16 + 8 * (foundZ >= 0 ? 1 : -1)} - {foundX} - {foundZ} - + + {foundX * 16 + 8 * (foundX >= 0 ? 1 : -1)} + {foundZ * 16 + 8 * (foundZ >= 0 ? 1 : -1)} + {foundX} + {foundZ} + )} {corners.map((corner, i) => ( - + @@ -162,9 +285,12 @@ function App() { aria-label="Remove" color="red.500" icon={} - onClick={() => + onClick={() => { + log.info( + `Removed corner: ${corner.position.x}, ${corner.position.y}, ${corner.position.z}` + ) setCorners(corners.filter((_, j) => i !== j)) - } + }} /> @@ -172,7 +298,7 @@ function App() { - + ) } diff --git a/src/constants.ts b/src/constants.ts index 0db970d..770dbbf 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1 +1,3 @@ export const clipboardTextUpdatedChannel = 'clipboard-text-updated' +export const openBrowserWindowChannel = 'open-browser-window' +export const getLogfilePathChannel = 'get-logfile-path' \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index f5b92a1..f4100f5 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,8 +1,14 @@ -import { app, BrowserWindow, clipboard } from 'electron' +import { app, BrowserWindow, clipboard, ipcMain, shell } from 'electron' +import path from 'path' import isDev from 'electron-is-dev' +import log from 'electron-log/main' import './index.css' -import { clipboardTextUpdatedChannel } from './constants' +import { + clipboardTextUpdatedChannel, + getLogfilePathChannel, + openBrowserWindowChannel, +} from './constants' // This allows TypeScript to pick up the magic constants that's auto-generated by Forge's Webpack // plugin that tells the Electron app where to look for the Webpack-bundled app code (depending on @@ -15,6 +21,9 @@ const clipboardPollInterval = 500 let mainWindow: BrowserWindow | undefined let lastText = clipboard.readText() +log.initialize() +log.info('Started PieRayHelper') + const clipboardPollHandle = setInterval(() => { const text = clipboard.readText() if (text === lastText || !mainWindow) return @@ -31,6 +40,7 @@ if (require('electron-squirrel-startup')) { const createWindow = (): void => { // Create the browser window. mainWindow = new BrowserWindow({ + icon: path.join(process.cwd(), 'images', 'icon.png'), height: 600, width: 800, webPreferences: { @@ -50,7 +60,16 @@ const createWindow = (): void => { // This method will be called when Electron has finished // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. -app.on('ready', createWindow) +app.on('ready', () => { + ipcMain.handle( + getLogfilePathChannel, + () => log.transports.file.getFile().path + ) + ipcMain.on(openBrowserWindowChannel, (_event, url: string) => + shell.openExternal(url) + ) + createWindow() +}) // Quit when all windows are closed, except on macOS. There, it's common // for applications and their menu bar to stay active until the user quits diff --git a/src/preload.ts b/src/preload.ts index 74e4b15..3376772 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -2,13 +2,20 @@ // https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts import { contextBridge, ipcRenderer } from 'electron/renderer' -import { clipboardTextUpdatedChannel } from './constants' +import { + clipboardTextUpdatedChannel, + getLogfilePathChannel, + openBrowserWindowChannel, +} from './constants' export const api = { + getLogfilePath: () => ipcRenderer.invoke(getLogfilePathChannel), onClipboardTextUpdated: (callback: (text: string) => void) => ipcRenderer.on(clipboardTextUpdatedChannel, (_event, value) => callback(value) ), + openBrowserWindow: (url: string) => + ipcRenderer.send(openBrowserWindowChannel, url), removeClipboardTextUpdatedListener: () => { ipcRenderer.removeAllListeners(clipboardTextUpdatedChannel) }, diff --git a/src/renderer.ts b/src/renderer.ts index 45f3885..d445ec4 100644 --- a/src/renderer.ts +++ b/src/renderer.ts @@ -1,5 +1,3 @@ -import { clipboard } from 'electron' - import './index.css' import './index.tsx' diff --git a/src/types.d.ts b/src/types.d.ts index f5df40d..54b75cf 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -10,17 +10,26 @@ interface ChunkDirection { z: 1 | -1 } +interface Corner { + chunk: Chunk + position: Vector3 +} + +interface Solution { + minX?: number + maxX?: number + minZ?: number + maxZ?: number + foundX?: number + foundZ?: number +} + interface Vector3 { x: number y: number z: number } -interface Corner { - chunk: Chunk - position: Vector3 -} - declare global { interface Window { api: typeof api diff --git a/yarn.lock b/yarn.lock index ebeb61a..56a499f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3567,6 +3567,11 @@ electron-is-dev@^3.0.1: resolved "https://registry.yarnpkg.com/electron-is-dev/-/electron-is-dev-3.0.1.tgz#1cbc79b1dd046787903acd357efdfab6549dc17a" integrity sha512-8TjjAh8Ec51hUi3o4TaU0mD3GMTOESi866oRNavj9A3IQJ7pmv+MJVmdZBFGw4GFT36X7bkqnuDNYvkQgvyI8Q== +electron-log@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/electron-log/-/electron-log-5.2.0.tgz#505716926dfcf9cb3e74f42b1003be6d865bcb88" + integrity sha512-VjLkvaLmbP3AOGOh5Fob9M8bFU0mmeSAb5G2EoTBx+kQLf2XA/0byzjsVGBTHhikbT+m1AB27NEQUv9wX9nM8w== + electron-squirrel-startup@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/electron-squirrel-startup/-/electron-squirrel-startup-1.0.1.tgz#c9171568d724884c7a2b03760bfeedcf921c63ab"