├── .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 |
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 |
27 |
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 |
42 |
43 |
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 |
--------------------------------------------------------------------------------