├── .github └── workflows │ └── release.yml ├── .gitignore ├── .markdownlint.json ├── .release-please-manifest.json ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── assets ├── 1.png ├── 2.png ├── 3.png ├── 4.png ├── readme-animation.gif ├── runmath_logo.png └── runmath_logo.svg ├── index.html ├── package.json ├── release-please-config.json ├── settings.html ├── src-tauri ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── build.rs ├── capabilities │ ├── base.json │ └── settings-page.json ├── icons │ ├── 128x128.png │ ├── 128x128@2x.png │ ├── 32x32.png │ ├── Square107x107Logo.png │ ├── Square142x142Logo.png │ ├── Square150x150Logo.png │ ├── Square284x284Logo.png │ ├── Square30x30Logo.png │ ├── Square310x310Logo.png │ ├── Square44x44Logo.png │ ├── Square71x71Logo.png │ ├── Square89x89Logo.png │ ├── StoreLogo.png │ ├── app-icon.png │ ├── icon.icns │ ├── icon.ico │ └── icon.png ├── src │ └── main.rs └── tauri.conf.json ├── src ├── App.tsx ├── app.module.css ├── app │ └── calculator │ │ ├── domain │ │ ├── Calculator.ts │ │ └── CalculatorSettings.ts │ │ └── infrastructure │ │ └── MathJsCalculator.ts ├── components │ ├── ConsoleInput.tsx │ ├── ConsoleResult.tsx │ ├── InputPlaceholder.tsx │ ├── Settings │ │ ├── BGColor.tsx │ │ ├── Checkbox.tsx │ │ ├── FormatSelector.tsx │ │ ├── HideOnEnter.tsx │ │ ├── RunOnStart.tsx │ │ ├── UseBigNumbers.tsx │ │ ├── bgcolor.css │ │ ├── index.module.css │ │ └── index.tsx │ ├── SubConsoleResult.tsx │ └── console.module.css ├── hooks │ ├── useBackgroundColor.ts │ ├── useBiggerScreen.ts │ ├── useCalculator.ts │ ├── useCopyToClipboardSubscription.ts │ ├── useExitOnClose.ts │ ├── useGlobalShortcut.ts │ └── useHistory.ts ├── index.css ├── main.tsx ├── main_settings.tsx ├── state │ ├── calculator.ts │ └── settings.ts ├── utils │ ├── configureRunOnStart.ts │ ├── configureShortcuts.ts │ ├── createSettingsPage.ts │ ├── singleInstance.ts │ ├── toggleWindowView.ts │ └── updateNotifier.ts └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: 'Generate Release' 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | jobs: 8 | test-types: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | 13 | - name: setup node 14 | uses: actions/setup-node@v4 15 | with: 16 | node-version: 22 17 | 18 | - uses: pnpm/action-setup@v3 19 | name: Install pnpm 20 | with: 21 | version: 8 22 | 23 | - name: Install app dependencies 24 | run: pnpm i 25 | 26 | - name: Run typecheck 27 | run: pnpm tsc 28 | 29 | release-please: 30 | runs-on: ubuntu-latest 31 | needs: test-types 32 | 33 | outputs: 34 | new_release: ${{ steps.release.outputs.release_created }} 35 | 36 | steps: 37 | - uses: googleapis/release-please-action@v4 38 | id: release 39 | 40 | build-executable: 41 | runs-on: windows-latest 42 | needs: release-please 43 | environment: publish 44 | if: needs.release-please.outputs.new_release == 'true' 45 | 46 | steps: 47 | - uses: actions/checkout@v4 48 | 49 | - name: setup node 50 | uses: actions/setup-node@v4 51 | with: 52 | node-version: 22 53 | 54 | - uses: pnpm/action-setup@v3 55 | name: Install pnpm 56 | with: 57 | version: 8 58 | 59 | - name: install Rust stable 60 | uses: dtolnay/rust-toolchain@stable 61 | 62 | - uses: Swatinem/rust-cache@v2 63 | with: 64 | workspaces: "./src-tauri -> target" 65 | 66 | - name: Install app dependencies 67 | run: pnpm i 68 | 69 | - name: get latest release id 70 | id: latest_release_id 71 | uses: actions/github-script@v7 72 | with: 73 | script: | 74 | const { data } = await github.rest.repos.getLatestRelease({ 75 | owner: context.repo.owner, 76 | repo: context.repo.repo 77 | }) 78 | 79 | return data.id 80 | 81 | - uses: tauri-apps/tauri-action@v0 82 | id: tauri_action 83 | env: 84 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 85 | TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} 86 | TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }} 87 | with: 88 | releaseId: ${{ steps.latest_release_id.outputs.result }} 89 | 90 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | yarn.lock 2 | pnpm-lock.yaml 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | pnpm-debug.log* 11 | lerna-debug.log* 12 | 13 | node_modules 14 | dist 15 | dist-ssr 16 | gen 17 | *.local 18 | 19 | # Editor directories and files 20 | .vscode/* 21 | !.vscode/extensions.json 22 | .idea 23 | .DS_Store 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "MD033": false, 3 | "MD041": false 4 | } 5 | -------------------------------------------------------------------------------- /.release-please-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | ".": "1.17.0" 3 | } 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [1.17.0](https://github.com/dubisdev/runmath/compare/runmath-v1.16.7...runmath-v1.17.0) (2024-12-22) 4 | 5 | 6 | ### Features 7 | 8 | * Add option to hide on Enter [#19](https://github.com/dubisdev/runmath/issues/19) ([49112d6](https://github.com/dubisdev/runmath/commit/49112d6cff1a422bbeafe58d8eda856806d7a0ee)) 9 | 10 | 11 | ### Bug Fixes 12 | 13 | * Updgrade to React 19 ([2b46874](https://github.com/dubisdev/runmath/commit/2b468743b39b95495a88b536b708ba6b0096d743)) 14 | 15 | ## [1.16.7](https://github.com/dubisdev/runmath/compare/runmath-v1.16.6...runmath-v1.16.7) (2024-10-02) 16 | 17 | 18 | ### Bug Fixes 19 | 20 | * Update to Tauri v2 ([61d8de7](https://github.com/dubisdev/runmath/commit/61d8de7f933bd253f9d67caa1c63f109aa2d999f)) 21 | 22 | ## [1.16.6](https://github.com/dubisdev/runmath/compare/runmath-v1.16.5...runmath-v1.16.6) (2024-09-11) 23 | 24 | 25 | ### Bug Fixes 26 | 27 | * Update deps ([11c3f94](https://github.com/dubisdev/runmath/commit/11c3f946899cb76d9ce7dcbe3e277e20624182f9)) 28 | 29 | ## [1.16.5](https://github.com/dubisdev/runmath/compare/runmath-v1.16.4...runmath-v1.16.5) (2024-08-26) 30 | 31 | 32 | ### Bug Fixes 33 | 34 | * Update deps ([763b332](https://github.com/dubisdev/runmath/commit/763b33264c08100560c60e36cd7ca7d5228d8f9a)) 35 | 36 | ## [1.16.4](https://github.com/dubisdev/runmath/compare/runmath-v1.16.3...runmath-v1.16.4) (2024-08-17) 37 | 38 | 39 | ### Bug Fixes 40 | 41 | * Update deps ([9c98045](https://github.com/dubisdev/runmath/commit/9c98045d26a34ab61f249fbd1ecadbcec2b948b6)) 42 | 43 | ## [1.16.3](https://github.com/dubisdev/runmath/compare/runmath-v1.16.2...runmath-v1.16.3) (2024-08-11) 44 | 45 | 46 | ### Bug Fixes 47 | 48 | * Update dependencies ([b1893cf](https://github.com/dubisdev/runmath/commit/b1893cf0d7d3ad50e1d6ad8e405d943256f9a485)) 49 | 50 | ## [1.16.2](https://github.com/dubisdev/runmath/compare/runmath-v1.16.1...runmath-v1.16.2) (2024-08-05) 51 | 52 | 53 | ### Bug Fixes 54 | 55 | * Update Tauri version to RC ([b338a33](https://github.com/dubisdev/runmath/commit/b338a33589382874410c87a4dba351707f04ed0e)) 56 | 57 | ## [1.16.1](https://github.com/dubisdev/runmath/compare/runmath-v1.16.0...runmath-v1.16.1) (2024-07-23) 58 | 59 | 60 | ### Bug Fixes 61 | 62 | * Update dependencies ([020a948](https://github.com/dubisdev/runmath/commit/020a948f0947309950e7b1e64e614156623e64d1)) 63 | 64 | ## [1.16.0](https://github.com/dubisdev/runmath/compare/runmath-v1.15.11...runmath-v1.16.0) (2024-07-21) 65 | 66 | 67 | ### Features 68 | 69 | * `alt+q` quits the app ([b224add](https://github.com/dubisdev/runmath/commit/b224add80590e4e6abb50174f0223cf8509fe6d9)) 70 | * 💫 Support for BigNumbers and different notations ([426e168](https://github.com/dubisdev/runmath/commit/426e1683a14b617ba1227efe2d38fb691f326861)), closes [#1](https://github.com/dubisdev/runmath/issues/1) 71 | * 🔐 Add content security policy ([cb6fbc2](https://github.com/dubisdev/runmath/commit/cb6fbc2284b14374382af9386796ce9dc636d884)) 72 | * add autostart option ([4a22134](https://github.com/dubisdev/runmath/commit/4a22134ca6d17f2e12ac8928a435703c6d203e08)) 73 | * add autoupdater ([e53cbc5](https://github.com/dubisdev/runmath/commit/e53cbc536569f472e29e2589fd18fce2dab7f874)) 74 | * add more placeholder animations ([e62f3b1](https://github.com/dubisdev/runmath/commit/e62f3b170c9471f719274a954bf5e6ec9dd89af4)) 75 | * add more placeholders ([48e6a78](https://github.com/dubisdev/runmath/commit/48e6a785ff06d1c3d59a9e96a8be783ba810b70c)) 76 | * add new icon ([d75b9ec](https://github.com/dubisdev/runmath/commit/d75b9ecbe3f96411880b9c7ad832ff7929e31285)) 77 | * Add option to open the settings page from the tray ([a9db499](https://github.com/dubisdev/runmath/commit/a9db499e51a86924d9268a5305afb545ea5170c4)) 78 | * add placeholder animation ([666b244](https://github.com/dubisdev/runmath/commit/666b2449afcad8840d824483bb8a27d8fe3a4967)) 79 | * Add tray icon with toggle and quit utilities ([4fb2f68](https://github.com/dubisdev/runmath/commit/4fb2f68818e0497bb9efc52c68db49b69c9025d5)) 80 | * add update available notification ([dde1252](https://github.com/dubisdev/runmath/commit/dde1252306a50c7ede33732a6d6e8583b57a7d96)) 81 | * added settings `alt+s` ([9fc5af4](https://github.com/dubisdev/runmath/commit/9fc5af4f0c2f3a70c1c57b9ba0fe294ab937ec43)) 82 | * configure single instance ([ecc5746](https://github.com/dubisdev/runmath/commit/ecc5746be5282c1ea71ff0aa6d3b499d54c0987c)) 83 | * EXE installer now available ([53c9452](https://github.com/dubisdev/runmath/commit/53c94524c96fdf3aa1cfd77ff6d0c22ade8da6c5)) 84 | * handle long results in subconsole view ([33f4ed2](https://github.com/dubisdev/runmath/commit/33f4ed2768c7d35b05a5e6b17e87fe4118f64449)) 85 | * navigate history with `ArrowUp` and `ArrowDown` ([d6ea0b2](https://github.com/dubisdev/runmath/commit/d6ea0b2b262348de13f15d4ed99c0ba48d0f8d01)) 86 | * save window position on exit ([9a308b7](https://github.com/dubisdev/runmath/commit/9a308b78cacd9a171a532cb06760e1e3f996840b)) 87 | * Unit conversion and Complex numbers ([8c36d99](https://github.com/dubisdev/runmath/commit/8c36d999dc85fadf25b5449c265742ca431b2ff7)) 88 | * Use `Alt+U` to open the downloads page ([e97ce8f](https://github.com/dubisdev/runmath/commit/e97ce8f5a08720ef040615977a6812419604463b)) 89 | 90 | 91 | ### Bug Fixes 92 | 93 | * add capabilities to the settings page ([dbb0e26](https://github.com/dubisdev/runmath/commit/dbb0e261d748cfb8b943b6e73026aaf4abcad04a)) 94 | * add csp to improve security ([e4248f0](https://github.com/dubisdev/runmath/commit/e4248f025e97425fae27748243dfc500977437eb)) 95 | * add drag permission ([1f053e2](https://github.com/dubisdev/runmath/commit/1f053e2d57fa997ccd72a3d97ffcc97cf145c95e)) 96 | * add space between input and result ([cacf607](https://github.com/dubisdev/runmath/commit/cacf607eea1272d2e7f31ae8650871a3af6aad2c)) 97 | * Avoid duplicate shortcut triggers making it impossible to hide the app ([445e73d](https://github.com/dubisdev/runmath/commit/445e73d1423bcf499024cd7de9e9b26d8d3f7da6)) 98 | * Avoid spellchecking in the input ([#7](https://github.com/dubisdev/runmath/issues/7)) ([7f3c268](https://github.com/dubisdev/runmath/commit/7f3c2687bd6bf5aa603add6ecd155de18d44e08c)) 99 | * build options ([b40171b](https://github.com/dubisdev/runmath/commit/b40171bbb6dd78e6c999c9017b70cca293e0f7d2)) 100 | * checkbox not calling onchange ([b7f6660](https://github.com/dubisdev/runmath/commit/b7f6660aac6b9df8218ba039232cf38d90ffeb8b)) 101 | * ci ([8265ff3](https://github.com/dubisdev/runmath/commit/8265ff39604d4cc12d21af4a999430f627e9df40)) 102 | * clear input on enter ([4fc059d](https://github.com/dubisdev/runmath/commit/4fc059d6045a869475dde4559449dcf5e6130930)) 103 | * copy to clipboard ([f732590](https://github.com/dubisdev/runmath/commit/f7325907e011bcf40f1e966e1f5897323dfe8cf6)) 104 | * csp error blocking color picker ([8c3473f](https://github.com/dubisdev/runmath/commit/8c3473f8679c055a39911694e299104493c6404e)) 105 | * csp issue ([741637b](https://github.com/dubisdev/runmath/commit/741637b1de6debb100089dd2e39286240da09351)) 106 | * dialog permission ([a9610ec](https://github.com/dubisdev/runmath/commit/a9610ec4e8b8be0d68342ec803dad7afbcee625b)) 107 | * Fit window size to app content ([790ad8a](https://github.com/dubisdev/runmath/commit/790ad8ac5c3c733636a9702f91f254f846645010)) 108 | * improve setting styles ([f606076](https://github.com/dubisdev/runmath/commit/f6060764594fca513dcff864932c57e976c30d12)) 109 | * improve settings page ([48ec479](https://github.com/dubisdev/runmath/commit/48ec4797cf9dc96875b2ab0b15b406951f330f31)) 110 | * Migrate RunMath to Kiimesoft organization ([646e128](https://github.com/dubisdev/runmath/commit/646e1287dae1b224b476f101a41770f60db124ed)) 111 | * min height breaking app UI ([0b9df85](https://github.com/dubisdev/runmath/commit/0b9df851562794561d03022751f09837a7a67088)) 112 | * move to [@dubisdev](https://github.com/dubisdev) acc ([f50db38](https://github.com/dubisdev/runmath/commit/f50db389eb9b8614e8ee886b29d9db32919a1cc3)) 113 | * Notation option not updated when resetting settings ([60f4af4](https://github.com/dubisdev/runmath/commit/60f4af47c37b1985ad8516ec783543a77c3bd213)), closes [#4](https://github.com/dubisdev/runmath/issues/4) 114 | * quiet updater not working ([8842378](https://github.com/dubisdev/runmath/commit/88423784b434e8c65b1ef668c967b8b32c3d9a5b)) 115 | * remove old shortcut information ([288ff9c](https://github.com/dubisdev/runmath/commit/288ff9cf3c7f451251af589c26a9b3caef5943ed)) 116 | * rm settings manager bloatware + update deps ([be0c846](https://github.com/dubisdev/runmath/commit/be0c846dd3e04772cbc74ba5b62675bdbb731ab0)) 117 | * semantic-release ([1b3e346](https://github.com/dubisdev/runmath/commit/1b3e346c68057ff4f6185ee5afa6b28fdce5d554)) 118 | * settings error on first load ([d11dc4a](https://github.com/dubisdev/runmath/commit/d11dc4a61744d5f6e9fe0c7b60947a7c260073c0)) 119 | * settings page on top of runmath window ([8b5a57a](https://github.com/dubisdev/runmath/commit/8b5a57ab534798bf383958dc5d9eefe0d4eb3859)) 120 | * Start hidden when run on windows start ([a7593fe](https://github.com/dubisdev/runmath/commit/a7593feea0bd20074ce0d9951965563df62a5310)), closes [#6](https://github.com/dubisdev/runmath/issues/6) 121 | * test autoupdater ([03c8daf](https://github.com/dubisdev/runmath/commit/03c8daf68c2e347e996e14d4271f6d3a6ff2d355)) 122 | * type error ([b8ab7fe](https://github.com/dubisdev/runmath/commit/b8ab7fe8e53a20034ce7a18a06fb40dc8e164fbd)) 123 | * Update calculate when notation settings change ([c292974](https://github.com/dubisdev/runmath/commit/c292974bb143b240883cde3bccbeb1281f866efb)), closes [#3](https://github.com/dubisdev/runmath/issues/3) 124 | * Update deoendencies ([b33cfef](https://github.com/dubisdev/runmath/commit/b33cfef8f854afa5077e97e4e7f36a1cf7ee1516)) 125 | * update dependencies ([b611f3a](https://github.com/dubisdev/runmath/commit/b611f3a03ebbdb670126d936c3128c43f9c090e7)) 126 | * update deps ([f3942d9](https://github.com/dubisdev/runmath/commit/f3942d949d451b5fc9189a81e1d654bf2c03943b)) 127 | * update deps ([dc0681b](https://github.com/dubisdev/runmath/commit/dc0681bb4ff077d259fc504cf28035dfb5cdda59)) 128 | * update deps ([b56dcf0](https://github.com/dubisdev/runmath/commit/b56dcf04a15f14b1e1a50d5a00b1aeb5cfb47ac6)) 129 | * update minor deps ([7c02a00](https://github.com/dubisdev/runmath/commit/7c02a005d4f3ceb2e97eda57573ae05e05a1e8de)) 130 | * Update to latest Tauri version ([aef1e3a](https://github.com/dubisdev/runmath/commit/aef1e3ad9cf2ba65994453971e7449264440e3f4)) 131 | * use / as separator for the date ([bde822a](https://github.com/dubisdev/runmath/commit/bde822a3ba82b5037de8512f021c9cb1c9c81ec4)) 132 | * use just needed APIS ([466ef23](https://github.com/dubisdev/runmath/commit/466ef23a623ea065725584ff4b57d7437b02e2ea)) 133 | * use precission for floating point numbers ([93aac10](https://github.com/dubisdev/runmath/commit/93aac10fb93c2fb7aed61a9a6cae477e2ec40442)) 134 | 135 | 136 | ### Performance Improvements 137 | 138 | * debounce settings saving to disk ([edf0462](https://github.com/dubisdev/runmath/commit/edf04623f653b603fbee2b4d81850024566985cd)) 139 | * remove windows state as it does not work ([94dcb9e](https://github.com/dubisdev/runmath/commit/94dcb9e1f5b169b8a3dbb446b8e5871d15db2479)) 140 | * simplify calculation results ([1aedf76](https://github.com/dubisdev/runmath/commit/1aedf76cff57ce822e05180ebe3f1cec4da6915e)) 141 | * simplify calculator state ([e2fee6a](https://github.com/dubisdev/runmath/commit/e2fee6a804305c2156f808d2b053120307b713d8)) 142 | * update deps ([e50953f](https://github.com/dubisdev/runmath/commit/e50953fb6c9a100923e520f1b2cab6055100cd2c)) 143 | * update deps ([9d2d4b0](https://github.com/dubisdev/runmath/commit/9d2d4b06e9e81c76c5ab649faabc0cb8ea2ca120)) 144 | * update deps ([e5e3a9d](https://github.com/dubisdev/runmath/commit/e5e3a9d4940e89bc2e968babc69e09a8f7ca75dc)) 145 | * update tauri version ([5844de2](https://github.com/dubisdev/runmath/commit/5844de2bff8c0405b6107b99d7914213c03706f9)) 146 | * update to tauri 1.3 ([6fa84c3](https://github.com/dubisdev/runmath/commit/6fa84c3744d28ede8d6564594af0f2262738bff0)) 147 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 David Jiménez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Runmath icon 4 | 5 |

6 | 7 |

RunMath

8 | 9 |

10 | A keyboard-first calculator for Windows - Do math, quickly 🚀 11 |
12 | Built with ❤️‍🔥 by @dubisdev 13 |

14 | 15 |

16 | 17 | 🔗 Go to Download Page 18 | 19 |   |   Give it a Star ⭐ |   20 | Support the project 🤝 21 |

22 | 23 |

24 | Last available version 27 | Total downloads 30 |

31 | 32 |
33 | 34 | ## 🧮 Features 35 | 36 | - `Basic math operations`: `+`, `-`, `x`, `/`, `%`, `^`, `√`, `!`,... 37 | - `Complex numbers` (ex: 2i * 8i = -16) 38 | - `Unit conversion` (ex: 1m to cm = 100) 39 | 40 |

41 | Animated runmath gif 42 | Runmath capabilities 43 | Runmath available config options 44 | 45 | > **Note**: Feel free to open an issue if you have 46 | > any suggestions or ideas for new features! 47 | 48 | ## 🚄 Shortcuts 49 | 50 | RunMath comes with a few shortcuts to make your life easier: 51 | 52 | - Use `up` and `down` arrows to navigate through the history 53 | - `Alt+M` to toggle the visibility of the app (M stands for math 👀) 54 | - `Alt+Q` to quit the app 55 | - `Alt+S` opens the settings 56 | - `Enter` copies the result to the clipboard and clears the input 57 | - `=` saves the result to the history 58 | - `tab` to select the input 59 | - `esc` to clear the input 60 | 61 | ## ⚙️ Settings 62 | 63 | Configure the app to your liking! For now, you can change: 64 | 65 | - Background color 66 | - Run app on startup or not 67 | - Use Big Numbers (for calculations with big numbers > 15 digits) 68 | - Output format (scientific, fixed, engineering, auto) 69 | - Hide on Enter 70 | 71 | ## 🔐 Privacy Policy 72 | 73 | RunMath does NOT: 74 | 75 | - collect any personal information, 76 | - track you, 77 | - send your keystrokes to any server, 78 | - send any data to any server. 79 | 80 | All the calculations are done locally on your computer. 81 | Your preferences are stored locally on your computer. 82 | 83 | The Windows Store version does not have update checks, 84 | so it does not connect to any server. 85 | 86 | ## ⚖️ License 87 | 88 | [MIT](./LICENSE.md) © [David Jimenez](https://dubis.dev) 89 | -------------------------------------------------------------------------------- /assets/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dubisdev/runmath/0dbfaf9e05b28c32ec4295bfdb6d33eedea93b83/assets/1.png -------------------------------------------------------------------------------- /assets/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dubisdev/runmath/0dbfaf9e05b28c32ec4295bfdb6d33eedea93b83/assets/2.png -------------------------------------------------------------------------------- /assets/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dubisdev/runmath/0dbfaf9e05b28c32ec4295bfdb6d33eedea93b83/assets/3.png -------------------------------------------------------------------------------- /assets/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dubisdev/runmath/0dbfaf9e05b28c32ec4295bfdb6d33eedea93b83/assets/4.png -------------------------------------------------------------------------------- /assets/readme-animation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dubisdev/runmath/0dbfaf9e05b28c32ec4295bfdb6d33eedea93b83/assets/readme-animation.gif -------------------------------------------------------------------------------- /assets/runmath_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dubisdev/runmath/0dbfaf9e05b28c32ec4295bfdb6d33eedea93b83/assets/runmath_logo.png -------------------------------------------------------------------------------- /assets/runmath_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 60 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | RunMath 9 | 10 | 11 | 12 |

13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "runmath", 3 | "private": true, 4 | "version": "1.17.0", 5 | "license": "MIT", 6 | "type": "module", 7 | "scripts": { 8 | "dev": "vite", 9 | "build": "tsc && vite build", 10 | "preview": "vite preview", 11 | "tauri:dev": "tauri dev", 12 | "tauri:build": "tauri build" 13 | }, 14 | "dependencies": { 15 | "@dubisdev/pointer-stack-structure": "1.1.1", 16 | "@tauri-apps/api": "2.0.1", 17 | "@tauri-apps/plugin-autostart": "2.2.0", 18 | "@tauri-apps/plugin-cli": "2.2.0", 19 | "@tauri-apps/plugin-clipboard-manager": "2.2.0", 20 | "@tauri-apps/plugin-dialog": "2.2.0", 21 | "@tauri-apps/plugin-global-shortcut": "2.2.0", 22 | "@tauri-apps/plugin-process": "2.2.0", 23 | "@tauri-apps/plugin-updater": "2.3.0", 24 | "just-debounce": "1.1.0", 25 | "mathjs": "14.0.1", 26 | "react": "19.0.0", 27 | "react-colorful": "5.6.1", 28 | "react-dom": "19.0.0", 29 | "react-switch": "7.1.0", 30 | "react-textra": "0.2.0", 31 | "shared-zustand": "2.1.0", 32 | "the-new-css-reset": "1.11.3", 33 | "zustand": "5.0.2" 34 | }, 35 | "devDependencies": { 36 | "@tauri-apps/cli": "2.0.0", 37 | "@types/node": "22.10.2", 38 | "@types/react": "19.0.2", 39 | "@types/react-dom": "19.0.2", 40 | "@vitejs/plugin-react": "4.3.4", 41 | "typescript": "5.7.2", 42 | "vite": "6.0.7" 43 | }, 44 | "engines": { 45 | "node": ">=22" 46 | }, 47 | "repository": "dubisdev/runmath" 48 | } 49 | -------------------------------------------------------------------------------- /release-please-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": { 3 | ".": {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /settings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | RunMath Settings 10 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src-tauri/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | -------------------------------------------------------------------------------- /src-tauri/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "runmath" 3 | version = "1.0.0" 4 | description = "Do math, quickly 🚀" 5 | authors = ["David Jiménez"] 6 | license = "MIT" 7 | repository = "" 8 | default-run = "runmath" 9 | edition = "2021" 10 | rust-version = "1.81" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [build-dependencies] 15 | tauri-build = { version = "2", features = [] } 16 | 17 | [dependencies] 18 | serde_json = "1.0" 19 | serde = { version = "1.0", features = ["derive"] } 20 | tauri = { version = "2", features = ["tray-icon"] } 21 | tauri-plugin-autostart = "2" 22 | tauri-plugin-single-instance = "2" 23 | tauri-plugin-clipboard-manager = "2" 24 | tauri-plugin-process = "2" 25 | tauri-plugin-updater = "2" 26 | tauri-plugin-dialog = "2" 27 | 28 | [features] 29 | # by default Tauri runs in production mode 30 | # when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL 31 | default = [ "custom-protocol" ] 32 | # this feature is used used for production builds where `devPath` points to the filesystem 33 | # DO NOT remove this 34 | custom-protocol = [ "tauri/custom-protocol" ] 35 | 36 | [target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies] 37 | tauri-plugin-cli = "2" 38 | tauri-plugin-global-shortcut = "2" 39 | -------------------------------------------------------------------------------- /src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tauri_build::build() 3 | } 4 | -------------------------------------------------------------------------------- /src-tauri/capabilities/base.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../gen/schemas/desktop-schema.json", 3 | "identifier": "base", 4 | "description": "Basic capabilities needed for the correct functioning of the application", 5 | "local": true, 6 | "platforms": [ 7 | "windows" 8 | ], 9 | "windows": [ 10 | "main" 11 | ], 12 | "permissions": [ 13 | "dialog:allow-ask", 14 | "dialog:allow-confirm", 15 | "core:event:allow-listen", 16 | "core:menu:default", 17 | "core:tray:default", 18 | "cli:allow-cli-matches", 19 | "process:allow-exit", 20 | "process:allow-restart", 21 | "global-shortcut:allow-register", 22 | "global-shortcut:allow-unregister-all", 23 | "clipboard-manager:allow-write-text", 24 | "core:window:allow-start-dragging", 25 | "core:window:allow-is-visible", 26 | "core:window:allow-show", 27 | "core:window:allow-hide", 28 | "core:window:allow-set-focus", 29 | "core:window:allow-unminimize", 30 | "core:window:allow-close", 31 | "core:window:allow-set-size", 32 | "core:webview:allow-internal-toggle-devtools", 33 | "core:webview:allow-create-webview-window", 34 | "updater:allow-check", 35 | "updater:allow-download-and-install" 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /src-tauri/capabilities/settings-page.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../gen/schemas/desktop-schema.json", 3 | "identifier": "settings-page", 4 | "description": "Capabilities used by the settings page", 5 | "local": true, 6 | "platforms": [ 7 | "windows" 8 | ], 9 | "windows": [ 10 | "settings-page" 11 | ], 12 | "permissions": [ 13 | "core:event:allow-listen", 14 | "core:menu:default", 15 | "core:tray:default", 16 | "autostart:allow-enable", 17 | "autostart:allow-disable", 18 | "autostart:allow-is-enabled", 19 | "core:window:allow-start-dragging", 20 | "core:window:allow-is-visible", 21 | "core:window:allow-show", 22 | "core:window:allow-hide", 23 | "core:window:allow-set-focus", 24 | "core:window:allow-unminimize", 25 | "core:window:allow-close", 26 | "core:window:allow-set-size", 27 | "core:webview:allow-internal-toggle-devtools", 28 | "core:webview:allow-create-webview-window", 29 | "updater:allow-check", 30 | "updater:allow-download-and-install" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /src-tauri/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dubisdev/runmath/0dbfaf9e05b28c32ec4295bfdb6d33eedea93b83/src-tauri/icons/128x128.png -------------------------------------------------------------------------------- /src-tauri/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dubisdev/runmath/0dbfaf9e05b28c32ec4295bfdb6d33eedea93b83/src-tauri/icons/128x128@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dubisdev/runmath/0dbfaf9e05b28c32ec4295bfdb6d33eedea93b83/src-tauri/icons/32x32.png -------------------------------------------------------------------------------- /src-tauri/icons/Square107x107Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dubisdev/runmath/0dbfaf9e05b28c32ec4295bfdb6d33eedea93b83/src-tauri/icons/Square107x107Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square142x142Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dubisdev/runmath/0dbfaf9e05b28c32ec4295bfdb6d33eedea93b83/src-tauri/icons/Square142x142Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dubisdev/runmath/0dbfaf9e05b28c32ec4295bfdb6d33eedea93b83/src-tauri/icons/Square150x150Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square284x284Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dubisdev/runmath/0dbfaf9e05b28c32ec4295bfdb6d33eedea93b83/src-tauri/icons/Square284x284Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square30x30Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dubisdev/runmath/0dbfaf9e05b28c32ec4295bfdb6d33eedea93b83/src-tauri/icons/Square30x30Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square310x310Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dubisdev/runmath/0dbfaf9e05b28c32ec4295bfdb6d33eedea93b83/src-tauri/icons/Square310x310Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dubisdev/runmath/0dbfaf9e05b28c32ec4295bfdb6d33eedea93b83/src-tauri/icons/Square44x44Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square71x71Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dubisdev/runmath/0dbfaf9e05b28c32ec4295bfdb6d33eedea93b83/src-tauri/icons/Square71x71Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square89x89Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dubisdev/runmath/0dbfaf9e05b28c32ec4295bfdb6d33eedea93b83/src-tauri/icons/Square89x89Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dubisdev/runmath/0dbfaf9e05b28c32ec4295bfdb6d33eedea93b83/src-tauri/icons/StoreLogo.png -------------------------------------------------------------------------------- /src-tauri/icons/app-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dubisdev/runmath/0dbfaf9e05b28c32ec4295bfdb6d33eedea93b83/src-tauri/icons/app-icon.png -------------------------------------------------------------------------------- /src-tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dubisdev/runmath/0dbfaf9e05b28c32ec4295bfdb6d33eedea93b83/src-tauri/icons/icon.icns -------------------------------------------------------------------------------- /src-tauri/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dubisdev/runmath/0dbfaf9e05b28c32ec4295bfdb6d33eedea93b83/src-tauri/icons/icon.ico -------------------------------------------------------------------------------- /src-tauri/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dubisdev/runmath/0dbfaf9e05b28c32ec4295bfdb6d33eedea93b83/src-tauri/icons/icon.png -------------------------------------------------------------------------------- /src-tauri/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr( 2 | all(not(debug_assertions), target_os = "windows"), 3 | windows_subsystem = "windows" 4 | )] 5 | 6 | use tauri::tray::{MouseButton, MouseButtonState, TrayIconEvent}; 7 | use tauri::{App, AppHandle, Emitter, Manager}; 8 | 9 | use tauri::menu::{MenuBuilder, MenuItemBuilder}; 10 | 11 | use tauri_plugin_autostart::MacosLauncher; 12 | 13 | #[derive(Clone, serde::Serialize)] 14 | struct Payload { 15 | args: Vec, 16 | cwd: String, 17 | } 18 | 19 | fn main() { 20 | tauri::Builder::default() 21 | .plugin(tauri_plugin_dialog::init()) 22 | .plugin(tauri_plugin_updater::Builder::new().build()) 23 | .plugin(tauri_plugin_global_shortcut::Builder::new().build()) 24 | .plugin(tauri_plugin_process::init()) 25 | .plugin(tauri_plugin_cli::init()) 26 | .plugin(tauri_plugin_clipboard_manager::init()) 27 | .plugin(tauri_plugin_autostart::init( 28 | MacosLauncher::LaunchAgent, 29 | Some(vec!["--start-hidden"]), 30 | )) 31 | .plugin(tauri_plugin_single_instance::init(|app, argv, cwd| { 32 | app.emit("single-instance", Payload { args: argv, cwd }) 33 | .unwrap(); 34 | })) 35 | .setup(|app| { 36 | configure_tray_menu(&app)?; 37 | Ok(()) 38 | }) 39 | .run(tauri::generate_context!()) 40 | .expect("error while running tauri application"); 41 | } 42 | 43 | fn configure_tray_menu(app: &App) -> Result<(), tauri::Error> { 44 | let quit = MenuItemBuilder::new("Quit".to_string()) 45 | .id("quit") 46 | .build(app)?; 47 | let toggle = MenuItemBuilder::new("Toggle".to_string()) 48 | .id("toggle") 49 | .build(app)?; 50 | let settings = MenuItemBuilder::new("Settings".to_string()) 51 | .id("settings") 52 | .build(app)?; 53 | 54 | let tray_menu = MenuBuilder::new(app) 55 | .items(&[&settings, &toggle, &quit]) 56 | .build()?; 57 | 58 | let tray_icon = app.tray_by_id("main").unwrap(); 59 | 60 | tray_icon.set_menu(Some(tray_menu))?; 61 | 62 | tray_icon.on_menu_event(|app, event| match event.id.as_ref() { 63 | "quit" => std::process::exit(0), 64 | "toggle" => toggle_window(app), 65 | "settings" => open_settings_window(app), 66 | _ => {} 67 | }); 68 | 69 | tray_icon.on_tray_icon_event(|tray, event| { 70 | if let TrayIconEvent::Click { 71 | button: MouseButton::Left, 72 | button_state: MouseButtonState::Up, 73 | .. 74 | } = event 75 | { 76 | let app = tray.app_handle(); 77 | toggle_window(app); 78 | } 79 | }); 80 | 81 | Ok(()) 82 | } 83 | 84 | fn toggle_window(app: &AppHandle) { 85 | let window = app.get_webview_window("main").unwrap(); 86 | if window.is_visible().unwrap() { 87 | window.hide().unwrap(); 88 | return; 89 | } else { 90 | window.show().unwrap(); 91 | window.set_focus().unwrap(); 92 | return; 93 | } 94 | } 95 | 96 | fn open_settings_window(app: &AppHandle) { 97 | app.emit_to("main", "open-settings", ()).unwrap(); 98 | } 99 | -------------------------------------------------------------------------------- /src-tauri/tauri.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../node_modules/@tauri-apps/cli/config.schema.json", 3 | "build": { 4 | "beforeBuildCommand": "pnpm build", 5 | "beforeDevCommand": "pnpm dev", 6 | "frontendDist": "../dist", 7 | "devUrl": "http://localhost:3000" 8 | }, 9 | "bundle": { 10 | "active": true, 11 | "category": "Utility", 12 | "publisher": "dubis.dev", 13 | "copyright": "David Jiménez Ⓒ 2025", 14 | "icon": [ 15 | "icons/32x32.png", 16 | "icons/128x128.png", 17 | "icons/128x128@2x.png", 18 | "icons/icon.icns", 19 | "icons/icon.ico" 20 | ], 21 | "windows": { 22 | "nsis": { 23 | "installerIcon": "icons/icon.ico" 24 | } 25 | }, 26 | "longDescription": "Do math, quickly 🚀", 27 | "targets": [ 28 | "msi", 29 | "nsis" 30 | ], 31 | "createUpdaterArtifacts": true 32 | }, 33 | "productName": "RunMath", 34 | "version": "../package.json", 35 | "identifier": "dev.dubis.runmath", 36 | "plugins": { 37 | "cli": { 38 | "args": [ 39 | { 40 | "name": "start-hidden", 41 | "short": "s" 42 | } 43 | ] 44 | }, 45 | "updater": { 46 | "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDFBMzM2NDgwQjQwQzFEOApSV1RZd1VBTFNEYWpBWFpMTlIxSzVYdEhjSDFqTTdPcFkwUEQzVHFsWlZ6a1dGRGFKdHY0UFBmbQo=", 47 | "endpoints": [ 48 | "https://github.com/dubisdev/runmath/releases/latest/download/latest.json" 49 | ], 50 | "windows": { 51 | "installMode": "passive" 52 | } 53 | } 54 | }, 55 | "app": { 56 | "security": { 57 | "dangerousDisableAssetCspModification": true, 58 | "csp": "default-src 'self'; style-src 'self' 'unsafe-inline';connect-src ipc: http://ipc.localhost api.github.com" 59 | }, 60 | "trayIcon": { 61 | "iconPath": "icons/app-icon.png" 62 | }, 63 | "windows": [ 64 | { 65 | "title": "RunMath", 66 | "alwaysOnTop": true, 67 | "fullscreen": false, 68 | "height": 10, 69 | "resizable": false, 70 | "width": 700, 71 | "center": true, 72 | "decorations": false, 73 | "transparent": true 74 | } 75 | ] 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { ConsoleInput } from "@components/ConsoleInput"; 2 | import { ConsoleResult } from "@components/ConsoleResult"; 3 | import styles from "./app.module.css"; 4 | import { useBakgroundColor } from "./hooks/useBackgroundColor"; 5 | import { useExitOnClose } from "./hooks/useExitOnClose"; 6 | import { useGlobalShortcut } from "./hooks/useGlobalShortcut"; 7 | 8 | export function App() { 9 | useExitOnClose(); 10 | useBakgroundColor(); 11 | useGlobalShortcut(); 12 | 13 | return ( 14 |
15 | 16 | 17 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /src/app.module.css: -------------------------------------------------------------------------------- 1 | .inputBoxLayout { 2 | height: 100%; 3 | background-color: var(--bg-color); 4 | position: relative; 5 | display: block; 6 | padding: 0 2.5%; 7 | font-size: xx-large; 8 | } 9 | 10 | .inputBoxLayout>input { 11 | height: 42px; 12 | } 13 | -------------------------------------------------------------------------------- /src/app/calculator/domain/Calculator.ts: -------------------------------------------------------------------------------- 1 | import { CalculatorSettings } from "./CalculatorSettings"; 2 | 3 | export abstract class Calculator { 4 | constructor(readonly settings: CalculatorSettings) { 5 | } 6 | 7 | abstract calculate(input: string): string 8 | } 9 | -------------------------------------------------------------------------------- /src/app/calculator/domain/CalculatorSettings.ts: -------------------------------------------------------------------------------- 1 | export interface CalculatorSettings { 2 | useBigNumbers: boolean; 3 | notation: "auto" | "fixed" | "exponential" | "engineering" 4 | } 5 | -------------------------------------------------------------------------------- /src/app/calculator/infrastructure/MathJsCalculator.ts: -------------------------------------------------------------------------------- 1 | import { create, all, typeOf, format } from "mathjs" 2 | import { CalculatorSettings } from "../domain/CalculatorSettings"; 3 | import { Calculator } from "../domain/Calculator"; 4 | 5 | export class MathJsCalculator implements Calculator { 6 | private readonly mathJsEngine = create(all); 7 | 8 | constructor(readonly settings: CalculatorSettings) { 9 | this.mathJsEngine.config({ 10 | number: this.settings.useBigNumbers ? 'BigNumber' : 'number' 11 | }); 12 | } 13 | 14 | calculate(input: string) { 15 | try { 16 | if (input === "") return ""; 17 | 18 | const res = this.mathJsEngine.evaluate(input); 19 | 20 | const { notation } = this.settings 21 | 22 | if (typeOf(res) === "number") return format(res, { notation, precision: notation !== "fixed" ? 14 : undefined }); 23 | 24 | // Big Numbeers precision is set to 64 by default (enough for 99.99999999999999% of cases) 25 | if (typeOf(res) === "BigNumber") return format(res, { notation }); 26 | 27 | if (typeOf(res) === "Complex") return String(res); 28 | if (typeOf(res) === "Unit") return String(res); 29 | 30 | throw new Error("Cannot evaluate object"); 31 | } catch { 32 | return ""; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/components/ConsoleInput.tsx: -------------------------------------------------------------------------------- 1 | import { useCalculatorStore } from "@state/calculator"; 2 | import { useHistory } from "../hooks/useHistory"; 3 | import styles from "./console.module.css"; 4 | import { InputPlaceholder } from "./InputPlaceholder"; 5 | 6 | export const ConsoleInput = () => { 7 | const { getHistoryActionResult, isHistoryAction } = useHistory(); 8 | 9 | const input = useCalculatorStore((s) => s.input); 10 | const setInput = useCalculatorStore((s) => s.setInput); 11 | 12 | const handleChange = (e: React.ChangeEvent) => { 13 | setInput(e.target.value); 14 | }; 15 | 16 | const handleKeyDown = (e: React.KeyboardEvent) => { 17 | if (isHistoryAction(e)) { 18 | const newInput = getHistoryActionResult(e, input); 19 | if (typeof newInput === "string") { 20 | setInput(newInput); 21 | } 22 | } 23 | }; 24 | 25 | return ( 26 | <> 27 | {!input && } 28 | 36 | 37 | ); 38 | }; 39 | -------------------------------------------------------------------------------- /src/components/ConsoleResult.tsx: -------------------------------------------------------------------------------- 1 | import { useCalculatorStore } from "@state/calculator"; 2 | import styles from "./console.module.css"; 3 | import { useCopyToClipboardSubscription } from "../hooks/useCopyToClipboardSubscription"; 4 | import { shouldDisplayInSubconsole, SubConsoleResult } from "./SubConsoleResult"; 5 | import { useCalculator } from "../hooks/useCalculator"; 6 | import { useEffect } from "react"; 7 | 8 | const calculateConsoleResult = (input: string, result: any) => { 9 | return " ".repeat(input.length) + " = " + result; 10 | }; 11 | 12 | export const ConsoleResult = () => { 13 | const input = useCalculatorStore((s) => s.input); 14 | const result = useCalculatorStore((s) => s.result); 15 | const setResult = useCalculatorStore((s) => s.setResult); 16 | 17 | const { calculate } = useCalculator(); 18 | 19 | useCopyToClipboardSubscription(); 20 | 21 | useEffect(() => { 22 | const result = calculate(input); 23 | setResult(result); 24 | 25 | }, [input]) 26 | 27 | if (!result) return null; 28 | 29 | if (shouldDisplayInSubconsole(input, String(result))) { 30 | return 31 | } 32 | 33 | const value = calculateConsoleResult(input, result); 34 | return ; 35 | }; 36 | -------------------------------------------------------------------------------- /src/components/InputPlaceholder.tsx: -------------------------------------------------------------------------------- 1 | import Textra from "react-textra"; 2 | import styles from "./console.module.css"; 3 | 4 | const dateAsSum = new Date().toLocaleDateString(); 5 | 6 | const placeHolderContents = [ 7 | dateAsSum, 8 | "alt + q = quit", 9 | "alt + s = settings", 10 | "alt + m = toggle visibility", 11 | "enter = copy to clipboard", 12 | "tab = select all", 13 | ]; 14 | 15 | export const InputPlaceholder = () => { 16 | return ( 17 | 23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /src/components/Settings/BGColor.tsx: -------------------------------------------------------------------------------- 1 | import { HexColorPicker } from "react-colorful"; 2 | import "./bgcolor.css"; 3 | import { useSettingsStore } from "@state/settings"; 4 | 5 | export const BGColor = () => { 6 | const backgroundColor = useSettingsStore((s) => s.backgroundColor); 7 | const setBackgroundColor = useSettingsStore((s) => s.setBackgrounColor); 8 | 9 | const handlePickerChange = (color: string) => { 10 | setBackgroundColor(color); 11 | }; 12 | 13 | return ( 14 | 18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /src/components/Settings/Checkbox.tsx: -------------------------------------------------------------------------------- 1 | import ReactSwitch from "react-switch"; 2 | 3 | type CheckboxProps = { 4 | checked: boolean; 5 | onChange: (newValue: boolean) => any; 6 | className?: string; 7 | }; 8 | 9 | export const Checkbox = (props: CheckboxProps) => { 10 | const { checked, onChange, className } = props; 11 | 12 | const handleChange = (newValue: boolean) => { 13 | onChange(newValue); 14 | }; 15 | 16 | const handleKeyDown = (event: React.KeyboardEvent) => { 17 | if (event.key === "Enter") { 18 | onChange(!checked); 19 | } 20 | }; 21 | 22 | return ( 23 | 32 | ); 33 | }; 34 | -------------------------------------------------------------------------------- /src/components/Settings/FormatSelector.tsx: -------------------------------------------------------------------------------- 1 | import { useSettingsStore } from "@state/settings"; 2 | import { FormatOptions } from "mathjs" 3 | import { ChangeEventHandler } from "react"; 4 | import styles from "./index.module.css"; 5 | 6 | const AVAILABLE_NOTATIONS = ["fixed", "exponential", "engineering", undefined] satisfies FormatOptions["notation"][]; 7 | 8 | export const FormatSelector = () => { 9 | const notation = useSettingsStore(s => s.notation); 10 | const setNotation = useSettingsStore(s => s.setNotation); 11 | 12 | const handleChange: ChangeEventHandler = (e) => { 13 | const newValue = e.target.value as typeof AVAILABLE_NOTATIONS[number]; 14 | setNotation(newValue || "auto"); 15 | } 16 | 17 | return ( 18 |
19 | 38 |
39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /src/components/Settings/HideOnEnter.tsx: -------------------------------------------------------------------------------- 1 | import styles from "./index.module.css"; 2 | import { Checkbox } from "./Checkbox"; 3 | import { useSettingsStore } from "@state/settings"; 4 | 5 | export const HideOnEnter = () => { 6 | const hideOnEnter = useSettingsStore(s => s.hideOnEnter); 7 | const setHideOnEnter = useSettingsStore(s => s.setHideOnEnter); 8 | 9 | const handleChange = (newValue: boolean) => { 10 | setHideOnEnter(newValue); 11 | }; 12 | 13 | return ( 14 | 19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /src/components/Settings/RunOnStart.tsx: -------------------------------------------------------------------------------- 1 | import styles from "./index.module.css"; 2 | import { Checkbox } from "./Checkbox"; 3 | import { useSettingsStore } from "@state/settings"; 4 | 5 | export const RunOnStart = () => { 6 | const runOnWindowsStart = useSettingsStore(s => s.runOnWindowsStart); 7 | const setRunOnStart = useSettingsStore(s => s.setRunOnWindowsStart); 8 | 9 | const handleChange = (newValue: boolean) => { 10 | setRunOnStart(newValue); 11 | }; 12 | 13 | return ( 14 | 19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /src/components/Settings/UseBigNumbers.tsx: -------------------------------------------------------------------------------- 1 | import styles from "./index.module.css"; 2 | import { Checkbox } from "./Checkbox"; 3 | import { useSettingsStore } from "@state/settings"; 4 | 5 | export const UseBigNumbers = () => { 6 | const useBigNumbers = useSettingsStore(s => s.useBigNumbers); 7 | const setUseBigNumbers = useSettingsStore(s => s.setUseBigNumbers); 8 | 9 | const handleChange = (newValue: boolean) => { 10 | setUseBigNumbers(newValue); 11 | }; 12 | 13 | return ( 14 | 19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /src/components/Settings/bgcolor.css: -------------------------------------------------------------------------------- 1 | .react-colorful { 2 | height: 95px !important; 3 | width: 30% !important; 4 | } 5 | 6 | .react-colorful__saturation { 7 | height: 80px !important; 8 | border-radius: 0px 10px 0px 0px !important; 9 | } 10 | 11 | .react-colorful__hue { 12 | height: 15px !important; 13 | border-radius: 0px 0px 10px 0px !important; 14 | } 15 | 16 | .react-colorful__pointer { 17 | border: 2px solid white !important; 18 | width: 15px !important; 19 | height: 15px !important; 20 | } 21 | -------------------------------------------------------------------------------- /src/components/Settings/index.module.css: -------------------------------------------------------------------------------- 1 | .settingsContainer { 2 | color: whitesmoke; 3 | display: flex; 4 | flex-direction: column; 5 | align-items: center; 6 | justify-content: center; 7 | height: 100%; 8 | width: 95%; 9 | margin: 0 auto; 10 | gap: 1em; 11 | padding-bottom: 25px; 12 | } 13 | 14 | .settingItem { 15 | background-color: #292e33; 16 | border-radius: 2px; 17 | width: 100%; 18 | display: inline-flex; 19 | justify-content: space-between; 20 | align-items: center; 21 | box-shadow: 0 0 5px 0 rgba(16, 15, 15, 0.206); 22 | } 23 | 24 | .settingTextZone { 25 | padding: 1em; 26 | display: flex; 27 | flex-direction: column; 28 | justify-content: center; 29 | gap: .2em; 30 | } 31 | 32 | .settingTitle { 33 | font-size: large; 34 | font-weight: bold; 35 | } 36 | 37 | .settingDescription { 38 | font-size: small; 39 | font-weight: normal; 40 | } 41 | 42 | .settingValue { 43 | padding: 0 1.5em; 44 | font-size: 1em; 45 | font-weight: bold; 46 | } 47 | 48 | .buttonarea { 49 | display: flex; 50 | flex-direction: row; 51 | justify-content: space-between; 52 | align-items: center; 53 | width: 60%; 54 | gap: 1em; 55 | } 56 | 57 | .save { 58 | margin: 0 auto; 59 | font-size: 1em; 60 | font-weight: bold; 61 | color: white; 62 | } 63 | 64 | .reset { 65 | margin: 0 auto; 66 | font-size: 1em; 67 | font-weight: bold; 68 | color: white; 69 | } 70 | 71 | button { 72 | background-color: #39434c; 73 | border: 1px solid #46525d; 74 | border-radius: 2px; 75 | padding: .5em 1em; 76 | cursor: pointer; 77 | } 78 | 79 | button:hover { 80 | background-color: #495662; 81 | } 82 | 83 | button:focus { 84 | background-color: #495662; 85 | outline-color: rgb(36, 37, 39); 86 | } 87 | 88 | .selectFormatOptions { 89 | padding: 0.5rem; 90 | border-radius: 0.5rem; 91 | border: 1px solid #ccc; 92 | outline: none; 93 | font-size: 1rem; 94 | background: "#ddd", 95 | } 96 | 97 | .selectFormatOptions:focus { 98 | border-color: #aaa; 99 | box-shadow: 0 0 1px 3px rgba(59, 153, 252, 0.7); 100 | box-shadow: 0 0 0 3px -moz-mac-focusring; 101 | color: #222; 102 | outline: none; 103 | } 104 | -------------------------------------------------------------------------------- /src/components/Settings/index.tsx: -------------------------------------------------------------------------------- 1 | import styles from "./index.module.css"; 2 | import { useSettingsStore } from "@state/settings"; 3 | import { BGColor } from "./BGColor"; 4 | import { RunOnStart } from "./RunOnStart"; 5 | import { UseBigNumbers } from "./UseBigNumbers"; 6 | import { FormatSelector } from "./FormatSelector"; 7 | import { HideOnEnter } from "./HideOnEnter"; 8 | 9 | const SETTINGS = [ 10 | { 11 | name: "Use Big Numbers", 12 | description: 13 | "Add support for big numbers (numbers with more than 16 digits). This will make the app slower, so it's recommended to turn it off if you don't need it.", 14 | SettingElement: UseBigNumbers, 15 | }, 16 | 17 | { 18 | name: "Output Format", 19 | description: `Auto (Default): RunMath will try to adjust the output for you. 20 | Fixed: fixed notation. 21 | Exponential: Scientific notation. 22 | Engineering: Scientific notation with multiples of 3.`, 23 | SettingElement: FormatSelector, 24 | }, 25 | 26 | { 27 | name: "Background Color", 28 | description: "Change the background color of the app", 29 | SettingElement: BGColor, 30 | }, 31 | { 32 | name: "Hide RunMath on enter", 33 | description: "After the result is calculated, RunMath will copy the result to the clipboard and hide the window.", 34 | SettingElement: HideOnEnter, 35 | }, 36 | { 37 | name: "Start RunMath on Windows Start", 38 | description: 39 | "RunMath will start automatically when you turn on your computer", 40 | SettingElement: RunOnStart, 41 | }, 42 | ] as const; 43 | 44 | export const Settings = () => { 45 | const resetSettings = useSettingsStore((s) => s.reset); 46 | 47 | const handleResetSettings = () => { 48 | resetSettings(); 49 | }; 50 | 51 | const handleExitSettings = () => { 52 | import("@tauri-apps/api/window").then((module) => 53 | module.getCurrentWindow().close() 54 | ); 55 | }; 56 | 57 | return ( 58 |
59 |

⚙️ RunMath Settings

60 | 61 | {SETTINGS.map(({ SettingElement, description, name }) => ( 62 | 70 | ))} 71 | 72 |
73 | 76 | 79 |
80 |
81 | ); 82 | }; 83 | -------------------------------------------------------------------------------- /src/components/SubConsoleResult.tsx: -------------------------------------------------------------------------------- 1 | import { useBiggerScreen } from "../hooks/useBiggerScreen"; 2 | import styles from "./console.module.css"; 3 | 4 | export const shouldDisplayInSubconsole = (input: string, result: string) => { 5 | return input.length + 3 + result.length > 34; 6 | }; 7 | 8 | export const SubConsoleResult = ({ value }: { value: string }) => { 9 | useBiggerScreen() 10 | return 15 | } 16 | -------------------------------------------------------------------------------- /src/components/console.module.css: -------------------------------------------------------------------------------- 1 | .placeholder { 2 | height: 100%; 3 | line-height: 42px; 4 | color: cornflowerblue; 5 | user-select: none; 6 | position: absolute; 7 | } 8 | 9 | .consoleInput { 10 | height: 100%; 11 | font-size: xx-large; 12 | position: relative; 13 | display: block; 14 | width: 100%; 15 | outline: none; 16 | border: none; 17 | z-index: 1; 18 | caret-color: transparent; 19 | } 20 | 21 | .consoleInput:focus-visible { 22 | caret-color: black; 23 | } 24 | 25 | .consoleResult { 26 | height: 100%; 27 | font-size: xx-large; 28 | position: absolute; 29 | width: 95%; 30 | z-index: 0; 31 | color: rgba(39, 39, 39, 0.525); 32 | top: 0; 33 | left: 2.5%; 34 | } 35 | 36 | .subconsoleResult { 37 | height: 100%; 38 | font-size: xx-large; 39 | width: 100%; 40 | color: rgba(39, 39, 39, 0.525); 41 | background: inherit; 42 | } 43 | -------------------------------------------------------------------------------- /src/hooks/useBackgroundColor.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { useSettingsStore } from "@state/settings"; 3 | 4 | export const useBakgroundColor = () => { 5 | const backgroundColor = useSettingsStore(s => s.backgroundColor); 6 | 7 | useEffect(() => { 8 | document.body.style.setProperty("--bg-color", backgroundColor); 9 | }, [backgroundColor]) 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/hooks/useBiggerScreen.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react" 2 | import { getCurrentWindow, LogicalSize } from "@tauri-apps/api/window" 3 | 4 | export const useBiggerScreen = () => { 5 | useEffect(() => { 6 | getCurrentWindow().setSize(new LogicalSize(700, 88)) 7 | 8 | return () => { 9 | getCurrentWindow().setSize(new LogicalSize(700, 46)) 10 | } 11 | }, []) 12 | } 13 | -------------------------------------------------------------------------------- /src/hooks/useCalculator.ts: -------------------------------------------------------------------------------- 1 | import { useSettingsStore } from "@state/settings"; 2 | import { MathJsCalculator } from "../app/calculator/infrastructure/MathJsCalculator"; 3 | import { useMemo } from "react"; 4 | 5 | export const useCalculator = () => { 6 | const settings = useSettingsStore() 7 | 8 | const calculator = useMemo(() => { 9 | return new MathJsCalculator({ 10 | useBigNumbers: settings.useBigNumbers, 11 | notation: settings.notation 12 | }); 13 | }, [settings.useBigNumbers, settings.notation]); 14 | 15 | const calculate = (input: string) => { 16 | return calculator.calculate(input); 17 | }; 18 | 19 | return { calculate }; 20 | } 21 | -------------------------------------------------------------------------------- /src/hooks/useCopyToClipboardSubscription.ts: -------------------------------------------------------------------------------- 1 | import { useCalculatorStore } from "@state/calculator"; 2 | import { useSettingsStore } from "@state/settings"; 3 | import { getCurrentWindow } from "@tauri-apps/api/window"; 4 | import { writeText } from "@tauri-apps/plugin-clipboard-manager"; 5 | import { useEffect } from "react"; 6 | 7 | export const useCopyToClipboardSubscription = () => { 8 | const result = useCalculatorStore(s => s.result) 9 | const setInput = useCalculatorStore(s => s.setInput); 10 | const hideOnEnter = useSettingsStore(s => s.hideOnEnter) 11 | 12 | useEffect(() => { 13 | function copyToClipboard() { 14 | if (result) { 15 | writeText(String(result)); 16 | 17 | if (hideOnEnter) { 18 | getCurrentWindow().hide(); 19 | } 20 | } 21 | 22 | setInput(""); 23 | } 24 | 25 | window.addEventListener("copy-to-clipboard", copyToClipboard); 26 | 27 | return () => { 28 | window.removeEventListener("copy-to-clipboard", copyToClipboard); 29 | }; 30 | }, [result]); 31 | }; 32 | -------------------------------------------------------------------------------- /src/hooks/useExitOnClose.ts: -------------------------------------------------------------------------------- 1 | import { getCurrentWindow } from "@tauri-apps/api/window"; 2 | import { exit } from "@tauri-apps/plugin-process"; 3 | 4 | /** 5 | * Configures the main window to exit the app when closed. 6 | * When the main window is closed, the app will exit. (The settings window will be forced to close) 7 | */ 8 | export const useExitOnClose = () => { 9 | const appWindow = getCurrentWindow(); 10 | 11 | appWindow.onCloseRequested(() => { 12 | appWindow.close(); 13 | exit(); 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /src/hooks/useGlobalShortcut.ts: -------------------------------------------------------------------------------- 1 | import { register, unregisterAll } from "@tauri-apps/plugin-global-shortcut" 2 | import { toggleWindowVisibility } from "@utils/toggleWindowView" 3 | import { useEffect } from "react" 4 | 5 | const registerGlobalShortcut = async () => { 6 | await unregisterAll() 7 | 8 | await register("Alt+m", (s) => { 9 | if (s.state === "Released") toggleWindowVisibility() 10 | }) 11 | } 12 | 13 | export const useGlobalShortcut = () => { 14 | useEffect(() => { 15 | registerGlobalShortcut() 16 | }, []) 17 | } 18 | -------------------------------------------------------------------------------- /src/hooks/useHistory.ts: -------------------------------------------------------------------------------- 1 | import { PointerStack } from "@dubisdev/pointer-stack-structure"; 2 | import { useRef } from "react"; 3 | 4 | export const useHistory = () => { 5 | const { current: history } = useRef(new PointerStack([""])); 6 | 7 | const getHistoryActionResult = ( 8 | e: React.KeyboardEvent, 9 | input: string 10 | ) => { 11 | const currentIsLastElement = history.getPointer() === history.size - 1; 12 | // Save input when user presses enter or = 13 | if ((e.key === "Enter" || e.key === "=") && input) { 14 | console.log(input); 15 | history.push(input); 16 | return null; 17 | } 18 | 19 | // Clear input when user presses escape, checks if input is empty to avoid rerender 20 | if (e.key === "Escape") { 21 | return input && ""; 22 | } 23 | 24 | if (e.key === "ArrowUp") { 25 | if (!input && currentIsLastElement) { 26 | return history.peek(); 27 | } 28 | return history.getPrev() || ""; 29 | } 30 | 31 | if (e.key === "ArrowDown") { 32 | return history.getNext() || ""; 33 | } 34 | 35 | return null; 36 | }; 37 | 38 | const isHistoryAction = (e: React.KeyboardEvent) => { 39 | return ["Enter", "Escape", "ArrowUp", "ArrowDown", "="].includes(e.key); 40 | }; 41 | 42 | return { getHistoryActionResult, isHistoryAction }; 43 | }; 44 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @import "the-new-css-reset"; 2 | 3 | body { 4 | height: 100vh; 5 | overflow: hidden; 6 | margin: 0; 7 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 8 | monospace; 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | --bg-color: rgb(163, 200, 255); 12 | } 13 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import { App } from "./App"; 4 | import { getMatches } from "@tauri-apps/plugin-cli"; 5 | import { getCurrentWindow } from "@tauri-apps/api/window"; 6 | import "./index.css"; 7 | 8 | // Start hidden if the arg is present (e.g. when the auto-launcher starts the app) 9 | getMatches().then((matches) => { 10 | const shouldHide = matches.args["start-hidden"].value 11 | if (shouldHide) { 12 | getCurrentWindow().hide(); 13 | } 14 | }) 15 | 16 | ReactDOM.createRoot(document.getElementById("root")!).render( 17 | 18 | 19 | 20 | ); 21 | 22 | import("@utils/configureShortcuts").then((module) => 23 | module.configureShortcuts() 24 | ); 25 | import("@utils/singleInstance").then((module) => 26 | module.configureSingleInstance() 27 | ); 28 | import("@utils/updateNotifier").then((module) => module.checkForUpdates()); 29 | -------------------------------------------------------------------------------- /src/main_settings.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { createRoot } from "react-dom/client"; 3 | import { Settings } from "@components/Settings"; 4 | 5 | const root = createRoot(document.getElementById("root")!) 6 | root.render( 7 | 8 | 9 | 10 | ); 11 | -------------------------------------------------------------------------------- /src/state/calculator.ts: -------------------------------------------------------------------------------- 1 | import { create } from "zustand"; 2 | 3 | interface BasicCalculatorState { 4 | input: string; 5 | setInput: (input: string) => void; 6 | 7 | result: string | null; 8 | setResult: (result: string | null) => void; 9 | } 10 | 11 | export const useCalculatorStore = create((set) => ({ 12 | input: "", 13 | result: "", 14 | 15 | setInput: (input) => { 16 | set({ input }); 17 | }, 18 | 19 | setResult: (result) => { 20 | set({ result }); 21 | }, 22 | 23 | })); 24 | -------------------------------------------------------------------------------- /src/state/settings.ts: -------------------------------------------------------------------------------- 1 | import { create } from "zustand"; 2 | import { persist, subscribeWithSelector } from "zustand/middleware"; 3 | import { share } from "shared-zustand"; 4 | import { configureRunOnStart } from "@utils/configureRunOnStart"; 5 | import { useCalculatorStore } from "./calculator"; 6 | import { getCurrentWindow } from "@tauri-apps/api/window"; 7 | import { CalculatorSettings } from "../app/calculator/domain/CalculatorSettings"; 8 | 9 | type RunmathSettings = CalculatorSettings & { 10 | backgroundColor: string; 11 | runOnWindowsStart: boolean; 12 | hideOnEnter: boolean; 13 | } 14 | 15 | const defaultSettings: Readonly = Object.freeze({ 16 | backgroundColor: "#a3c8ff", 17 | runOnWindowsStart: false, 18 | useBigNumbers: false, 19 | notation: "auto", 20 | hideOnEnter: false 21 | }) 22 | 23 | type SettingsState = RunmathSettings & { 24 | setBackgrounColor: (color: RunmathSettings["backgroundColor"]) => void; 25 | setRunOnWindowsStart: (runOnWindowsStart: RunmathSettings["runOnWindowsStart"]) => void; 26 | setUseBigNumbers: (useBigNumbers: RunmathSettings["useBigNumbers"]) => void; 27 | setNotation: (notation: RunmathSettings["notation"]) => void; 28 | setHideOnEnter: (hideOnEnter: RunmathSettings["hideOnEnter"]) => void; 29 | reset: () => void; 30 | } 31 | 32 | const useSettingsStore = create()( 33 | // use suscription to sync tabs (calculator & settings) 34 | subscribeWithSelector( 35 | // use persist to save settings in localStorage 36 | persist( 37 | (set) => ({ 38 | ...defaultSettings, 39 | setBackgrounColor: (backgroundColor) => set({ backgroundColor }), 40 | 41 | setRunOnWindowsStart: (runOnWindowsStart) => { 42 | configureRunOnStart(runOnWindowsStart); 43 | set({ runOnWindowsStart }) 44 | }, 45 | 46 | setUseBigNumbers: (useBigNumbers) => set({ useBigNumbers }), 47 | 48 | setNotation: (notation) => set({ notation }), 49 | 50 | setHideOnEnter: (hideOnEnter) => set({ hideOnEnter }), 51 | 52 | reset: () => { 53 | configureRunOnStart(defaultSettings.runOnWindowsStart); 54 | set(defaultSettings) 55 | } 56 | }), 57 | 58 | { 59 | name: "runmath-settings", 60 | version: 1, 61 | migrate(persistedState, version) { 62 | if (version === 1) { 63 | (persistedState as RunmathSettings).hideOnEnter = false; 64 | 65 | return persistedState; 66 | } 67 | }, 68 | } 69 | ) 70 | ) 71 | ); 72 | 73 | if ("BroadcastChannel" in globalThis) { 74 | for (const key in defaultSettings) { 75 | share(key as keyof RunmathSettings, useSettingsStore); 76 | } 77 | } 78 | 79 | // Force re-render to calculate when notation or useBigNumbers changes 80 | if (getCurrentWindow().label === "main") { 81 | useSettingsStore.subscribe( 82 | async (newState, prevState) => { 83 | const isNewNotation = newState.notation !== prevState.notation; 84 | const iseDifferentBigNumbers = newState.useBigNumbers !== prevState.useBigNumbers; 85 | 86 | if (isNewNotation || iseDifferentBigNumbers) { 87 | const currentInput = useCalculatorStore.getState().input; 88 | 89 | useCalculatorStore.setState({ input: "" }) 90 | await new Promise((resolve) => setTimeout(resolve, 10)); 91 | useCalculatorStore.setState({ input: currentInput + "" }); 92 | } 93 | } 94 | ) 95 | } 96 | 97 | 98 | export { useSettingsStore } 99 | -------------------------------------------------------------------------------- /src/utils/configureRunOnStart.ts: -------------------------------------------------------------------------------- 1 | import { disable, enable, isEnabled } from "@tauri-apps/plugin-autostart"; 2 | 3 | export const configureRunOnStart = async (runOnStart: boolean) => { 4 | if (runOnStart) { 5 | if (await isEnabled()) return; 6 | await enable(); 7 | } else { 8 | if (!(await isEnabled())) return; 9 | await disable(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/configureShortcuts.ts: -------------------------------------------------------------------------------- 1 | import { listen } from "@tauri-apps/api/event"; 2 | import { exit } from "@tauri-apps/plugin-process"; 3 | import { createSettingsPage } from "./createSettingsPage"; 4 | 5 | export const configureShortcuts = async () => { 6 | listen("open-settings", () => { 7 | createSettingsPage(); 8 | }) 9 | 10 | // register shortcuts when the window is focused 11 | window.addEventListener("keydown", async (e) => { 12 | if (isExitCommand(e)) { 13 | await exit(); 14 | } 15 | 16 | if (isCopyCommand(e)) { 17 | window.dispatchEvent(new Event("copy-to-clipboard")); 18 | } 19 | 20 | if (isSettingsCommand(e)) { 21 | createSettingsPage(); 22 | } 23 | 24 | }); 25 | 26 | // prevent context menu from opening 27 | window.addEventListener("contextmenu", (e) => { 28 | e.preventDefault(); 29 | }); 30 | }; 31 | 32 | const isExitCommand = (e: KeyboardEvent) => e.key === "q" && e.altKey; 33 | const isCopyCommand = (e: KeyboardEvent) => e.key === "Enter"; 34 | const isSettingsCommand = (e: KeyboardEvent) => e.key === "s" && e.altKey; 35 | -------------------------------------------------------------------------------- /src/utils/createSettingsPage.ts: -------------------------------------------------------------------------------- 1 | import { WebviewWindow } from "@tauri-apps/api/webviewWindow"; 2 | 3 | const SETTINGS_PAGE_LABEL = "settings-page"; 4 | 5 | export const createSettingsPage = () => { 6 | const settingsPage = new WebviewWindow(SETTINGS_PAGE_LABEL, { 7 | url: "./settings.html", 8 | alwaysOnTop: true, 9 | height: 600, 10 | resizable: false, 11 | title: "RunMath Settings", 12 | visible: false, 13 | width: 700, 14 | }); 15 | 16 | settingsPage.once("tauri://created", () => { 17 | settingsPage.show(); 18 | }); 19 | 20 | settingsPage.once<"tauri://error">("tauri://error", async (e) => { 21 | if (e.payload.includes(` \`${SETTINGS_PAGE_LABEL}\` already exists`)) { 22 | await settingsPage.unminimize(); 23 | await settingsPage.show(); 24 | await settingsPage.setFocus(); 25 | return; 26 | } 27 | console.error(e); 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /src/utils/singleInstance.ts: -------------------------------------------------------------------------------- 1 | import { listen } from "@tauri-apps/api/event"; 2 | import { getCurrentWindow } from "@tauri-apps/api/window"; 3 | 4 | export const configureSingleInstance = async () => { 5 | await listen("single-instance", async () => { 6 | const appWindow = getCurrentWindow(); 7 | await appWindow.show(); 8 | await appWindow.unminimize(); 9 | await appWindow.setFocus(); 10 | }); 11 | }; 12 | -------------------------------------------------------------------------------- /src/utils/toggleWindowView.ts: -------------------------------------------------------------------------------- 1 | import { getCurrentWindow } from "@tauri-apps/api/window"; 2 | 3 | export const toggleWindowVisibility = async () => { 4 | const appWindow = getCurrentWindow(); 5 | 6 | if (await appWindow.isVisible()) { 7 | await appWindow.hide(); 8 | } else { 9 | await appWindow.show(); 10 | await appWindow.setFocus(); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/utils/updateNotifier.ts: -------------------------------------------------------------------------------- 1 | import { check } from "@tauri-apps/plugin-updater" 2 | import { relaunch } from "@tauri-apps/plugin-process"; 3 | import { ask } from "@tauri-apps/plugin-dialog" 4 | 5 | export const checkForUpdates = async () => { 6 | const update = await check(); 7 | 8 | if (!update?.available) return; 9 | 10 | const response = await ask("Would you like to install it now?", { 11 | title: "New RunMath version available (v" + update.version + ")", 12 | kind: "info" 13 | }) 14 | 15 | if (!response) return; 16 | 17 | await update.downloadAndInstall() 18 | await relaunch(); 19 | } 20 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": [ 6 | "DOM", 7 | "DOM.Iterable", 8 | "ESNext" 9 | ], 10 | "allowJs": false, 11 | "skipLibCheck": true, 12 | "esModuleInterop": false, 13 | "allowSyntheticDefaultImports": true, 14 | "strict": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "module": "ESNext", 17 | "moduleResolution": "Bundler", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx", 22 | "paths": { 23 | "@components/*": [ 24 | "./src/components/*" 25 | ], 26 | "@utils/*": [ 27 | "./src/utils/*" 28 | ], 29 | "@state/*": [ 30 | "./src/state/*" 31 | ] 32 | } 33 | }, 34 | "include": [ 35 | "src" 36 | ], 37 | "references": [ 38 | { 39 | "path": "./tsconfig.node.json" 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "esnext", 5 | "moduleResolution": "Bundler" 6 | }, 7 | "include": [ 8 | "vite.config.ts" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import { resolve } from "path"; 3 | import react from "@vitejs/plugin-react"; 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | plugins: [react()], 8 | build: { 9 | target: "esnext", 10 | rollupOptions: { 11 | input: { 12 | main: resolve(__dirname, "index.html"), 13 | settings: resolve(__dirname, "settings.html"), 14 | }, 15 | }, 16 | }, 17 | resolve: { 18 | alias: [ 19 | { 20 | find: "@components", 21 | replacement: resolve(__dirname, "src", "components"), 22 | }, 23 | { find: "@utils", replacement: resolve(__dirname, "src", "utils") }, 24 | { find: "@state", replacement: resolve(__dirname, "src", "state") }, 25 | ], 26 | }, 27 | server: { 28 | port: 3000, 29 | }, 30 | }); 31 | --------------------------------------------------------------------------------