├── .eslintrc.json ├── .github ├── FUNDING.yml ├── scripts │ ├── prepareCMD.sh │ ├── setup-linux.sh │ ├── setup-mac.sh │ └── setup-windows.ps1 └── workflows │ ├── README.md │ └── build.yml ├── .gitignore ├── .prettierrc.json ├── .releaserc ├── .vscode ├── extensions.json └── settings.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── firmwareflasher.code-workspace ├── index.html ├── jsdoc.conf.json ├── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── postcss.config.cjs ├── public ├── audio │ ├── EyeTrackApp_Audio_compleated.wav │ ├── EyeTrackApp_Audio_notif.mp3 │ └── EyeTrackApp_Audio_start.wav ├── images │ ├── icons │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── browserconfig.xml │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon.ico │ │ ├── icon-128x128.png │ │ ├── icon-144x144.png │ │ ├── icon-152x152.png │ │ ├── icon-192x192.png │ │ ├── icon-384x384.png │ │ ├── icon-48x48.png │ │ ├── icon-512x512.png │ │ ├── icon-72x72.png │ │ ├── icon-96x96.png │ │ ├── maskable_icon_x128.png │ │ ├── maskable_icon_x192.png │ │ ├── maskable_icon_x384.png │ │ ├── maskable_icon_x48.png │ │ ├── maskable_icon_x512.png │ │ ├── maskable_icon_x72.png │ │ ├── maskable_icon_x96.png │ │ ├── mstile-150x150.png │ │ └── safari-pinned-tab.svg │ ├── logo.png │ ├── logo.svg │ ├── logo512x512.png │ ├── png │ │ └── logo.png │ └── svg │ │ ├── arrow.svg │ │ ├── eye.svg │ │ ├── gearSolid.svg │ │ ├── grip-solid.svg │ │ └── list-ul-solid.svg └── robots.txt ├── renovate.json ├── src-tauri ├── .cargo │ └── config.toml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── build.rs ├── crates │ ├── linux │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── macos │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ └── windows │ │ ├── Cargo.toml │ │ └── src │ │ └── lib.rs ├── 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 │ ├── icon.icns │ ├── icon.ico │ └── icon.png ├── rustfmt.toml ├── src │ ├── lib │ │ ├── api │ │ │ ├── .gitignore │ │ │ ├── Cargo.toml │ │ │ ├── index.ts │ │ │ ├── package.json │ │ │ ├── src │ │ │ │ └── lib.rs │ │ │ ├── tauri-plugin-request-client.ts │ │ │ └── tsconfig.json │ │ ├── esp │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ │ ├── api.rs │ │ │ │ ├── command.rs │ │ │ │ ├── lib.rs │ │ │ │ ├── manifest.rs │ │ │ │ └── state.rs │ │ └── util │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ ├── colors.rs │ │ │ ├── errors.rs │ │ │ ├── lib.rs │ │ │ ├── logger.rs │ │ │ └── prelude.rs │ ├── main.rs │ └── modules │ │ ├── menu.rs │ │ ├── mod.rs │ │ └── tauri_commands.rs └── tauri.conf.json ├── src ├── App.tsx ├── assets │ ├── favicon.ico │ └── js │ │ └── custom.js ├── common │ ├── theme.ts │ └── typography.ts ├── components │ ├── AppUpdater │ │ ├── index.tsx │ │ └── styles.css │ ├── Board │ │ ├── DefaultBoard │ │ │ └── index.tsx │ │ └── SelectBoard │ │ │ └── index.tsx │ ├── Buttons │ │ ├── Button │ │ │ └── index.tsx │ │ ├── Checkbox │ │ │ └── index.tsx │ │ ├── DropdownButton │ │ │ └── index.tsx │ │ └── SelectButton │ │ │ └── index.tsx │ ├── Checkbox │ │ └── index.tsx │ ├── DevTools │ │ └── index.tsx │ ├── Dropdown │ │ ├── Dropdown │ │ │ └── index.tsx │ │ ├── DropdownList │ │ │ └── index.tsx │ │ └── PortDropdown │ │ │ └── index.tsx │ ├── Footer │ │ └── index.tsx │ ├── Header │ │ └── index.tsx │ ├── Inputs │ │ ├── Input │ │ │ └── index.tsx │ │ ├── NetworkInput │ │ │ └── index.tsx │ │ └── PasswordInput │ │ │ └── index.tsx │ ├── Modal │ │ └── index.tsx │ ├── ModalHeader │ │ └── index.tsx │ ├── Notifications │ │ ├── CustomToast │ │ │ └── index.tsx │ │ └── index.tsx │ ├── ProgressBar │ │ ├── index.tsx │ │ └── styles.css │ ├── SelectNetwork │ │ └── index.tsx │ ├── Terminal │ │ ├── Firmware │ │ │ └── index.tsx │ │ ├── Step │ │ │ └── index.tsx │ │ └── TerminalHeader │ │ │ └── index.tsx │ ├── Titlebar │ │ └── index.tsx │ └── Typography │ │ └── index.tsx ├── containers │ ├── 404 │ │ └── [...404].tsx │ ├── FlashFirmware │ │ └── index.tsx │ ├── Header │ │ └── index.tsx │ ├── ManageBoard │ │ └── index.tsx │ ├── ManageNetwork │ │ └── index.tsx │ └── Modals │ │ ├── ApModeModalContainer.tsx │ │ ├── BeforeFlashingContainer.tsx │ │ ├── BeforeSelectBoardContainer.tsx │ │ ├── WifiModalcontainer.tsx │ │ └── index.tsx ├── esp │ └── api.ts ├── main.tsx ├── pages │ ├── BoardManagement │ │ └── index.tsx │ ├── Modals │ │ ├── ApModeModal.tsx │ │ ├── BeforeFlashingModal.tsx │ │ ├── BeforeSelectBoard.tsx │ │ └── WifiModal.tsx │ ├── NetworkManagement │ │ └── index.tsx │ ├── Terminal │ │ └── index.tsx │ └── VirtualList │ │ └── index.tsx ├── routes │ ├── Routes.tsx │ └── index.tsx ├── static │ ├── endpoints.ts │ ├── index.ts │ ├── types │ │ ├── enums.ts │ │ ├── index.ts │ │ ├── interfaces.ts │ │ ├── types.ts │ │ └── utils.ts │ └── ui │ │ └── logs.ts ├── store │ ├── context │ │ ├── api.tsx │ │ ├── app.tsx │ │ ├── main.tsx │ │ ├── notifications.tsx │ │ └── ui.tsx │ ├── tauriStore │ │ └── index.ts │ └── terminal │ │ ├── actions.ts │ │ ├── selectors.ts │ │ └── terminal.ts ├── styles │ ├── docs-imports.css │ ├── imports.css │ ├── index.css │ ├── scrollbar.css │ └── titlebar.css ├── utils │ └── index.ts ├── vite-env.d.ts └── windows │ └── docs │ ├── docs.tsx │ └── index.html ├── tailwind.config.cjs ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": [ 7 | "prettier", 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/recommended", 10 | "plugin:solid/typescript", 11 | "plugin:import/typescript", 12 | "plugin:import/recommended" 13 | ], 14 | "parser": "@typescript-eslint/parser", 15 | "parserOptions": { 16 | "ecmaVersion": "latest", 17 | "sourceType": "module", 18 | "ecmaFeatures": { 19 | "jsx": true 20 | } 21 | }, 22 | "plugins": ["@typescript-eslint", "solid", "import"], 23 | "rules": { 24 | "solid/reactivity": "warn", 25 | "solid/no-destructure": "warn", 26 | "solid/jsx-no-undef": "error", 27 | "indent": ["error", 4 , { "SwitchCase": 1 }], 28 | "quotes": ["error", "single"], 29 | "semi": ["error", "never"], 30 | "arrow-body-style": "off", 31 | "import/no-unresolved": "error", 32 | "import/extensions": [ 33 | "error", 34 | "ignorePackages", 35 | { 36 | "js": "never", 37 | "jsx": "never", 38 | "ts": "never", 39 | "tsx": "never" 40 | } 41 | ], 42 | "import/order": [ 43 | "error", 44 | { 45 | "groups": [ 46 | "builtin", 47 | "external", 48 | "parent", 49 | "sibling", 50 | "index", 51 | "object", 52 | "type" 53 | ], 54 | "pathGroups": [ 55 | { 56 | "pattern": "@/**/**", 57 | "group": "parent", 58 | "position": "before" 59 | } 60 | ], 61 | "alphabetize": { 62 | "order": "asc" 63 | } 64 | } 65 | ] 66 | }, 67 | "settings": { 68 | "import/resolver": { 69 | "typescript": "true", 70 | "node": "true" 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/scripts/prepareCMD.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # create a vairable to hold a passed in argument 4 | # this argument is the next release version 5 | # this is passed in from the .releaserc file 6 | 7 | sudo apt-get install -y jq 8 | 9 | nextReleaseVersion=$1 10 | TARGET_KEY="version" 11 | 12 | # parse all letters a-z and A-Z and replace with nothing 13 | # this will remove all letters from the version string 14 | # this is to ensure that the version string is a valid semver 15 | 16 | # check if there is a letter in the version string 17 | # if there is a letter, then remove it 18 | # if there is no letter, then do nothing 19 | if [[ $nextReleaseVersion =~ [a-zA-Z] ]]; then 20 | nextReleaseVersion=$(echo $nextReleaseVersion | sed 's/[a-zA-Z]//g') 21 | 22 | # check if there is a dash in the version string 23 | # if there is a dash, then replace it with a dot 24 | # if there is no dash, then do nothing 25 | if [[ $nextReleaseVersion =~ "-" ]]; then 26 | # parse all dashes and replace with dots 27 | # this is to ensure that the version string is a valid semver 28 | nextReleaseVersion=$(echo $nextReleaseVersion | sed 's/-/./g') 29 | 30 | # remove everything after the third dot and the dot itself 31 | # this is to ensure that the version string is a valid semver 32 | nextReleaseVersion=$(echo $nextReleaseVersion | sed 's/\.[0-9]*$//g') 33 | # remove the last dot 34 | nextReleaseVersion=$(echo $nextReleaseVersion | sed 's/\.$//g') 35 | fi 36 | fi 37 | 38 | # print the next release version 39 | 40 | printf "[prepareCMD.sh]: Next version: ${nextReleaseVersion}\n" 41 | 42 | # This script is used to execute the prepareCMD.sh script on the remote host 43 | printf "[prepareCMD.sh]: Executing prepareCMD.sh on remote host \n" 44 | 45 | printf "[prepareCMD.sh]: Updating the version in the package.json file \n" 46 | 47 | # make a temp file 48 | tmp=$(mktemp) 49 | 50 | jq --arg a "$nextReleaseVersion" '.version = $a' ./package.json > "$tmp" && mv "$tmp" ./package.json -f 51 | 52 | printf "[prepareCMD.sh]: Done \n" 53 | 54 | printf "[prepareCMD.sh]: Updating the version in the tauri.conf.json file \n" 55 | 56 | jq --arg a "$nextReleaseVersion" '.package.version = $a' ./src-tauri/tauri.conf.json > "$tmp" && mv "$tmp" ./src-tauri/tauri.conf.json -f 57 | printf "[prepareCMD.sh]: Done \n" 58 | 59 | #printf "Update the version in the Cargo.toml file \n" 60 | # 61 | #sed -i "s/version = \"[0-9\\.]*\"/version = \"${nextReleaseVersion}\"/g" ./src-tauri/Cargo.toml 62 | 63 | # Install the dependencies for toml file 64 | printf "[prepareCMD.sh]: Installing the dependencies for the toml file \n" 65 | 66 | pip3 install yq 67 | 68 | export PATH="~/.local/bin:$PATH" 69 | source ~/.bashrc 70 | 71 | tmp=$(mktemp) 72 | tomlq -t --arg version "$nextReleaseVersion" '.package.version |= $version' ./src-tauri/Cargo.toml > "$tmp" && mv "$tmp" ./src-tauri/Cargo.toml -f 73 | 74 | # validate the Cargo.toml file 75 | #printf "[prepareCMD.sh]: Validating the Cargo.toml file \n" 76 | #cat ./src-tauri/Cargo.toml 77 | 78 | printf "[prepareCMD.sh]: Done, continuing with release. \n" 79 | -------------------------------------------------------------------------------- /.github/scripts/setup-linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | printf "This Script will setup the tauri dev environment\n" 4 | 5 | # check if the user is running as root 6 | if [ "$EUID" -ne 0 ]; then 7 | printf "Please run as root\n" 8 | exit 1 9 | fi 10 | 11 | # get user input and store in variable 12 | 13 | printf "Do you want to install the Linux Development Dependencies? (y/n)\n" 14 | read -r input 15 | 16 | # check if the user input is y or n 17 | 18 | if [ "$input" == "y" ]; then 19 | printf "Installing Linux Development Dependencies\n" 20 | else 21 | printf "Not installing Linux Development Dependencies\n" 22 | fi 23 | 24 | # Get their distro 25 | 26 | printf "Which distro are you running? (ubuntu/arch/fedora/gentoo/opensuse/void)\n" 27 | 28 | read -r distro 29 | 30 | # convert distro to lowercase 31 | 32 | distro=${distro,,} 33 | 34 | # check if the distro is Ubuntu or arch or fedora or gentoo or openSUSE or NixOS or GNU GUIX or void 35 | 36 | if [ "$distro" == "ubuntu" ] || [ "$distro" == "arch" ] || [ "$distro" == "fedora" ] || [ "$distro" == "gentoo" ] || [ "$distro" == "opensuse" ] || [ "$distro" == "void" ]; then 37 | printf "Distro is $distro\n" 38 | else 39 | printf "Distro is not supported\n" 40 | printf "NixOS and GNU GUIX are not supported by this script, but are supported by tauri - please see the tauri docs for more information.\n" 41 | exit 1 42 | fi 43 | 44 | # Install Linux Development Dependencies 45 | # run specific commands based on the distro 46 | case $distro in 47 | "ubuntu") 48 | printf "Installing Ubuntu Development Dependencies\n" 49 | sudo apt update 50 | sudo apt install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev 51 | ;; 52 | "arch") 53 | printf "Installing Arch Development Dependencies\n" 54 | sudo pacman -Syu 55 | sudo pacman -S --needed webkit2gtk base-devel curl wget openssl appmenu-gtk-module gtk3 libappindicator-gtk3 librsvg libvips 56 | ;; 57 | "fedora") 58 | printf "Installing Fedora Development Dependencies\n" 59 | sudo dnf check-update 60 | sudo dnf install webkit2gtk4.0-devel openssl-devel curl wget libappindicator-gtk3 librsvg2-devel 61 | sudo dnf group install "C Development Tools and Libraries" 62 | ;; 63 | "gentoo") 64 | printf "Installing Gentoo Development Dependencies\n" 65 | sudo emerge --ask net-libs/webkit-gtk:4 dev-libs/libappindicator net-misc/curl net-misc/wget 66 | ;; 67 | "opensuse") 68 | printf "Installing openSUSE Development Dependencies\n" 69 | sudo zypper up 70 | sudo zypper in webkit2gtk3-soup2-devel libopenssl-devel curl wget libappindicator3-1 librsvg-devel 71 | sudo zypper in -t pattern devel_basis 72 | ;; 73 | "void") 74 | printf "Installing Void Development Dependencies\n" 75 | sudo xbps-install -Syu 76 | sudo xbps-install -S webkit2gtk curl wget openssl gtk+3 libappindicator librsvg gcc pkg-config 77 | ;; 78 | esac 79 | # Install Rust 80 | curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh 81 | # All done! 82 | printf "All done!\n" 83 | 84 | sudo apt update 85 | 86 | sudo apt install wget unzip 87 | 88 | printf "Checking the Rust toolchain \n" 89 | 90 | rustup update 91 | rustc --version 92 | 93 | # Path: setup\setup_linux_debian.sh 94 | -------------------------------------------------------------------------------- /.github/scripts/setup-mac.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | brew install wget 4 | brew install unzip 5 | 6 | printf "Installing Xcode Tools \n" 7 | xcode-select --install 8 | 9 | printf "Installing Rust toolchain \n" 10 | curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh 11 | 12 | 13 | 14 | printf "Please restart the terminal and then run the post_installation.sh script to finish the setup \n" 15 | -------------------------------------------------------------------------------- /.github/scripts/setup-windows.ps1: -------------------------------------------------------------------------------- 1 | function SetupProject { 2 | Write-Host "Checking if Rustup is already installed" -ForegroundColor Green 3 | $rustup = Get-Command rustup -ErrorAction SilentlyContinue 4 | if ($rustup) { 5 | Write-Host "Rustup is already installed" -ForegroundColor Green 6 | }else{ 7 | Write-Host "Installing Rustup" -ForegroundColor Blue 8 | winget install --id Rustlang.Rustup 9 | } 10 | Write-Host "Setting Rust Toolchain version" -ForegroundColor Green 11 | rustup default stable-msvc 12 | 13 | Write-Host "Checking if NodeJS is already installed" -ForegroundColor Green 14 | $nodejs = Get-Command node -ErrorAction SilentlyContinue 15 | 16 | if ($nodejs){ 17 | Write-Host "NodeJS is already installed, continuing" -ForegroundColor Green 18 | }else{ 19 | Write-Host "Installing NodeJS and NPM" -ForegroundColor Blue 20 | Invoke-WebRequest 'https://nodejs.org/dist/v18.16.1/node-v18.16.1-win-x64.zip' OutFile 'C:/nodejs.zip' 21 | Expand-Archive C:\nodejs.zip -DestinationPath C:\ 22 | Rename-Item "C:\\node-v18.16.1-win-x64" C:\nodejs 23 | $Env:Path += ";C:\nodejs" 24 | Write-Host "Done" -ForegroundColor Green 25 | } 26 | 27 | Write-Host "Installing PNPM" -ForegroundColor Blue 28 | npm install -g pnpm 29 | 30 | Write-Host "Setup complete" -ForegroundColor Green 31 | 32 | Write-Host "Please follow the Tauri documentation for further information" -ForegroundColor Blue 33 | Start-Process "https://next--tauri.netlify.app/next/guides/getting-started/prerequisites/windows" 34 | } 35 | 36 | & SetupProject -------------------------------------------------------------------------------- /.github/workflows/README.md: -------------------------------------------------------------------------------- 1 | # Instructions 2 | 3 | Please rename the `.github/workflows/.txt` file of the workflow you wish to enable to `.github/workflows/.yml`. 4 | 5 | This file should be formatted as YAML, feel free to change the variables in the file. Please follow the [workflow documentation](https://docs.github.com/en/actions) for more information. [Learn more about workflows](https://docs.github.com/en/actions/using-workflows). 6 | 7 | Please note that the workflows will be enabled automatically after you push your changes to the repository, and can always be enabled manually from the `Workflows` section of the repository settings. 8 | 9 | Please ***do not delete*** the `.github/workflows/.txt` files, and do not delete the `.github/workflows/build-jekyll.yml` file. 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": true, 3 | "quoteProps": "as-needed", 4 | "semi": false, 5 | "tabWidth": 4, 6 | "singleQuote": true, 7 | "printWidth": 100, 8 | "bracketSameLine": true 9 | } -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "tauri-apps.tauri-vscode", 4 | "rust-lang.rust-analyzer", 5 | "esbenp.prettier-vscode" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.fixAll": "explicit" 4 | }, 5 | "cSpell.words": [ 6 | "dialoguer", 7 | "FREENOVE", 8 | "QIOUSB", 9 | "ratelimit", 10 | "reqwest", 11 | "solidjs", 12 | "specta", 13 | "TESP", 14 | "thiserror", 15 | "WROOMS", 16 | "XAIO", 17 | "XIAOSENSES" 18 | ] 19 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 EyeTrackVR 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 | # Firmware Flashing Tool 2 | 3 | This is a streamlined `Tauri` app using `Vite` and `SolidJS`. 4 | 5 | A firmware flashing tool for the [Open Source EyeTrackVR hardware](https://docs.eyetrack.vr) project. 6 | 7 | This repo includes: 8 | 9 | - [Tauri](https://tauri.app/) 10 | - [JSDoc](https://jsdoc.app/) 11 | - [Prettier](https://prettier.io/) 12 | - [ESLint](https://eslint.org/) 13 | - [TailwindCSS](https://tailwindcss.com/) 14 | - [Typescript](https://www.typescriptlang.org/) 15 | - [Proper VSCode Workspace](./firmwareflasher.code-workspace) 16 | 17 | ## Usage 18 | 19 | > [!WARNING]\ 20 | This project uses `pnpm` by default, and utilizes `pnpm workspaces`. If you do not have `pnpm` installed, you can install it with `npm install -g pnpm`. 21 | 22 | You _can_ use `yarn` or `npm`, however, _you_ will need to modify the project to remove the `pnpm` specific commands and workspace. 23 | 24 | ## Setup 25 | 26 | This project uses custom `Tauri Plugins` to communicate with the hardware. These plugins are located in the [`src-tauri/src/lib`](/src-tauri/src/lib) folder. 27 | 28 | > [!WARNING]\ 29 | > You **must** run the following command to install the project deps and build the plugins before running the app: 30 | 31 | ```bash 32 | pnpm run setup 33 | ``` 34 | 35 | ## Available Scripts 36 | 37 | See the [`package.json`](/package.json) for all available scripts. 38 | 39 | ### `pnpm tauri dev` 40 | 41 | Runs the app in the development mode.
42 | 43 | An app should launch on your desktop. 44 | 45 | The page will reload if you make edits.
46 | 47 | ### `pnpm docs` 48 | 49 | Uses `JSDoc` to build a documentation website based on the projects documentation. 50 | 51 | ### `pnpm lint` 52 | 53 | Runs `eslint` on all of the included files. 54 | 55 | ### `pnpm format` 56 | 57 | Uses `Prettier` and the above `pnpm lint` command to lint and then format all included file types. 58 | 59 | ## Deployment 60 | 61 | To build the app, run the following: 62 | 63 | ```bash 64 | pnpm tauri build 65 | ``` 66 | 67 | Builds the app for production to the `src-tauri/target` folder.
68 | This will correctly bundle Solid in production mode and optimizes the build for the best performance. 69 | 70 | The build is minified and the filenames include the hashes.
71 | -------------------------------------------------------------------------------- /firmwareflasher.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "name": "Repo Root", 5 | "path": "." 6 | } 7 | ], 8 | "settings": { 9 | "files.autoSave": "afterDelay", 10 | "rust-analyzer.checkOnSave.enable": true, 11 | "rust-analyzer.checkOnSave.command": "clippy", 12 | "rust-analyzer.checkOnSave.allTargets": true, 13 | "rust-analyzer.files.excludeDirs": [ 14 | "**/target", 15 | "**/node_modules", 16 | "**/dist", 17 | "**/build", 18 | "**/public", 19 | "**/assets", 20 | "**/src-tauri/target", 21 | "**/scripts", 22 | "**/src/components", 23 | "**/src/interfaces", 24 | "**/src/pages", 25 | "**/src/static", 26 | "**/src/styles", 27 | "**/src/utils" 28 | ], 29 | "rust-analyzer.files.watcher": "client", 30 | "editor.defaultFormatter": "esbenp.prettier-vscode", 31 | "[toml]": { 32 | "editor.defaultFormatter": "tamasfe.even-better-toml" 33 | }, 34 | "[jsonc]": { 35 | "editor.defaultFormatter": "esbenp.prettier-vscode" 36 | }, 37 | "[javascript]": { 38 | "editor.defaultFormatter": "esbenp.prettier-vscode" 39 | }, 40 | "[typescript]": { 41 | "editor.defaultFormatter": "esbenp.prettier-vscode" 42 | }, 43 | "liveServer.settings.multiRootWorkspaceName": "Example", 44 | "cSpell.words": [ 45 | "autodetection", 46 | "codegen", 47 | "daisyui", 48 | "eyetrack", 49 | "eyetrackvrfft", 50 | "flashtool", 51 | "Goebel", 52 | "highlightjs", 53 | "hljs", 54 | "improv", 55 | "Iprops", 56 | "kobalte", 57 | "Kruckenberg", 58 | "manifestfile", 59 | "msvc", 60 | "nanos", 61 | "ratelimit", 62 | "solidjs", 63 | "specta", 64 | "stackoverflow", 65 | "Swatinem", 66 | "typecheck", 67 | "Unlisten", 68 | "webserial" 69 | ], 70 | "[json]": { 71 | "editor.defaultFormatter": "esbenp.prettier-vscode" 72 | }, 73 | "editor.formatOnSave": true, 74 | "files.exclude": { 75 | "**/.git": false 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | ETVR Docs Site 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 32 | 33 | 34 | 35 | 36 | 40 | 43 | 44 | 45 | 46 |
47 |
48 |
49 | 50 | 51 | 52 |
53 |
54 | 55 | 56 | 57 |
58 |
59 | 60 | 63 | 64 |
65 |
66 |
67 | 68 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /jsdoc.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags": { 3 | "allowUnknownTags": true, 4 | "dictionaries": [ 5 | "jsdoc", 6 | "closure" 7 | ] 8 | }, 9 | "source": { 10 | "include": [ 11 | "src" 12 | ], 13 | "exclude": [ 14 | "src/vite-env.d.ts" 15 | ], 16 | "includePattern": ".+\\.js|ts(doc|x)?$", 17 | "excludePattern": "(^|\\/|\\\\)_" 18 | }, 19 | "plugins": [ 20 | "plugins/markdown", 21 | "plugins/summarize", 22 | "node_modules/better-docs/typescript", 23 | "node_modules/better-docs/typescript" 24 | ], 25 | "markdown": { 26 | "excludeTags": [ 27 | "author" 28 | ] 29 | }, 30 | "templates": { 31 | "better-docs": { 32 | "name": "Components" 33 | } 34 | }, 35 | "opts": { 36 | "destination": "docs", 37 | "recurse": true, 38 | "readme": "README.md" 39 | } 40 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "etvr-firmware-flasher-tool", 3 | "version": "1.6.0", 4 | "private": true, 5 | "description": "A SolidJS project for EyeTrackVR", 6 | "author": "EyeTrackVR", 7 | "license": "MIT", 8 | "scripts": { 9 | "dev": "vite dev", 10 | "build": "vite build", 11 | "start": "vite start", 12 | "serve": "vite preview", 13 | "setup": "pnpm install && pnpm run specta:post-install", 14 | "tauri:dev": "tauri dev", 15 | "tauri:dev:release": "tauri dev --release", 16 | "tauri:build": "tauri build", 17 | "tauri:build:dev": "tauri build --debug", 18 | "tauri:update": "cd src-tauri && cargo update && cargo upgrade", 19 | "tauri:clean": "cd src-tauri && cargo clean", 20 | "specta:post-install": "pnpm run specta:gen && pnpm run specta:build", 21 | "specta:build": "pnpm --stream -r specta:build", 22 | "specta:gen": "pnpm --stream -r specta:gen", 23 | "docs": "jsdoc -c jsdoc.conf.json", 24 | "lint": "eslint --ext .js,.ts,.jsx,.tsx src", 25 | "format": "pnpm run lint --fix & pnpm prettier --write \"src/**/*.{js,jsx,ts,tsx}\"", 26 | "update-deps": "pnpm up -Li", 27 | "typecheck": "tsc --noEmit" 28 | }, 29 | "devDependencies": { 30 | "@babel/core": "^7.26.0", 31 | "@babel/preset-env": "^7.26.0", 32 | "@tailwindcss/forms": "^0.5.10", 33 | "@tauri-apps/api": "1.6.0", 34 | "@tauri-apps/cli": "1.6.3", 35 | "@types/node": "^22.10.7", 36 | "@typescript-eslint/eslint-plugin": "^8.20.0", 37 | "@typescript-eslint/parser": "^8.20.0", 38 | "autoprefixer": "^10.4.20", 39 | "babel-loader": "^9.2.1", 40 | "better-docs": "^2.7.3", 41 | "daisyui": "4.6.0", 42 | "eslint": "^9.18.0", 43 | "eslint-config-google": "^0.14.0", 44 | "eslint-config-prettier": "^10.0.1", 45 | "eslint-import-resolver-typescript": "^3.7.0", 46 | "eslint-plugin-autofix": "^2.2.0", 47 | "eslint-plugin-import": "^2.31.0", 48 | "eslint-plugin-solid": "^0.14.5", 49 | "https-localhost": "^4.7.1", 50 | "jsdoc": "^4.0.4", 51 | "postcss": "^8.5.1", 52 | "postcss-import": "^16.1.0", 53 | "prettier": "^3.4.2", 54 | "tailwindcss": "^3.4.17", 55 | "typescript": "^5.7.3", 56 | "vite": "^6.0.7", 57 | "vite-plugin-solid": "^2.11.0" 58 | }, 59 | "dependencies": { 60 | "@kobalte/core": "^0.13.7", 61 | "@kobalte/tailwindcss": "^0.9.0", 62 | "@optimize-lodash/rollup-plugin": "^5.0.0", 63 | "@solid-primitives/i18n": "^2.1.1", 64 | "@solid-primitives/map": "^0.5.0", 65 | "@solid-primitives/refs": "^1.0.8", 66 | "@solidjs/meta": "^0.29.4", 67 | "@solidjs/router": "^0.15.3", 68 | "@stitches/core": "^1.2.8", 69 | "@tailwindcss/typography": "^0.5.16", 70 | "babel-preset-solid": "^1.9.3", 71 | "class-variance-authority": "^0.7.1", 72 | "esptool-js": "^0.5.4", 73 | "fp-ts": "^2.16.9", 74 | "highlight.js": "^11.11.1", 75 | "lodash": "^4.17.21", 76 | "solid-form-handler": "^1.2.3", 77 | "solid-headless": "^0.13.1", 78 | "solid-i18n": "^1.1.0", 79 | "solid-icons": "^1.1.0", 80 | "solid-js": "^1.9.4", 81 | "solid-spinner": "^0.2.0", 82 | "solid-transition-group": "^0.2.3", 83 | "solidjs-icons": "^1.0.12", 84 | "solidjs-use": "^2.3.0", 85 | "tauri-plugin-log-api": "github:tauri-apps/tauri-plugin-log", 86 | "tauri-plugin-request-client": "workspace:*", 87 | "tauri-plugin-serialport-api": "github:deid84/tauri-plugin-serialport#v1.0.0", 88 | "tauri-plugin-store-api": "github:tauri-apps/tauri-plugin-store", 89 | "tauri-plugin-upload-api": "github:tauri-apps/tauri-plugin-upload" 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "src-tauri/src/lib/api" 3 | - "src-tauri/src/lib/utils" 4 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-undef 2 | module.exports = { 3 | purge: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], 4 | plugins: { 5 | 'postcss-import': {}, 6 | tailwindcss: {}, 7 | autoprefixer: {}, 8 | }, 9 | } -------------------------------------------------------------------------------- /public/audio/EyeTrackApp_Audio_compleated.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/public/audio/EyeTrackApp_Audio_compleated.wav -------------------------------------------------------------------------------- /public/audio/EyeTrackApp_Audio_notif.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/public/audio/EyeTrackApp_Audio_notif.mp3 -------------------------------------------------------------------------------- /public/audio/EyeTrackApp_Audio_start.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/public/audio/EyeTrackApp_Audio_start.wav -------------------------------------------------------------------------------- /public/images/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/public/images/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/images/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/public/images/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/images/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/public/images/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /public/images/icons/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #22222d 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/images/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/public/images/icons/favicon-16x16.png -------------------------------------------------------------------------------- /public/images/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/public/images/icons/favicon-32x32.png -------------------------------------------------------------------------------- /public/images/icons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/public/images/icons/favicon.ico -------------------------------------------------------------------------------- /public/images/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/public/images/icons/icon-128x128.png -------------------------------------------------------------------------------- /public/images/icons/icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/public/images/icons/icon-144x144.png -------------------------------------------------------------------------------- /public/images/icons/icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/public/images/icons/icon-152x152.png -------------------------------------------------------------------------------- /public/images/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/public/images/icons/icon-192x192.png -------------------------------------------------------------------------------- /public/images/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/public/images/icons/icon-384x384.png -------------------------------------------------------------------------------- /public/images/icons/icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/public/images/icons/icon-48x48.png -------------------------------------------------------------------------------- /public/images/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/public/images/icons/icon-512x512.png -------------------------------------------------------------------------------- /public/images/icons/icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/public/images/icons/icon-72x72.png -------------------------------------------------------------------------------- /public/images/icons/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/public/images/icons/icon-96x96.png -------------------------------------------------------------------------------- /public/images/icons/maskable_icon_x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/public/images/icons/maskable_icon_x128.png -------------------------------------------------------------------------------- /public/images/icons/maskable_icon_x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/public/images/icons/maskable_icon_x192.png -------------------------------------------------------------------------------- /public/images/icons/maskable_icon_x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/public/images/icons/maskable_icon_x384.png -------------------------------------------------------------------------------- /public/images/icons/maskable_icon_x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/public/images/icons/maskable_icon_x48.png -------------------------------------------------------------------------------- /public/images/icons/maskable_icon_x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/public/images/icons/maskable_icon_x512.png -------------------------------------------------------------------------------- /public/images/icons/maskable_icon_x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/public/images/icons/maskable_icon_x72.png -------------------------------------------------------------------------------- /public/images/icons/maskable_icon_x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/public/images/icons/maskable_icon_x96.png -------------------------------------------------------------------------------- /public/images/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/public/images/icons/mstile-150x150.png -------------------------------------------------------------------------------- /public/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/public/images/logo.png -------------------------------------------------------------------------------- /public/images/logo512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/public/images/logo512x512.png -------------------------------------------------------------------------------- /public/images/png/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/public/images/png/logo.png -------------------------------------------------------------------------------- /public/images/svg/arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/images/svg/gearSolid.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/svg/grip-solid.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/svg/list-ul-solid.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /src-tauri/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [env] 2 | # RUST_BACKTRACE = 1 # Enable backtraces 3 | RUST_LOG = "info" # Set to "debug" for more verbose output, debug, info, warn, error -------------------------------------------------------------------------------- /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 = "firmware_flashing_tool" 3 | version = "1.6.0" 4 | description = "A tool to flash the firmware of the EyeTrackVR device." 5 | authors = ["DaOfficialWizard"] 6 | license = "MIT" 7 | repository = "https://github.com/RedHawk989/EyeTrackVR" 8 | default-run = "firmware_flashing_tool" 9 | edition = "2021" 10 | rust-version = "1.69" 11 | 12 | [dependencies] 13 | serde_json = "1.0" 14 | zip-extract = "0.1.3" 15 | command-group = "2.1.0" 16 | 17 | [dependencies.reqwest] 18 | workspace = true 19 | 20 | [dependencies.serde] 21 | workspace = true 22 | 23 | [dependencies.tauri] 24 | workspace = true 25 | 26 | [dependencies.log] 27 | workspace = true 28 | 29 | [dependencies.specta] 30 | workspace = true 31 | 32 | [dependencies.dialoguer] 33 | workspace = true 34 | 35 | [dependencies.tauri-specta] 36 | workspace = true 37 | 38 | [dependencies.tauri-plugin-request-client] 39 | path = "src/lib/api" 40 | 41 | [dependencies.tauri-plugin-esp] 42 | path = "src/lib/esp" 43 | 44 | [dependencies.tauri-plugin-window-state] 45 | git = "https://github.com/tauri-apps/plugins-workspace" 46 | branch = "v1" 47 | 48 | [dependencies.tauri-plugin-single-instance] 49 | git = "https://github.com/tauri-apps/plugins-workspace" 50 | branch = "v1" 51 | 52 | [dependencies.tauri-plugin-upload] 53 | git = "https://github.com/tauri-apps/plugins-workspace" 54 | branch = "v1" 55 | 56 | [dependencies.tauri-plugin-log] 57 | git = "https://github.com/tauri-apps/plugins-workspace" 58 | branch = "v1" 59 | 60 | [dependencies.tauri-plugin-store] 61 | git = "https://github.com/tauri-apps/plugins-workspace" 62 | branch = "v1" 63 | 64 | [dependencies.tokio] 65 | version = "1" 66 | features = ["full"] 67 | 68 | [workspace] 69 | members = ["src/lib/api", "src/lib/util", "src/lib/esp"] 70 | 71 | [workspace.dependencies] 72 | dialoguer = "0.11.0" 73 | log = "0.4.21" 74 | 75 | [workspace.dependencies.reqwest] 76 | version = "0.11" 77 | features = ["json", "rustls-tls"] 78 | 79 | [workspace.dependencies.serde] 80 | version = "1.0.197" 81 | features = ["derive"] 82 | 83 | [workspace.dependencies.specta] 84 | version = "2.0.0-rc.7" 85 | features = ["functions", "tauri"] 86 | 87 | [workspace.dependencies.tauri-specta] 88 | version = "2.0.0-rc.4" 89 | features = ["javascript", "typescript"] 90 | 91 | [workspace.dependencies.tauri] 92 | version = "1.6.1" 93 | features = ["process-exit", "protocol-asset", "fs-all", "dialog-all", "http-all", "icon-ico", "notification-all", "os-all", "path-all", "process-relaunch", "shell-open", "system-tray", "window-center", "window-close", "window-create", "window-hide", "window-maximize", "window-minimize", "window-request-user-attention", "window-set-decorations", "window-set-focus", "window-set-fullscreen", "window-set-position", "window-set-resizable", "window-set-size", "window-set-title", "window-show", "window-start-dragging", "window-unmaximize", "window-unminimize"] 94 | 95 | [features] 96 | default = ["custom-protocol"] 97 | custom-protocol = ["tauri/custom-protocol"] 98 | updater = ["tauri/updater"] 99 | 100 | [build-dependencies.tauri-build] 101 | version = "1.5.1" 102 | features = [] 103 | 104 | [profile.dev] 105 | debug = 0 106 | 107 | [profile.release] 108 | panic = "abort" 109 | codegen-units = 1 110 | lto = true 111 | opt-level = "s" 112 | strip = true 113 | -------------------------------------------------------------------------------- /src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tauri_build::build() 3 | } 4 | -------------------------------------------------------------------------------- /src-tauri/crates/linux/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "linux" 3 | version = "0.1.0" 4 | license = "MIT" 5 | #default-run = "linux" 6 | edition = "2021" 7 | rust-version = "1.69" 8 | include = ["/src", "/README.md", "/LICENCE"] 9 | -------------------------------------------------------------------------------- /src-tauri/crates/linux/src/lib.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/src-tauri/crates/linux/src/lib.rs -------------------------------------------------------------------------------- /src-tauri/crates/macos/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "macos" 3 | version = "0.1.0" 4 | license = "MIT" 5 | #default-run = "macos" 6 | edition = "2021" 7 | rust-version = "1.69" 8 | include = ["/src", "/README.md", "/LICENCE"] 9 | -------------------------------------------------------------------------------- /src-tauri/crates/macos/src/lib.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/src-tauri/crates/macos/src/lib.rs -------------------------------------------------------------------------------- /src-tauri/crates/windows/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "windows" 3 | version = "0.1.0" 4 | license = "MIT" 5 | #default-run = "windows" 6 | edition = "2021" 7 | rust-version = "1.69" 8 | include = ["/src", "/README.md", "/LICENCE"] -------------------------------------------------------------------------------- /src-tauri/crates/windows/src/lib.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/src-tauri/crates/windows/src/lib.rs -------------------------------------------------------------------------------- /src-tauri/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/src-tauri/icons/128x128.png -------------------------------------------------------------------------------- /src-tauri/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/src-tauri/icons/128x128@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/src-tauri/icons/32x32.png -------------------------------------------------------------------------------- /src-tauri/icons/Square107x107Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/src-tauri/icons/Square107x107Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square142x142Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/src-tauri/icons/Square142x142Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/src-tauri/icons/Square150x150Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square284x284Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/src-tauri/icons/Square284x284Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square30x30Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/src-tauri/icons/Square30x30Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square310x310Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/src-tauri/icons/Square310x310Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/src-tauri/icons/Square44x44Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square71x71Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/src-tauri/icons/Square71x71Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square89x89Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/src-tauri/icons/Square89x89Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/src-tauri/icons/StoreLogo.png -------------------------------------------------------------------------------- /src-tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/src-tauri/icons/icon.icns -------------------------------------------------------------------------------- /src-tauri/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/src-tauri/icons/icon.ico -------------------------------------------------------------------------------- /src-tauri/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/src-tauri/icons/icon.png -------------------------------------------------------------------------------- /src-tauri/rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 100 2 | hard_tabs = false 3 | tab_spaces = 2 4 | newline_style = "Auto" 5 | use_small_heuristics = "Default" 6 | reorder_imports = true 7 | reorder_modules = true 8 | remove_nested_parens = true 9 | edition = "2018" 10 | merge_derives = true 11 | use_try_shorthand = false 12 | use_field_init_shorthand = false 13 | force_explicit_abi = true 14 | imports_granularity = "Crate" 15 | -------------------------------------------------------------------------------- /src-tauri/src/lib/api/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | target -------------------------------------------------------------------------------- /src-tauri/src/lib/api/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tauri-plugin-request-client" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | etvr-utils = { path = "../util" } 10 | thiserror = "1" 11 | serde = { workspace = true } 12 | specta = { workspace = true } 13 | tauri = { workspace = true } 14 | tauri-specta = { workspace = true } 15 | log = { workspace = true } 16 | reqwest = { workspace = true } 17 | -------------------------------------------------------------------------------- /src-tauri/src/lib/api/index.ts: -------------------------------------------------------------------------------- 1 | import { commands } from './tauri-plugin-request-client' 2 | 3 | export const { makeRequest } = commands 4 | -------------------------------------------------------------------------------- /src-tauri/src/lib/api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tauri-plugin-request-client", 3 | "private": true, 4 | "exports": { 5 | "./package.json": "./package.json", 6 | ".": { 7 | "types": "./dist/index.d.ts", 8 | "import": "./dist/index.js", 9 | "default": "./dist/index.js" 10 | } 11 | }, 12 | "devDependencies": { 13 | "typescript": "^5.2.2" 14 | }, 15 | "scripts": { 16 | "specta:post-install": "pnpm specta:build", 17 | "specta:build": "pnpm tsc", 18 | "specta:gen": "cargo test && pnpm specta:build" 19 | } 20 | } -------------------------------------------------------------------------------- /src-tauri/src/lib/api/tauri-plugin-request-client.ts: -------------------------------------------------------------------------------- 1 | // This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually. 2 | 3 | export const commands = { 4 | async makeRequest( 5 | endpoint: string, 6 | deviceName: string, 7 | method: string, 8 | ): Promise<__Result__> { 9 | try { 10 | return { 11 | status: 'ok', 12 | data: await TAURI_INVOKE('plugin:tauri-plugin-request-client|make_request', { 13 | endpoint, 14 | deviceName, 15 | method, 16 | }), 17 | } 18 | } catch (e) { 19 | if (e instanceof Error) throw e 20 | else return { status: 'error', error: e as any } 21 | } 22 | }, 23 | } 24 | 25 | /** user-defined types **/ 26 | 27 | /** tauri-specta globals **/ 28 | 29 | import { invoke as TAURI_INVOKE } from '@tauri-apps/api' 30 | import * as TAURI_API_EVENT from '@tauri-apps/api/event' 31 | import { type WebviewWindowHandle as __WebviewWindowHandle__ } from '@tauri-apps/api/window' 32 | 33 | type __EventObj__ = { 34 | listen: (cb: TAURI_API_EVENT.EventCallback) => ReturnType> 35 | once: (cb: TAURI_API_EVENT.EventCallback) => ReturnType> 36 | emit: T extends null 37 | ? (payload?: T) => ReturnType 38 | : (payload: T) => ReturnType 39 | } 40 | 41 | type __Result__ = { status: 'ok'; data: T } | { status: 'error'; error: E } 42 | 43 | function __makeEvents__>(mappings: Record) { 44 | return new Proxy( 45 | {} as unknown as { 46 | [K in keyof T]: __EventObj__ & { 47 | (handle: __WebviewWindowHandle__): __EventObj__ 48 | } 49 | }, 50 | { 51 | get: (_, event) => { 52 | const name = mappings[event as keyof T] 53 | 54 | return new Proxy((() => {}) as any, { 55 | apply: (_, __, [window]: [__WebviewWindowHandle__]) => ({ 56 | listen: (arg: any) => window.listen(name, arg), 57 | once: (arg: any) => window.once(name, arg), 58 | emit: (arg: any) => window.emit(name, arg), 59 | }), 60 | get: (_, command: keyof __EventObj__) => { 61 | switch (command) { 62 | case 'listen': 63 | return (arg: any) => TAURI_API_EVENT.listen(name, arg) 64 | case 'once': 65 | return (arg: any) => TAURI_API_EVENT.once(name, arg) 66 | case 'emit': 67 | return (arg: any) => TAURI_API_EVENT.emit(name, arg) 68 | } 69 | }, 70 | }) 71 | }, 72 | }, 73 | ) 74 | } 75 | -------------------------------------------------------------------------------- /src-tauri/src/lib/esp/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tauri-plugin-esp" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | thiserror = "1" 10 | serde = { workspace = true } 11 | serde_json = "1.0.128" 12 | tauri = { workspace = true } 13 | log = { workspace = true } 14 | serialport = { version = "4.5.0", features = ["serde"] } 15 | espflash = { version = "3.1.1", default-features = false, features = ["serialport"] } 16 | -------------------------------------------------------------------------------- /src-tauri/src/lib/esp/src/command.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::Read; 3 | use std::path::Path; 4 | 5 | use serde::{Deserialize, Serialize, Serializer}; 6 | use thiserror::Error; 7 | 8 | 9 | -------------------------------------------------------------------------------- /src-tauri/src/lib/esp/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Mutex; 2 | use tauri::{ 3 | plugin::{Builder, TauriPlugin}, 4 | Manager, Runtime, 5 | }; 6 | 7 | use api::*; 8 | 9 | use crate::state::EspState; 10 | 11 | mod api; 12 | mod command; 13 | mod manifest; 14 | mod state; 15 | 16 | pub fn init() -> TauriPlugin { 17 | Builder::new("esp") 18 | .invoke_handler(tauri::generate_handler![ 19 | available_ports, 20 | test_connection, 21 | flash, 22 | stream_logs, 23 | cancel_stream_logs, 24 | send_commands 25 | ]) 26 | .setup(move |app| { 27 | app.manage(Mutex::new(EspState { 28 | log_stream_cancel: None, 29 | })); 30 | Ok(()) 31 | }) 32 | .build() 33 | } 34 | -------------------------------------------------------------------------------- /src-tauri/src/lib/esp/src/manifest.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::Read; 3 | use std::path::Path; 4 | 5 | use serde::{Deserialize, Serialize, Serializer}; 6 | use thiserror::Error; 7 | use crate::api::EspError; 8 | 9 | #[derive(Debug, Deserialize)] 10 | pub struct Part { 11 | pub path: String, 12 | pub offset: u32, 13 | } 14 | 15 | #[derive(Debug, Deserialize)] 16 | pub struct Build { 17 | #[serde(rename = "chipFamily")] 18 | pub chip_family: String, 19 | pub parts: Vec, 20 | } 21 | 22 | #[derive(Debug, Deserialize)] 23 | pub struct Manifest { 24 | pub name: String, 25 | pub version: String, 26 | pub new_install_prompt_erase: bool, 27 | pub builds: Vec, 28 | } 29 | 30 | #[derive(Error, Debug)] 31 | pub enum ManifestError { 32 | #[error(transparent)] 33 | IoError { 34 | #[from] 35 | source: std::io::Error, 36 | }, 37 | #[error(transparent)] 38 | JsonError { 39 | #[from] 40 | source: serde_json::Error, 41 | }, 42 | } 43 | 44 | impl Serialize for ManifestError { 45 | fn serialize(&self, serializer: S) -> Result 46 | where 47 | S: Serializer, 48 | { 49 | serializer.serialize_str(self.to_string().as_ref()) 50 | } 51 | } 52 | 53 | pub fn load_manifest(path: &Path) -> Result { 54 | let mut file = File::open(path)?; 55 | let mut json = String::new(); 56 | file.read_to_string(&mut json)?; 57 | 58 | let manifest: Manifest = serde_json::from_str(&json)?; 59 | Ok(manifest) 60 | } 61 | -------------------------------------------------------------------------------- /src-tauri/src/lib/esp/src/state.rs: -------------------------------------------------------------------------------- 1 | use std::sync::mpsc::Sender; 2 | 3 | pub struct EspState { 4 | pub log_stream_cancel: Option>, 5 | } 6 | -------------------------------------------------------------------------------- /src-tauri/src/lib/util/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "etvr-utils" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | thiserror = "1" 10 | log = { workspace = true } 11 | reqwest = { workspace = true } 12 | dialoguer = { workspace = true } 13 | serde = { workspace = true } 14 | specta = { workspace = true } 15 | tauri = { workspace = true } 16 | tauri-specta = { workspace = true } 17 | -------------------------------------------------------------------------------- /src-tauri/src/lib/util/src/colors.rs: -------------------------------------------------------------------------------- 1 | use dialoguer::console::{style, StyledObject}; 2 | use log::Level; 3 | use std::fmt::{Display, Formatter}; 4 | 5 | pub struct Color<'a> { 6 | inner: StyledObject<&'a str>, 7 | } 8 | 9 | impl<'a> Color<'a> { 10 | pub fn new(value: &'a str) -> Self { 11 | Color { 12 | inner: style(value), 13 | } 14 | } 15 | 16 | pub fn map_level(self, level: Level) -> Self { 17 | match level { 18 | Level::Error => Color { 19 | inner: self.inner.red(), 20 | }, 21 | Level::Warn => Color { 22 | inner: self.inner.magenta().bold(), 23 | }, 24 | Level::Info => Color { 25 | inner: self.inner.green(), 26 | }, 27 | Level::Debug => Color { 28 | inner: self.inner.blue(), 29 | }, 30 | Level::Trace => Color { 31 | inner: self.inner.cyan(), 32 | }, 33 | } 34 | } 35 | 36 | pub fn bold(self) -> Self { 37 | Color { 38 | inner: self.inner.bold(), 39 | } 40 | } 41 | 42 | pub fn red(self) -> Self { 43 | Color { 44 | inner: self.inner.red(), 45 | } 46 | } 47 | 48 | pub fn green(self) -> Self { 49 | Color { 50 | inner: self.inner.green(), 51 | } 52 | } 53 | 54 | pub fn yellow(self) -> Self { 55 | Color { 56 | inner: self.inner.yellow(), 57 | } 58 | } 59 | 60 | pub fn blue(self) -> Self { 61 | Color { 62 | inner: self.inner.blue(), 63 | } 64 | } 65 | 66 | pub fn magenta(self) -> Self { 67 | Color { 68 | inner: self.inner.magenta(), 69 | } 70 | } 71 | 72 | pub fn cyan(self) -> Self { 73 | Color { 74 | inner: self.inner.cyan(), 75 | } 76 | } 77 | } 78 | 79 | impl Display for Color<'_> { 80 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 81 | f.write_str(&format!("{}", self.inner)) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src-tauri/src/lib/util/src/errors.rs: -------------------------------------------------------------------------------- 1 | //! Main crate Error 2 | use serde::{ser::Serializer, Serialize}; 3 | 4 | //use crate::{colors::Color, etvr_stderr, etvr_stdout, f}; 5 | 6 | #[derive(thiserror::Error, Debug)] 7 | pub enum Error { 8 | #[error("Generic error: {0}")] 9 | Generic(String), 10 | #[error(transparent)] 11 | IO(#[from] std::io::Error), 12 | #[error("Operation Canceled error: {0}")] 13 | OperationCancelled(String), 14 | } 15 | 16 | pub type ETVResult = std::result::Result; 17 | 18 | impl Error { 19 | pub fn new(message: String) -> Self { 20 | Self::Generic(message) 21 | } 22 | 23 | pub fn op_cancelled(message: &str) -> Self { 24 | Self::OperationCancelled(message.to_string()) 25 | } 26 | } 27 | 28 | impl Serialize for Error { 29 | fn serialize(&self, serializer: S) -> std::result::Result 30 | where 31 | S: Serializer, 32 | { 33 | serializer.serialize_str(self.to_string().as_ref()) 34 | } 35 | } 36 | 37 | impl From for Error { 38 | fn from(e: reqwest::Error) -> Self { 39 | Error::IO(std::io::Error::new(std::io::ErrorKind::Other, e)) 40 | } 41 | } 42 | 43 | // TODO: Handler for custom errors 44 | /* pub fn handle(result: ETVResult<()>) { 45 | if let Err(error) = result { 46 | match error { 47 | Error::Generic(msg) => { 48 | etvr_stderr!(&msg); 49 | std::process::exit(1) 50 | } 51 | Error::OperationCancelled(msg) => { 52 | etvr_stdout!(f!("Operation cancelled: {}", Color::new(&msg).bold()).as_str()); 53 | } 54 | Error::IO(error) => { 55 | etvr_stderr!(&error.to_string()); 56 | std::process::exit(1) 57 | } 58 | } 59 | } 60 | } */ 61 | -------------------------------------------------------------------------------- /src-tauri/src/lib/util/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub use crate::prelude::*; 2 | 3 | pub mod colors; 4 | pub mod errors; 5 | pub mod logger; 6 | pub mod prelude; 7 | -------------------------------------------------------------------------------- /src-tauri/src/lib/util/src/logger.rs: -------------------------------------------------------------------------------- 1 | //! Crate Logger 2 | //! 3 | //! TODO: This needs to be implemented and connected to the tauri logger. 4 | //use crate::{colors::Color, f}; 5 | //use log::{LevelFilter, Log, Metadata, Record, SetLoggerError}; 6 | 7 | /* pub struct EyeTrackVRLogger { 8 | inner: Filter, 9 | } 10 | 11 | impl EyeTrackVRLogger { 12 | pub fn new(log_level: LevelFilter) -> EyeTrackVRLogger { 13 | let mut builder = Builder::new(); 14 | 15 | //builder 16 | // .filter(None, LevelFilter::Info) 17 | // .filter(Some("desktop_cleaner"), LevelFilter::Debug); 18 | 19 | builder.filter_level(log_level); 20 | EyeTrackVRLogger { 21 | inner: builder.build(), 22 | } 23 | } 24 | 25 | pub fn init(log_level: LevelFilter) -> Result<(), SetLoggerError> { 26 | let logger = Self::new(log_level); 27 | log::set_max_level(logger.inner.filter()); 28 | log::set_boxed_logger(Box::new(logger)) 29 | } 30 | } 31 | 32 | impl Log for EyeTrackVRLogger { 33 | fn enabled(&self, metadata: &Metadata) -> bool { 34 | self.inner.enabled(metadata) 35 | } 36 | 37 | fn log(&self, record: &Record) { 38 | if self.inner.matches(record) { 39 | println!( 40 | "{}", 41 | format_args!( 42 | "{} {}", 43 | Color::new( 44 | f!( 45 | "[EyeTrackVR - {}]:", 46 | Color::new(record.level().as_str()) 47 | .map_level(record.level()) 48 | .bold(), 49 | ) 50 | .as_str() 51 | ) 52 | .bold() 53 | .green(), 54 | Color::new(f!("{}", record.args()).as_str()).cyan() 55 | ) 56 | ); 57 | } 58 | } 59 | 60 | fn flush(&self) {} 61 | } */ 62 | 63 | #[macro_export] 64 | macro_rules! etvr_stderr { 65 | ($($arg:tt)+) => (println!("{}", f!("{} {}", Color::new("[EyeTrackVR]:").bold().green(), Color::new($($arg)+).red()))); 66 | } 67 | 68 | #[macro_export] 69 | macro_rules! etvr_stdout { 70 | ($($arg:tt)+) => (println!("{}", f!("{} {}", Color::new("[EyeTrackVR]:").bold().green(), Color::new($($arg)+).green()))); 71 | } 72 | 73 | #[allow(unused_imports)] 74 | pub(crate) use etvr_stderr; 75 | #[allow(unused_imports)] 76 | pub(crate) use etvr_stdout; 77 | -------------------------------------------------------------------------------- /src-tauri/src/lib/util/src/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use crate::errors::Error; 2 | pub use crate::{etvr_stderr, etvr_stdout}; 3 | pub use log::{debug, error, info, trace, warn}; 4 | pub use tauri_specta::ts; 5 | pub use tauri_specta::*; 6 | 7 | //pub const EXPORT_PATH_ROOT: &str = "../../../../src/static/types/tauri/"; 8 | pub const EXPORT_PATH_ROOT: &str = "./"; 9 | pub use std::format as f; 10 | // Generic wrapper tuple struct for new type pattern , mostly used for type aliasing 11 | pub struct W(pub T); 12 | 13 | pub fn generate_plugin_path(plugin_name: &str) -> String { 14 | f!("{}{}.ts", EXPORT_PATH_ROOT, plugin_name) 15 | } 16 | 17 | #[allow(unused_macros)] 18 | macro_rules! tauri_handlers { 19 | ($($name:path),+) => {{ 20 | #[cfg(debug_assertions)] 21 | tauri_specta::ts::export(specta::collect_types![$($name),+], "../../../src/static/types/tauri_types.ts").unwrap(); 22 | 23 | tauri::generate_handler![$($name),+] 24 | }}; 25 | } 26 | -------------------------------------------------------------------------------- /src-tauri/src/modules/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod menu; 2 | pub mod tauri_commands; 3 | -------------------------------------------------------------------------------- /src-tauri/src/modules/tauri_commands.rs: -------------------------------------------------------------------------------- 1 | use log::debug; 2 | use log::{error, info}; 3 | use tauri::{self, Manager}; 4 | use tauri_plugin_store::with_store; 5 | use tauri_plugin_window_state::{AppHandleExt, StateFlags, WindowExt}; 6 | 7 | /// TODO: refactor to use tauri::fs and tauri::path 8 | #[tauri::command(async)] 9 | #[specta::specta] 10 | pub async fn unzip_archive(archive_path: String, target_dir: String) -> Result { 11 | // The third parameter allows you to strip away toplevel directories. 12 | // If `archive` contained a single directory, its contents would be extracted instead. 13 | let _target_dir = std::path::PathBuf::from(target_dir); // Doesn't need to exist 14 | 15 | let archive = std::fs::read(&archive_path).expect("Failed to read archive"); 16 | zip_extract::extract(std::io::Cursor::new(archive), &_target_dir, true) 17 | .expect("Failed to extract archive"); 18 | 19 | // erase the archive 20 | //TODO: remove JS api for remove file and add rust api for remove file here when it is available through tauri 21 | 22 | // ! Using std:: is BAD practice, but it is the only way to get this to work for now 23 | //std::fs::remove_file(archive_path).expect("Failed to remove archive"); 24 | Ok(archive_path) 25 | } 26 | 27 | #[tauri::command] 28 | #[specta::specta] 29 | pub async fn handle_save_window_state( 30 | app: tauri::AppHandle, 31 | ) -> Result<(), String> { 32 | 33 | // disabled on macos, because it's causing infinite loading with a constant white screen 34 | // todo fixme 35 | #[cfg(not(target_os = "macos"))] 36 | app 37 | .save_window_state(StateFlags::all()) 38 | .expect("[Window State]: Failed to save window state"); 39 | 40 | Ok(()) 41 | } 42 | 43 | #[tauri::command] 44 | #[specta::specta] 45 | pub async fn handle_load_window_state( 46 | window: tauri::Window, 47 | ) -> Result<(), String> { 48 | 49 | // disabled on macos, because it's causing infinite loading with a constant white screen 50 | // todo fixme 51 | #[cfg(not(target_os = "macos"))] 52 | window 53 | .restore_state(StateFlags::all()) 54 | .expect("[Window State]: Failed to restore window state"); 55 | 56 | Ok(()) 57 | } 58 | 59 | pub fn handle_debug( 60 | app: tauri::AppHandle, 61 | ) -> Result { 62 | // read the Store file 63 | let stores = app.state::>(); 64 | let path = std::path::PathBuf::from(".app-settings.bin"); 65 | // match the store value to a LogFilter 66 | let mut debug_state: String = String::new(); 67 | with_store(app.clone(), stores, path, |store| { 68 | let settings = store.get("settings"); 69 | debug!("Settings: {:?}", settings); 70 | if let Some(json) = settings { 71 | let _serde_json = serde_json::from_value::(json.clone()); 72 | debug!("Serde JSON: {:?}", _serde_json); 73 | let serde_json_result = match _serde_json { 74 | Ok(serde_json) => serde_json, 75 | Err(err) => { 76 | error!("Error configuring JSON config file: {}", err); 77 | return Err(tauri_plugin_store::Error::Json(err)); 78 | } 79 | }; 80 | let temp = &serde_json_result["debugMode"]; 81 | debug!("Debug: {:?}", temp); 82 | debug_state = serde_json::from_value::(temp.clone()).unwrap(); 83 | } else { 84 | debug_state = serde_json::json!({}).to_string(); 85 | } 86 | info!("Debug state: {}", debug_state); 87 | Ok(()) 88 | }) 89 | .expect("Failed to get store"); 90 | // set the log level 91 | let log_level = match debug_state.as_str() { 92 | "off" => log::LevelFilter::Off, 93 | "error" => log::LevelFilter::Error, 94 | "warn" => log::LevelFilter::Warn, 95 | "info" => log::LevelFilter::Info, 96 | "debug" => log::LevelFilter::Debug, 97 | "trace" => log::LevelFilter::Trace, 98 | _ => log::LevelFilter::Info, 99 | }; 100 | // return the result 101 | Ok(log_level) 102 | } 103 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { AppProvider } from '@store/context/app' 2 | import { lazy, onMount, Suspense } from 'solid-js' 3 | import { useAppContextMain } from './store/context/main' 4 | 5 | const ToastNotificationWindow = lazy(() => import('@components/Notifications')) 6 | const Modals = lazy(() => import('@containers/Modals')) 7 | const AppRoutes = lazy(() => import('@routes/Routes')) 8 | 9 | const App = () => { 10 | const { handleTitlebar, handleAppBoot } = useAppContextMain() 11 | 12 | onMount(() => { 13 | handleTitlebar(true) 14 | handleAppBoot() 15 | }) 16 | 17 | return ( 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | ) 26 | } 27 | 28 | export default App 29 | -------------------------------------------------------------------------------- /src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeTrackVR/FirmwareFlashingTool/70a159434119a1aef9bdba96f91804cc6e3f28a0/src/assets/favicon.ico -------------------------------------------------------------------------------- /src/assets/js/custom.js: -------------------------------------------------------------------------------- 1 | // On page load or when changing themes, best to add inline in `head` to avoid FOUC 2 | if ( 3 | localStorage.theme === 'dark' || 4 | (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches) 5 | ) { 6 | document.documentElement.classList.add('dark') 7 | } else { 8 | document.documentElement.classList.remove('dark') 9 | } 10 | 11 | // Whenever the user explicitly chooses light mode 12 | //localStorage.theme = 'light' 13 | 14 | //// Whenever the user explicitly chooses dark mode 15 | //localStorage.theme = 'dark' 16 | 17 | //// Whenever the user explicitly chooses to respect the OS preference 18 | //localStorage.removeItem('theme') 19 | -------------------------------------------------------------------------------- /src/common/theme.ts: -------------------------------------------------------------------------------- 1 | export const theme = { 2 | fontSize: { 3 | h1: '28px', 4 | h2: '22px', 5 | h3: '20px', 6 | caption: '14px', 7 | body: '16px', 8 | smallText: '12px', 9 | textXs: '10px', 10 | }, 11 | colors: { 12 | white: { 13 | 200: '#FFFFFFe3', 14 | 100: '#FFFFFF', 15 | }, 16 | red: { 17 | 200: '#FB7D89', 18 | 100: '#D15861', 19 | }, 20 | purple: { 21 | 300: '#9092FF', 22 | 200: '#817DF7', 23 | 100: '#9793FD', 24 | }, 25 | grey: { 26 | 200: '#30475e', 27 | 100: '#7288a1', 28 | }, 29 | blue: { 30 | 200: '#526D82', 31 | 100: '#4F6B87', 32 | }, 33 | transparent: { 34 | 300: '#9092FF80', 35 | 200: '#FB7D8966', 36 | 100: '#19273666', 37 | }, 38 | black: { 39 | 900: '#0D1B26', 40 | 800: '#192736', 41 | 700: '#00101C', 42 | 200: '#A2A2A2', 43 | }, 44 | }, 45 | borderRadius: { 46 | 2: '2px', 47 | 4: '4px', 48 | 6: '6px', 49 | 8: '8px', 50 | 10: '10px', 51 | 11: '11px', 52 | 12: '12px', 53 | 14: '14px', 54 | 16: '16px', 55 | 17: '17px', 56 | 18: '18px', 57 | 20: '20px', 58 | 24: '24px', 59 | 100: '100px', 60 | }, 61 | spacing: { 62 | 0: '0px', 63 | 1: '1px', 64 | 2: '2px', 65 | 3: '3px', 66 | 4: '4px', 67 | 5: '5px', 68 | 6: '6px', 69 | 7: '7px', 70 | 8: '8px', 71 | 9: '9px', 72 | 10: '10px', 73 | 20: '20px', 74 | 11: '11px', 75 | 12: '12px', 76 | 14: '14px', 77 | 16: '16px', 78 | 24: '24px', 79 | 30: '30px', 80 | 32: '32px', 81 | 38: '38px', 82 | 48: '48px', 83 | 64: '64px', 84 | 80: '80px', 85 | 96: '96px', 86 | }, 87 | } 88 | 89 | export default theme 90 | -------------------------------------------------------------------------------- /src/common/typography.ts: -------------------------------------------------------------------------------- 1 | import { cva } from 'class-variance-authority' 2 | 3 | export const typography = cva('not-italic select-none', { 4 | variants: { 5 | text: { 6 | h1: 'text-h1 font-[400] leading-[30px] tracking-[0.02em]', 7 | h2: 'text-h2 font-[400] leading-[24px] tracking-[0.02em]', 8 | h3: 'text-h3 font-[400] leading-[22px] tracking-[0.02em]', 9 | h2Medium: 'text-h2 font-bold leading-[26px] tracking-[0.02em]', 10 | caption: 'text-caption font-[400] leading-[16px] tracking-[0.02em]', 11 | captionMedium: 'text-caption font-[700] leading-[14px] tracking-[0.02em]', 12 | captionBold: 'text-caption font-bold leading-[14px] tracking-[0.02em]', 13 | body: 'text-body font-[400] leading-[16px] tracking-[0.02em]', 14 | bodyBold: 'text-body font-bold leading-[18px] tracking-[0.02em]', 15 | bodyMedium: 'text-body font-medium leading-[16px] tracking-[0.02em]', 16 | small: 'text-smallText font-[400] leading-[14px] tracking-[0.02em]', 17 | smallBold: 'text-smallText font-bold leading-[14px] tracking-[0.02em]', 18 | tiny: 'font-normal text-textXs leading-[12px] tracking-[0.02em]', 19 | tinyBold: 'text-textXs font-bold leading-[12px] tracking-[0.02em]', 20 | }, 21 | color: { 22 | white: 'text-white-100', 23 | grey: 'text-black-200', 24 | blue: 'text-blue-100', 25 | purple: 'text-purple-100', 26 | lightGrey: 'text-grey-100', 27 | }, 28 | nowrap: { 29 | true: 'whitespace-nowrap', 30 | }, 31 | ellipsis: { 32 | true: 'text-ellipsis overflow-hidden whitespace-nowrap', 33 | }, 34 | }, 35 | defaultVariants: { 36 | text: 'body', 37 | color: 'grey', 38 | }, 39 | }) 40 | -------------------------------------------------------------------------------- /src/components/AppUpdater/index.tsx: -------------------------------------------------------------------------------- 1 | import { Progress } from '@kobalte/core' 2 | import { relaunch } from '@tauri-apps/api/process' 3 | import { checkUpdate, installUpdate, type UpdateResult } from '@tauri-apps/api/updater' 4 | import { createEffect, createSignal, Show } from 'solid-js' 5 | import './styles.css' 6 | 7 | const Updater = () => { 8 | const [updating, setUpdating] = createSignal(false) 9 | const [updateAvailable, setUpdateAvailable] = createSignal() 10 | 11 | createEffect(() => { 12 | checkUpdate().then((updateResult) => setUpdateAvailable(updateResult)) 13 | }) 14 | 15 | const handleUpdate = () => { 16 | setUpdating(true) 17 | /* showNotification({ 18 | title: t('Installing update v{{ v }}', { v: newVersion }), 19 | message: t('Will relaunch afterwards'), 20 | autoClose: false, 21 | }) */ 22 | installUpdate().then(relaunch) 23 | } 24 | 25 | return ( 26 |
27 | 28 | 29 |
30 | Loading... 31 | 32 |
33 | 34 | 35 | 36 |
37 |
38 | 39 | 40 | 41 |
42 | ) 43 | } 44 | 45 | export default Updater 46 | -------------------------------------------------------------------------------- /src/components/AppUpdater/styles.css: -------------------------------------------------------------------------------- 1 | .progress { 2 | display: flex; 3 | flex-direction: column; 4 | gap: 2px; 5 | width: 300px; 6 | } 7 | .progress__label-container { 8 | display: flex; 9 | justify-content: space-between; 10 | } 11 | .progress__label, 12 | .progress__value-label { 13 | color: hsl(240 4% 16%); 14 | font-size: 14px; 15 | } 16 | .progress__track { 17 | height: 10px; 18 | background-color: hsl(240 6% 90%); 19 | } 20 | .progress__fill { 21 | background-color: hsl(200 98% 39%); 22 | height: 100%; 23 | width: var(--kb-progress-fill-width); 24 | transition: width 250ms linear; 25 | } 26 | .progress__fill[data-progress="complete"] { 27 | background-color: #16a34a; 28 | } -------------------------------------------------------------------------------- /src/components/Board/DefaultBoard/index.tsx: -------------------------------------------------------------------------------- 1 | import Typography from '@components/Typography' 2 | import { Component, Show } from 'solid-js' 3 | 4 | export interface IProps { 5 | onClick: () => void 6 | label: string 7 | description?: string 8 | isActive?: boolean 9 | } 10 | 11 | export const DefaultBoard: Component = (props) => { 12 | return ( 13 | 34 | ) 35 | } 36 | 37 | export default DefaultBoard 38 | -------------------------------------------------------------------------------- /src/components/Board/SelectBoard/index.tsx: -------------------------------------------------------------------------------- 1 | import { SelectButton } from '@components/Buttons/SelectButton' 2 | import Dropdown from '@components/Dropdown/Dropdown' 3 | import DropdownList from '@components/Dropdown/DropdownList' 4 | import Typography from '@components/Typography' 5 | import { type IDropdownList } from '@interfaces/interfaces' 6 | import { Component, Show } from 'solid-js' 7 | 8 | export interface IProps { 9 | boards: IDropdownList[] 10 | onSubmit: (board: string) => void 11 | selectedBoard: string 12 | firmwareVersion: string 13 | } 14 | 15 | export const SelectBoard: Component = (props) => { 16 | return ( 17 |
18 |
19 | 20 | 26 | { 34 | props.onSubmit(data.label) 35 | }} 36 | /> 37 | 38 |
39 | 40 | Firmware version: 41 | 42 | }> 45 | 46 | {props.firmwareVersion} 47 | 48 | 49 |
50 |
51 |
52 | ) 53 | } 54 | -------------------------------------------------------------------------------- /src/components/Buttons/Button/index.tsx: -------------------------------------------------------------------------------- 1 | import Typography from '@components/Typography' 2 | import { Component, Show } from 'solid-js' 3 | 4 | export interface IProps { 5 | type?: 'submit' | 'reset' | 'button' | undefined 6 | isLoadingPrimaryButton?: boolean 7 | disabled?: boolean 8 | size?: string 9 | isActive?: boolean 10 | label: string 11 | isLoader?: boolean 12 | onClick?: () => void 13 | } 14 | 15 | export const Button: Component = (props) => { 16 | return ( 17 | 46 | ) 47 | } 48 | 49 | export default Button 50 | -------------------------------------------------------------------------------- /src/components/Buttons/Checkbox/index.tsx: -------------------------------------------------------------------------------- 1 | import Checkbox from '@components/Checkbox' 2 | import Typography from '@components/Typography' 3 | import { Component } from 'solid-js' 4 | 5 | export interface IProps { 6 | onClick: () => void 7 | checked: boolean 8 | label: string 9 | } 10 | 11 | const CheckboxButton: Component = (props) => { 12 | return ( 13 |
{ 16 | props.onClick() 17 | }}> 18 | 19 | 20 | Don’t show this again 21 | 22 |
23 | ) 24 | } 25 | 26 | export default CheckboxButton 27 | -------------------------------------------------------------------------------- /src/components/Buttons/DropdownButton/index.tsx: -------------------------------------------------------------------------------- 1 | import Typography from '@components/Typography' 2 | import { Component } from 'solid-js' 3 | 4 | export interface IProps { 5 | label: string 6 | } 7 | 8 | export const DropdownButton: Component = (props) => { 9 | return ( 10 | 15 | ) 16 | } 17 | 18 | export default DropdownButton 19 | -------------------------------------------------------------------------------- /src/components/Buttons/SelectButton/index.tsx: -------------------------------------------------------------------------------- 1 | import Typography from '@components/Typography' 2 | import { Component } from 'solid-js' 3 | 4 | export interface IProps { 5 | type?: 'submit' | 'reset' | 'button' | undefined 6 | label: string 7 | onClick?: () => void 8 | tabIndex?: number 9 | header: string 10 | } 11 | 12 | export const SelectButton: Component = (props) => { 13 | return ( 14 |
15 | 16 | {props.header} 17 | 18 | 30 |
31 | ) 32 | } 33 | 34 | export default SelectButton 35 | -------------------------------------------------------------------------------- /src/components/Checkbox/index.tsx: -------------------------------------------------------------------------------- 1 | import { Component } from 'solid-js' 2 | 3 | export interface IProps { 4 | checked: boolean 5 | } 6 | 7 | const Checkbox: Component = (props) => { 8 | return ( 9 |
13 | ) 14 | } 15 | 16 | export default Checkbox 17 | -------------------------------------------------------------------------------- /src/components/Dropdown/Dropdown/index.tsx: -------------------------------------------------------------------------------- 1 | import { type IEventType } from '@interfaces/types' 2 | import { classNames } from '@src/utils' 3 | import { type ParentComponent } from 'solid-js' 4 | 5 | export interface IProps { 6 | onFocusOut?: (event: IEventType) => void 7 | styles?: string 8 | } 9 | 10 | const Dropdown: ParentComponent = (props) => { 11 | return ( 12 |
props.onFocusOut?.(el)}> 15 | {props.children} 16 |
17 | ) 18 | } 19 | 20 | export default Dropdown 21 | -------------------------------------------------------------------------------- /src/components/Dropdown/DropdownList/index.tsx: -------------------------------------------------------------------------------- 1 | import { DefaultBoard } from '@components/Board/DefaultBoard' 2 | import Typography from '@components/Typography' 3 | import { IDropdownList } from '@interfaces/interfaces' 4 | import { classNames } from '@src/utils' 5 | import { Component, For, Show } from 'solid-js' 6 | 7 | export interface IProps { 8 | onClick: (data: IDropdownList) => void 9 | ref?: (el: HTMLDivElement) => void 10 | activeElement: string 11 | data: IDropdownList[] 12 | styles?: string 13 | tabIndex?: number 14 | fallbackLabel?: string 15 | isScrollbar?: boolean 16 | disableTop?: boolean 17 | } 18 | 19 | const DropdownList: Component = (props) => { 20 | return ( 21 |
props.ref?.(el)} 24 | class={classNames( 25 | props.styles, 26 | 'dropdown-content right-[-13px] p-12 rounded-12 border border-solid border-black-800 bg-black-900 w-[350px]', 27 | )} 28 | style={!props.disableTop ? { top: 'calc(100% + 20px)' } : undefined}> 29 |
32 | 36 | 37 | 38 | {props.fallbackLabel ?? 'Loading...'} 39 | 40 |
41 | }> 42 | 43 | {(data) => ( 44 | { 48 | props.onClick(data) 49 | }} 50 | /> 51 | )} 52 | 53 | 54 |
55 |
56 | ) 57 | } 58 | 59 | export default DropdownList 60 | -------------------------------------------------------------------------------- /src/components/Dropdown/PortDropdown/index.tsx: -------------------------------------------------------------------------------- 1 | import { DropdownButton } from '@components/Buttons/DropdownButton' 2 | import Dropdown from '@components/Dropdown/Dropdown' 3 | import DropdownList from '@components/Dropdown/DropdownList' 4 | import { IDropdownList } from '@interfaces/interfaces' 5 | import { classNames } from '@src/utils' 6 | import { Component } from 'solid-js' 7 | 8 | export interface IProps { 9 | onClick: (port: IDropdownList) => void 10 | label: string 11 | isScrollbar: boolean 12 | activeElement: string 13 | data: IDropdownList[] 14 | class?: string 15 | } 16 | 17 | const PortDropdown: Component = (props) => { 18 | return ( 19 | 20 | 21 | 31 | 32 | ) 33 | } 34 | 35 | export default PortDropdown 36 | -------------------------------------------------------------------------------- /src/components/Footer/index.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@components/Buttons/Button' 2 | import { classNames } from '@src/utils' 3 | import { Component, Show } from 'solid-js' 4 | 5 | export interface IProps { 6 | onClickSecond?: () => void 7 | onClickPrimary?: () => void 8 | isPrimaryActive?: boolean 9 | isSecondActive?: boolean 10 | primaryLabel?: string 11 | secondLabel?: string 12 | hidePrimaryButton?: boolean 13 | isPrimaryButtonDisabled?: boolean 14 | isSecondButtonDisabled?: boolean 15 | isLoadingPrimaryButton?: boolean 16 | size?: string 17 | primaryType?: 'submit' | 'reset' | 'button' | undefined 18 | secondType?: 'submit' | 'reset' | 'button' | undefined 19 | styles?: string 20 | } 21 | 22 | export const Footer: Component = (props) => { 23 | return ( 24 |
25 | 26 |
47 | ) 48 | } 49 | -------------------------------------------------------------------------------- /src/components/Header/index.tsx: -------------------------------------------------------------------------------- 1 | import { ProgressBar } from '@components/ProgressBar' 2 | import Typography from '@components/Typography' 3 | import { Image } from '@kobalte/core' 4 | import { Component } from 'solid-js' 5 | 6 | interface IProps { 7 | onClick: () => void 8 | step: { step: string; description: string; dashoffset: string; index: string } 9 | currentStep: string 10 | name: string 11 | } 12 | 13 | const MainHeader: Component = (props) => { 14 | return ( 15 |
16 |
17 |
{ 20 | props.onClick() 21 | }}> 22 |
23 |
24 | 25 | 31 | 32 |
33 | 34 | EyetrackVR 35 | 36 |
37 |
38 |
39 | 43 |
44 | 45 | {props.step.step} 46 | 47 | 48 | {props.step.description} 49 | 50 |
51 |
52 |
53 |
54 | ) 55 | } 56 | 57 | export default MainHeader 58 | -------------------------------------------------------------------------------- /src/components/Inputs/Input/index.tsx: -------------------------------------------------------------------------------- 1 | import type { Component } from 'solid-js' 2 | 3 | interface IProps { 4 | onChange: (value: string) => void 5 | placeholder: string 6 | type?: string 7 | id?: string 8 | required?: boolean 9 | value: string 10 | autoFocus?: boolean 11 | autoComplete?: string 12 | } 13 | 14 | const Input: Component = (props) => { 15 | return ( 16 | { 21 | props.onChange(e.currentTarget.value) 22 | e.currentTarget.value = props.value 23 | }} 24 | placeholder={props.placeholder} 25 | type={props.type} 26 | name={props.id} 27 | value={props.value} 28 | id={props.id} 29 | required={props.required} 30 | /> 31 | ) 32 | } 33 | 34 | export default Input 35 | -------------------------------------------------------------------------------- /src/components/Inputs/NetworkInput/index.tsx: -------------------------------------------------------------------------------- 1 | import Typography from '@components/Typography' 2 | import { createSignal, type Component } from 'solid-js' 3 | 4 | interface IProps { 5 | onChange: (value: string) => void 6 | placeholder: string 7 | autoFocus?: boolean 8 | type?: string 9 | id?: string 10 | value: string 11 | } 12 | 13 | const NetworkInput: Component = (props) => { 14 | const [active, setActive] = createSignal(false) 15 | 16 | return ( 17 |
23 | 24 | http:// 25 | 26 | setActive(true)} 30 | onBlur={() => setActive(false)} 31 | class="h-full pl-2 pr-2 bg-black-800 w-full border-solid border-0 focus:ring-0 focus:ring-black-800 border-black-800 placeholder-white-100 text-[12px] text-white-100 focus:border-0 focus:border-black-800" 32 | onInput={(e) => { 33 | props.onChange(e.currentTarget.value) 34 | e.currentTarget.value = props.value 35 | }} 36 | placeholder={props.placeholder} 37 | type={props.type} 38 | name={props.id} 39 | value={props.value} 40 | id={props.id} 41 | /> 42 | 43 | .local 44 | 45 |
46 | ) 47 | } 48 | 49 | export default NetworkInput 50 | -------------------------------------------------------------------------------- /src/components/Inputs/PasswordInput/index.tsx: -------------------------------------------------------------------------------- 1 | import { FaRegularEye, FaRegularEyeSlash } from 'solid-icons/fa' 2 | import { createSignal, type Component } from 'solid-js' 3 | 4 | interface IProps { 5 | onChange: (value: string) => void 6 | placeholder: string 7 | value: string 8 | autoFocus?: boolean 9 | required?: boolean 10 | } 11 | 12 | const PasswordInput: Component = (props) => { 13 | const [active, setActive] = createSignal(false) 14 | const [showPassword, setShowPassword] = createSignal(false) 15 | 16 | return ( 17 |
23 | setActive(true)} 27 | onBlur={() => setActive(false)} 28 | class="h-full pl-6 pr-6 bg-black-800 w-full rounded-6 border-solid border-0 focus:ring-0 focus:ring-black-800 border-black-800 placeholder-white-100 text-[12px] text-white-100 focus:border-0 focus:border-black-800" 29 | onInput={(e) => { 30 | props.onChange(e.currentTarget.value) 31 | e.currentTarget.value = props.value 32 | }} 33 | placeholder={props.placeholder} 34 | value={props.value} 35 | type={showPassword() ? 'text' : 'password'} 36 | /> 37 |
setShowPassword(!showPassword())}> 41 | {!showPassword() ? : } 42 |
43 |
44 | ) 45 | } 46 | 47 | export default PasswordInput 48 | -------------------------------------------------------------------------------- /src/components/Modal/index.tsx: -------------------------------------------------------------------------------- 1 | import { Titlebar } from '@components/Titlebar' 2 | import { type TITLEBAR_ACTION } from '@src/static/types/enums' 3 | import { Component, createEffect, JSX } from 'solid-js' 4 | 5 | export interface IProps { 6 | onClickHeader: (action: TITLEBAR_ACTION) => void 7 | onClickCloseModal: () => void 8 | isActive: boolean 9 | id: string 10 | children: JSX.Element 11 | isSending?: boolean 12 | } 13 | 14 | export const Modal: Component = (props) => { 15 | createEffect(() => { 16 | if (props.isActive) { 17 | const el = document.getElementById(props.id) 18 | if (el instanceof HTMLDialogElement) { 19 | el.showModal() 20 | } 21 | } 22 | }) 23 | 24 | return ( 25 | 26 | 27 | 32 | 41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /src/components/ModalHeader/index.tsx: -------------------------------------------------------------------------------- 1 | import Typography from '@components/Typography' 2 | import theme from '@src/common/theme' 3 | import { FaSolidXmark } from 'solid-icons/fa' 4 | import { type Component } from 'solid-js' 5 | 6 | export interface IProps { 7 | onClick?: () => void 8 | disabled?: boolean 9 | label: string 10 | } 11 | 12 | const ModalHeader: Component = (props) => { 13 | return ( 14 |
15 | 16 | {props.label} 17 | 18 | 31 |
32 | ) 33 | } 34 | 35 | export default ModalHeader 36 | -------------------------------------------------------------------------------- /src/components/Notifications/CustomToast/index.tsx: -------------------------------------------------------------------------------- 1 | import Typography from '@components/Typography' 2 | import { ENotificationType } from '@src/static/types/enums' 3 | import type { Notifications } from '@src/static/types/interfaces' 4 | import { useAppNotificationsContext } from '@src/store/context/notifications' 5 | import { Alert, Toast, Transition } from 'solid-headless' 6 | import { AiOutlineCheckCircle } from 'solid-icons/ai' 7 | import { FiAlertOctagon, FiAlertTriangle } from 'solid-icons/fi' 8 | import { IoAlertCircleSharp, IoCloseSharp } from 'solid-icons/io' 9 | import { Component, createSignal, Show } from 'solid-js' 10 | 11 | interface ToastProps { 12 | id: string 13 | notif: Notifications 14 | } 15 | 16 | const CustomToast: Component = (props) => { 17 | const [isOpen, setIsOpen] = createSignal(true) 18 | 19 | const { getNotifications } = useAppNotificationsContext() 20 | 21 | const dismiss = () => { 22 | setIsOpen(false) 23 | } 24 | 25 | return ( 26 | { 36 | getNotifications()?.remove(props.id) 37 | }}> 38 | 39 | 40 |
41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
54 | 55 | {props.notif.message} 56 | 57 | 63 |
64 |
65 |
66 | ) 67 | } 68 | 69 | export default CustomToast 70 | -------------------------------------------------------------------------------- /src/components/Notifications/index.tsx: -------------------------------------------------------------------------------- 1 | import { Notifications } from '@src/static/types/interfaces' 2 | import { useAppNotificationsContext } from '@src/store/context/notifications' 3 | import { Toaster, ToasterStore, Transition, useToaster } from 'solid-headless' 4 | import { createEffect, createSignal, For, onCleanup } from 'solid-js' 5 | import { debug } from 'tauri-plugin-log-api' 6 | import CustomToast from './CustomToast' 7 | 8 | const ToastNotificationWindow = () => { 9 | const { getNotifications, getEnableNotifications } = useAppNotificationsContext() 10 | const notifs = useToaster(getNotifications() as ToasterStore) 11 | const [isOpen, setIsOpen] = createSignal(false) 12 | const closeNotifs = () => { 13 | setIsOpen(false) 14 | } 15 | const clearNotifs = () => { 16 | getNotifications()?.clear() 17 | } 18 | createEffect(() => { 19 | if (getEnableNotifications()) { 20 | if (notifs().length > 0) { 21 | debug(`[Notifications]: Notifications Added - ${notifs().length}`) 22 | setIsOpen(true) 23 | } 24 | const timeout = setTimeout(() => { 25 | closeNotifs() 26 | debug('[Notifications] Closed - Cleaned up') 27 | }, 5000) 28 | onCleanup(() => { 29 | clearTimeout(timeout) 30 | }) 31 | } 32 | }) 33 | return ( 34 | 35 | 45 |
46 | 47 | {(item) => { 48 | debug(`[Notifications]: Rendering - ${item.id}`) 49 | return 50 | }} 51 | 52 |
53 |
54 |
55 | ) 56 | } 57 | 58 | export default ToastNotificationWindow 59 | -------------------------------------------------------------------------------- /src/components/ProgressBar/index.tsx: -------------------------------------------------------------------------------- 1 | import Typography from '@components/Typography' 2 | import { radius } from '@src/static' 3 | import { Component } from 'solid-js' 4 | import './styles.css' 5 | 6 | export interface IProps { 7 | currentStep: string 8 | dashoffset: string 9 | } 10 | 11 | export const ProgressBar: Component = (props) => { 12 | return ( 13 |
14 |
15 | 16 | {props.currentStep} 17 | 18 |
19 | 20 | 29 | 39 | 40 |
41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /src/components/ProgressBar/styles.css: -------------------------------------------------------------------------------- 1 | #svg circle { 2 | transition: stroke-dashoffset 1s linear; 3 | stroke: #192736; 4 | } 5 | 6 | #svg #bar { 7 | stroke: #9092FF; 8 | } 9 | 10 | 11 | .step { 12 | position: absolute; 13 | left: 50%; 14 | top: 50%; 15 | transform: translate(-50%,-50%); 16 | } -------------------------------------------------------------------------------- /src/components/SelectNetwork/index.tsx: -------------------------------------------------------------------------------- 1 | import Input from '@components/Inputs/Input' 2 | import NetworkInput from '@components/Inputs/NetworkInput' 3 | import PasswordInput from '@components/Inputs/PasswordInput' 4 | import Typography from '@components/Typography' 5 | import { shortMdnsAddress } from '@src/utils' 6 | import { Component } from 'solid-js' 7 | 8 | export interface IProps { 9 | ssid: string 10 | password: string 11 | mdns: string 12 | onChangeSSID: (ssid: string) => void 13 | onChangePassword: (password: string) => void 14 | onChangeMdns: (mdns: string) => void 15 | } 16 | 17 | export const SelectNetwork: Component = (props) => { 18 | return ( 19 |
22 |
23 | 24 | Tracker name 25 | 26 | 27 | {`The tracker will be accessible under: http://${ 28 | !props.mdns ? 'openiristracker' : shortMdnsAddress(props.mdns) 29 | }.local`} 30 | 31 |
32 | 39 |
40 |
41 |
42 | 43 | SSID 44 | 45 |
46 | 56 |
57 |
58 |
59 | 60 | Password 61 | 62 |
63 | 69 |
70 |
71 |
72 | ) 73 | } 74 | -------------------------------------------------------------------------------- /src/components/Terminal/Firmware/index.tsx: -------------------------------------------------------------------------------- 1 | import Typography from '@components/Typography' 2 | import { Component } from 'solid-js' 3 | 4 | export interface IProps { 5 | version: string 6 | board: string 7 | } 8 | 9 | const Firmware: Component = (props) => { 10 | return ( 11 |
12 | 13 | {props.version} {`${!props.board.trim() ? '' : `| ${props.board}`}`} 14 | 15 |
16 | ) 17 | } 18 | 19 | export default Firmware 20 | -------------------------------------------------------------------------------- /src/components/Terminal/Step/index.tsx: -------------------------------------------------------------------------------- 1 | import Typography from '@components/Typography' 2 | import { FLASH_STATUS } from '@interfaces/enums' 3 | import { IFirmwareState } from '@interfaces/interfaces' 4 | import theme from '@src/common/theme' 5 | import { OcCheckcircle2, OcCircleslash2, OcX } from 'solid-icons/oc' 6 | import { RiArrowsArrowDropRightLine } from 'solid-icons/ri' 7 | import { Component, Show } from 'solid-js' 8 | 9 | export interface IProps extends IFirmwareState { 10 | onMouseDown: () => void 11 | hasLogs: boolean 12 | open: boolean 13 | hover: boolean 14 | progress?: number 15 | } 16 | 17 | const Step: Component = (props) => { 18 | return ( 19 |
20 |
24 |
{ 26 | if (!props.hasLogs) return 27 | props.onMouseDown() 28 | }} 29 | class="p-8 w-full pr-8 h-[44px] rounded-[6px] flex items-center justify-start gap-12" 30 | classList={{ 31 | 'cursor-pointer': props.hasLogs, 32 | 'border border-solid border-[#00101C]': props.status !== FLASH_STATUS.FAILED, 33 | 'border border-solid border-[#FB7D89]': props.status === FLASH_STATUS.FAILED, 34 | 'bg-black-800': props.open && props.status !== FLASH_STATUS.FAILED, 35 | 'bg-[#00101C]': !props.hover && !props.open, 36 | 'bg-[#FB7D8966]': 37 | (props.hover || props.open) && props.status === FLASH_STATUS.FAILED, 38 | 'bg-[#19273666]': 39 | props.hover && !props.open && props.status !== FLASH_STATUS.FAILED, 40 | }}> 41 |
42 |
43 | 54 |
55 |
56 | {props.status === FLASH_STATUS.SUCCESS ? ( 57 | 58 | ) : props.status === FLASH_STATUS.FAILED ? ( 59 | 60 | ) : props.status === FLASH_STATUS.ABORTED ? ( 61 | 62 | ) : ( 63 | 64 | )} 65 |
66 |
67 |
68 | 69 | {props.label} 70 | 71 | 72 | 73 | {props.progress}% 74 | 75 | 76 |
77 |
78 |
79 | ) 80 | } 81 | 82 | export default Step 83 | -------------------------------------------------------------------------------- /src/components/Terminal/TerminalHeader/index.tsx: -------------------------------------------------------------------------------- 1 | import Typography from '@components/Typography' 2 | import theme from '@src/common/theme' 3 | import { ImBooks } from 'solid-icons/im' 4 | import { TbTerminal2 } from 'solid-icons/tb' 5 | import { Component } from 'solid-js' 6 | 7 | export interface IProps { 8 | onClickOpenDocs: () => void 9 | } 10 | 11 | const TerminalHeader: Component = (props) => { 12 | return ( 13 |
14 |
15 | 16 | 17 | Serial Terminal 18 | 19 |
20 |
{ 23 | props.onClickOpenDocs() 24 | }}> 25 | 26 |
27 |
28 | ) 29 | } 30 | 31 | export default TerminalHeader 32 | -------------------------------------------------------------------------------- /src/components/Titlebar/index.tsx: -------------------------------------------------------------------------------- 1 | import { TITLEBAR_ACTION } from '@src/static/types/enums' 2 | import { Component } from 'solid-js' 3 | 4 | export interface IProps { 5 | onClickHeader: (action: TITLEBAR_ACTION) => void 6 | } 7 | 8 | export const Titlebar: Component = (props) => { 9 | return ( 10 |
11 |
{ 14 | props.onClickHeader(TITLEBAR_ACTION.MINIMIZE) 15 | }}> 16 | 17 | 18 | 19 |
20 |
{ 23 | props.onClickHeader(TITLEBAR_ACTION.MAXIMIZE) 24 | }}> 25 | 26 | 27 | 28 |
29 |
{ 32 | props.onClickHeader(TITLEBAR_ACTION.CLOSE) 33 | }}> 34 | 35 | 39 | 40 |
41 |
42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /src/components/Typography/index.tsx: -------------------------------------------------------------------------------- 1 | import { typography } from '@common/typography' 2 | import { VariantProps } from 'class-variance-authority' 3 | import { createMemo, ParentComponent } from 'solid-js' 4 | 5 | export interface IProps { 6 | classList?: { 7 | [k: string]: boolean | undefined 8 | } 9 | class?: string 10 | } 11 | 12 | export interface TypographyProps extends IProps, VariantProps {} 13 | 14 | const Typography: ParentComponent = (props) => { 15 | const styles = createMemo(() => { 16 | return { 17 | ...props, 18 | class: props?.class ?? '', 19 | classList: props?.classList ?? {}, 20 | } 21 | }) 22 | 23 | return ( 24 |

25 | {props.children} 26 |

27 | ) 28 | } 29 | 30 | export default Typography 31 | -------------------------------------------------------------------------------- /src/containers/404/[...404].tsx: -------------------------------------------------------------------------------- 1 | import Typography from '@components/Typography' 2 | 3 | const page404 = () => { 4 | return ( 5 |
6 | 7 | 404: Not Found 8 | 9 | 10 | Welp, something went wrong 😞 11 | 12 |
13 | ) 14 | } 15 | 16 | export default page404 17 | -------------------------------------------------------------------------------- /src/containers/Header/index.tsx: -------------------------------------------------------------------------------- 1 | import MainHeader from '@components/Header' 2 | import { useLocation, useNavigate } from '@solidjs/router' 3 | import { stepStatus, usb } from '@src/static' 4 | import { DIRECTION, ENotificationType } from '@src/static/types/enums' 5 | import { useAppAPIContext } from '@store/context/api' 6 | import { useAppNotificationsContext } from '@store/context/notifications' 7 | import { isActiveProcess } from '@store/terminal/selectors' 8 | import { setAbortController } from '@store/terminal/terminal' 9 | import { createMemo } from 'solid-js' 10 | 11 | export const Header = () => { 12 | const location = useLocation() 13 | const navigate = useNavigate() 14 | 15 | const { addNotification } = useAppNotificationsContext() 16 | const { activeBoard } = useAppAPIContext() 17 | 18 | const isUSBBoard = createMemo(() => { 19 | return activeBoard().includes(usb) ? 1 : 0 20 | }) 21 | 22 | const step = createMemo(() => { 23 | const index = stepStatus[DIRECTION[location.pathname]].index - isUSBBoard() 24 | return index <= 0 ? 1 : index 25 | }) 26 | 27 | return ( 28 | { 35 | if (isActiveProcess()) { 36 | addNotification({ 37 | title: 'There is an active installation. Please wait.', 38 | message: 'There is an active installation. Please wait.', 39 | type: ENotificationType.INFO, 40 | }) 41 | return true 42 | } 43 | setAbortController() 44 | navigate('/') 45 | }} 46 | currentStep={`${step()}/${Object.values(stepStatus).length - isUSBBoard()} `} 47 | /> 48 | ) 49 | } 50 | -------------------------------------------------------------------------------- /src/containers/ManageNetwork/index.tsx: -------------------------------------------------------------------------------- 1 | import { NetworkManagement } from '@pages/NetworkManagement' 2 | import { useNavigate } from '@solidjs/router' 3 | import { staticMdns } from '@src/static' 4 | import { useAppAPIContext } from '@store/context/api' 5 | 6 | export const ManageNetwork = () => { 7 | const navigate = useNavigate() 8 | const { ssid, password, setNetwork, mdns } = useAppAPIContext() 9 | const { loader } = useAppAPIContext() 10 | 11 | return ( 12 | { 18 | navigate('/') 19 | }} 20 | onSubmit={(ssid, password, trackerName) => { 21 | if (loader()) return 22 | const mdns = !trackerName ? staticMdns : trackerName 23 | setNetwork(ssid, password, mdns) 24 | navigate('/flashFirmware') 25 | }} 26 | /> 27 | ) 28 | } 29 | 30 | export default ManageNetwork 31 | -------------------------------------------------------------------------------- /src/containers/Modals/ApModeModalContainer.tsx: -------------------------------------------------------------------------------- 1 | import { ENotificationType, MODAL_TYPE, TITLEBAR_ACTION } from '@interfaces/enums' 2 | import ApModeModal from '@pages/Modals/ApModeModal' 3 | import { useAppAPIContext } from '@store/context/api' 4 | import { useAppNotificationsContext } from '@store/context/notifications' 5 | import { useAppUIContext } from '@store/context/ui' 6 | import { listen } from '@tauri-apps/api/event' 7 | import { appWindow } from '@tauri-apps/api/window' 8 | import { createEffect, createSignal, onCleanup } from 'solid-js' 9 | import { debug } from 'tauri-plugin-log-api' 10 | 11 | const ApModeContainer = () => { 12 | const { modal, setOpenModal } = useAppUIContext() 13 | const { addNotification } = useAppNotificationsContext() 14 | const { ssid, password, useRequestHook } = useAppAPIContext() 15 | const [response, setResponse] = createSignal() 16 | 17 | const configureAPConnection = async () => { 18 | addNotification({ 19 | title: 'Making request', 20 | message: 'Making request', 21 | type: ENotificationType.INFO, 22 | }) 23 | debug(`ssid: ${ssid()}`) 24 | debug(`pass: ${password()}`) 25 | debug(`confirmPass: ${password()}`) 26 | 27 | //* Check if there is a response from the device 28 | await useRequestHook('ping', '192.168.4.1') 29 | 30 | if (response()!['msg'] !== 'ok') { 31 | addNotification({ 32 | title: 'Error', 33 | message: 34 | 'Could not connect to device, please connect your PC to the EyeTrackVR Access Point and try again.', 35 | type: ENotificationType.ERROR, 36 | }) 37 | return 38 | } 39 | 40 | //* Make Request to set network settings 41 | await useRequestHook( 42 | 'wifi', 43 | '192.168.4.1', 44 | `?ssid=${ssid()}&password=${password()}&networkName=${ssid()}&channel=1&power=52&adhoc=0`, 45 | ) 46 | 47 | //* Trigger save of network settings 48 | addNotification({ 49 | title: 'Success', 50 | message: response()!['msg'], 51 | type: ENotificationType.SUCCESS, 52 | }) 53 | await useRequestHook('save', '192.168.4.1') 54 | } 55 | 56 | const _listen = async () => { 57 | const unlisten = await listen('request-response', (event) => { 58 | const parsedResponse = JSON.parse(event.payload) 59 | setResponse(parsedResponse) 60 | debug(`[NetworkSettings]: ${JSON.stringify(parsedResponse)}`) 61 | }) 62 | return unlisten 63 | } 64 | 65 | const listenToResponse = async () => { 66 | const unlisten = await _listen() 67 | onCleanup(unlisten) 68 | } 69 | 70 | createEffect(() => { 71 | if (modal().type === MODAL_TYPE.AP_MODE) { 72 | listenToResponse().catch(console.error) 73 | } 74 | }) 75 | 76 | return ( 77 | { 80 | switch (action) { 81 | case TITLEBAR_ACTION.MINIMIZE: 82 | appWindow.minimize() 83 | break 84 | case TITLEBAR_ACTION.MAXIMIZE: 85 | appWindow.toggleMaximize() 86 | break 87 | case TITLEBAR_ACTION.CLOSE: 88 | appWindow.close() 89 | break 90 | default: 91 | return 92 | } 93 | }} 94 | onClickClose={() => { 95 | setOpenModal({ open: false, type: MODAL_TYPE.NONE }) 96 | }} 97 | onClick={() => { 98 | configureAPConnection().catch(() => { 99 | addNotification({ 100 | title: 'AP Mode configuration failed', 101 | message: 'Failed to configure AP Mode', 102 | type: ENotificationType.ERROR, 103 | }) 104 | }) 105 | }} 106 | /> 107 | ) 108 | } 109 | 110 | export default ApModeContainer 111 | -------------------------------------------------------------------------------- /src/containers/Modals/BeforeFlashingContainer.tsx: -------------------------------------------------------------------------------- 1 | import { ENotificationType, MODAL_TYPE, TITLEBAR_ACTION } from '@interfaces/enums' 2 | import BeforeFlashingModal from '@pages/Modals/BeforeFlashingModal' 3 | import { usb } from '@src/static' 4 | import { useAppAPIContext } from '@store/context/api' 5 | import { useAppNotificationsContext } from '@store/context/notifications' 6 | import { useAppUIContext } from '@store/context/ui' 7 | import { installOpenIris } from '@store/terminal/actions' 8 | import { isActiveProcess } from '@store/terminal/selectors' 9 | import { 10 | restartFirmwareState, 11 | setAbortController, 12 | setProcessStatus, 13 | } from '@store/terminal/terminal' 14 | import { appWindow } from '@tauri-apps/api/window' 15 | import { createMemo } from 'solid-js' 16 | 17 | const BeforeFlashingContainer = () => { 18 | const { downloadAsset, getFirmwareType, activeBoard, activePort } = useAppAPIContext() 19 | const { modal, setOpenModal, hideModal, setHideModal } = useAppUIContext() 20 | const { addNotification } = useAppNotificationsContext() 21 | 22 | const isUSBBoard = createMemo(() => { 23 | return activeBoard().includes(usb) 24 | }) 25 | 26 | const activePortName = createMemo(() => { 27 | return activePort().activePortName 28 | }) 29 | 30 | return ( 31 | { 35 | switch (action) { 36 | case TITLEBAR_ACTION.MINIMIZE: 37 | appWindow.minimize() 38 | break 39 | case TITLEBAR_ACTION.MAXIMIZE: 40 | appWindow.toggleMaximize() 41 | break 42 | case TITLEBAR_ACTION.CLOSE: 43 | appWindow.close() 44 | break 45 | default: 46 | return 47 | } 48 | }} 49 | onClickClose={() => { 50 | setOpenModal({ open: false, type: MODAL_TYPE.NONE }) 51 | }} 52 | onClickCheckbox={() => { 53 | setHideModal() 54 | }} 55 | onClickInstallOpeniris={() => { 56 | setOpenModal({ open: false, type: MODAL_TYPE.NONE }) 57 | if (isActiveProcess()) { 58 | addNotification({ 59 | title: 'There is an active installation. Please wait.', 60 | message: 'There is an active installation. Please wait.', 61 | type: ENotificationType.INFO, 62 | }) 63 | return true 64 | } 65 | setAbortController('openiris') 66 | setProcessStatus(true) 67 | restartFirmwareState() 68 | installOpenIris( 69 | isUSBBoard(), 70 | activePortName(), 71 | async () => { 72 | await downloadAsset(getFirmwareType()) 73 | }, 74 | () => { 75 | setOpenModal({ open: true, type: MODAL_TYPE.UPDATE_NETWORK }) 76 | }, 77 | ).catch(() => ({})) 78 | }} 79 | /> 80 | ) 81 | } 82 | 83 | export default BeforeFlashingContainer 84 | -------------------------------------------------------------------------------- /src/containers/Modals/BeforeSelectBoardContainer.tsx: -------------------------------------------------------------------------------- 1 | import { MODAL_TYPE, TITLEBAR_ACTION } from '@interfaces/enums' 2 | import BeforeSelectBoard from '@pages/Modals/BeforeSelectBoard' 3 | import { useAppAPIContext } from '@store/context/api' 4 | import { useAppUIContext } from '@store/context/ui' 5 | import { setIsSoftwareDownloaded } from '@store/terminal/terminal' 6 | import { appWindow } from '@tauri-apps/api/window' 7 | 8 | const BeforeSelectBoardContainer = () => { 9 | const { confirmFirmwareSelection } = useAppAPIContext() 10 | const { modal, setOpenModal } = useAppUIContext() 11 | 12 | return ( 13 | { 16 | switch (action) { 17 | case TITLEBAR_ACTION.MINIMIZE: 18 | appWindow.minimize() 19 | break 20 | case TITLEBAR_ACTION.MAXIMIZE: 21 | appWindow.toggleMaximize() 22 | break 23 | case TITLEBAR_ACTION.CLOSE: 24 | appWindow.close() 25 | break 26 | default: 27 | return 28 | } 29 | }} 30 | onClickClose={() => { 31 | setOpenModal({ open: false, type: MODAL_TYPE.NONE }) 32 | }} 33 | onClickConfirmBoard={() => { 34 | const board = modal()?.board 35 | if (board) { 36 | setIsSoftwareDownloaded(false) 37 | confirmFirmwareSelection(board) 38 | } 39 | setOpenModal({ open: false, type: MODAL_TYPE.NONE }) 40 | }} 41 | /> 42 | ) 43 | } 44 | 45 | export default BeforeSelectBoardContainer 46 | -------------------------------------------------------------------------------- /src/containers/Modals/WifiModalcontainer.tsx: -------------------------------------------------------------------------------- 1 | import { ENotificationType, MODAL_TYPE, TITLEBAR_ACTION } from '@interfaces/enums' 2 | import WifiModal from '@pages/Modals/WifiModal' 3 | import { type Command, espApi } from '@src/esp/api' 4 | import { DEFAULT_PORT_NAME } from '@src/static' 5 | import { useAppAPIContext } from '@store/context/api' 6 | import { useAppNotificationsContext } from '@store/context/notifications' 7 | import { useAppUIContext } from '@store/context/ui' 8 | import { appWindow } from '@tauri-apps/api/window' 9 | import { createMemo, createSignal } from 'solid-js' 10 | 11 | const WifiModalContainer = () => { 12 | const [isSending, setIsSending] = createSignal(false) 13 | const { mdns, ssid, password, activePort } = useAppAPIContext() 14 | const { modal, setOpenModal } = useAppUIContext() 15 | const { addNotification } = useAppNotificationsContext() 16 | 17 | const config = createMemo(() => { 18 | return [ 19 | { command: 'set_mdns', data: { hostname: mdns() } }, 20 | { command: 'set_wifi', data: { ssid: ssid(), password: password() } }, 21 | ] 22 | }) 23 | 24 | const notify = (title: string, type: ENotificationType) => { 25 | addNotification({ title, message: title, type }) 26 | } 27 | 28 | const activePortName = createMemo(() => { 29 | return activePort().activePortName 30 | }) 31 | 32 | const onClickUpdateNetworkSettings = async () => { 33 | setIsSending(true) 34 | 35 | if (activePortName() === DEFAULT_PORT_NAME) { 36 | setTimeout(() => { 37 | setIsSending(false) 38 | }, 250) 39 | return 40 | } 41 | 42 | notify('sending credentials', ENotificationType.INFO) 43 | await espApi.sendCommands(activePortName(), config()) 44 | notify('Sent credentials', ENotificationType.INFO) 45 | 46 | setIsSending(false) 47 | setOpenModal({ open: false, type: MODAL_TYPE.NONE }) 48 | } 49 | 50 | return ( 51 | { 55 | switch (action) { 56 | case TITLEBAR_ACTION.MINIMIZE: 57 | appWindow.minimize() 58 | break 59 | case TITLEBAR_ACTION.MAXIMIZE: 60 | appWindow.toggleMaximize() 61 | break 62 | case TITLEBAR_ACTION.CLOSE: 63 | appWindow.close() 64 | break 65 | default: 66 | return 67 | } 68 | }} 69 | onClickClose={() => { 70 | if (isSending()) return 71 | setOpenModal({ open: false, type: MODAL_TYPE.NONE }) 72 | }} 73 | onClick={() => { 74 | if (isSending()) return 75 | onClickUpdateNetworkSettings().catch(async (err) => { 76 | if (err instanceof Error) { 77 | notify(err.message, ENotificationType.ERROR) 78 | } else { 79 | notify(err, ENotificationType.ERROR) 80 | } 81 | setIsSending(false) 82 | }) 83 | }} 84 | /> 85 | ) 86 | } 87 | 88 | export default WifiModalContainer 89 | -------------------------------------------------------------------------------- /src/containers/Modals/index.tsx: -------------------------------------------------------------------------------- 1 | import { MODAL_TYPE } from '@interfaces/enums' 2 | import { useAppUIContext } from '@store/context/ui' 3 | import { Match, Show, Switch } from 'solid-js' 4 | import ApModeContainer from './ApModeModalContainer' 5 | import BeforeFlashingModal from './BeforeFlashingContainer' 6 | import BeforeSelectBoardModal from './BeforeSelectBoardContainer' 7 | import WifiModal from './WifiModalcontainer' 8 | 9 | export const ModalRoot = () => { 10 | const { modal } = useAppUIContext() 11 | 12 | return ( 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 |
31 | ) 32 | } 33 | 34 | export default ModalRoot 35 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | /* @refresh reload */ 2 | import { render } from 'solid-js/web' 3 | import App from './App' 4 | import { AppContextMainProvider } from '@store/context/main' 5 | import '@styles/imports.css' 6 | 7 | render( 8 | () => ( 9 | 10 | 11 | 12 | ), 13 | document.getElementById('root') as HTMLElement, 14 | ) 15 | -------------------------------------------------------------------------------- /src/pages/BoardManagement/index.tsx: -------------------------------------------------------------------------------- 1 | import { type Component } from 'solid-js' 2 | import { SelectBoard } from '@components/Board/SelectBoard' 3 | import { Devtools } from '@components/DevTools' 4 | import { Footer } from '@components/Footer' 5 | import { type IDropdownList } from '@interfaces/interfaces' 6 | import { type CHANNEL_TYPE, TITLEBAR_ACTION } from '@src/static/types/enums' 7 | 8 | export interface IProps { 9 | onClickSetChannelMode: (data: string) => void 10 | onClickHeader: (action: TITLEBAR_ACTION) => void 11 | setDebugMode: (debugMode: string) => void 12 | onClickOpenModal: (id: string) => void 13 | onSubmit: (board: string) => void 14 | onClickConfirm: () => void 15 | boards: IDropdownList[] 16 | channelOptions: IDropdownList[] 17 | firmwareVersion: string 18 | debugModes: IDropdownList[] 19 | activeBoard: string 20 | channelMode: CHANNEL_TYPE 21 | debugMode: string 22 | lockButton: boolean 23 | } 24 | 25 | export const BoardManagement: Component = (props) => { 26 | return ( 27 |
28 |
29 |
30 | 40 | 46 |
47 |
48 |
53 |
54 | ) 55 | } 56 | 57 | export default BoardManagement 58 | -------------------------------------------------------------------------------- /src/pages/Modals/ApModeModal.tsx: -------------------------------------------------------------------------------- 1 | import { Component, createSignal, Show } from 'solid-js' 2 | import { Button } from '@components/Buttons/Button' 3 | import { Modal } from '@components/Modal' 4 | import ModalHeader from '@components/ModalHeader' 5 | import { TITLEBAR_ACTION } from '@interfaces/enums' 6 | import { apModalID } from '@src/static' 7 | import Typography from '@components/Typography' 8 | 9 | export interface IProps { 10 | onClickHeader: (action: TITLEBAR_ACTION) => void 11 | onClickClose: () => void 12 | onClick: () => void 13 | isActive: boolean 14 | } 15 | 16 | const ApModeModal: Component = (props) => { 17 | const [enableAPMode, setEnableAPMode] = createSignal(false) 18 | 19 | return ( 20 | 25 |
26 | 27 |
28 | 29 | Important! 30 | 31 | 38 | Before pressing the Send AP Request check 39 | that you have the firmware already{' '} 40 | installed and you are connected to 41 | EyeTrackVR Wi-Fi. 42 | 43 | }> 44 | 45 | Read the documentation before turning on 46 | AP mode. 47 | 48 | 49 |
50 |
51 |
70 |
71 |
72 | ) 73 | } 74 | 75 | export default ApModeModal 76 | -------------------------------------------------------------------------------- /src/pages/Modals/BeforeFlashingModal.tsx: -------------------------------------------------------------------------------- 1 | import { Component } from 'solid-js' 2 | import { Button } from '@components/Buttons/Button' 3 | import { Modal } from '@components/Modal' 4 | import ModalHeader from '@components/ModalHeader' 5 | import { TITLEBAR_ACTION } from '@interfaces/enums' 6 | import { beforeFlashingModalID } from '@src/static' 7 | import Typography from '@components/Typography' 8 | import CheckboxButton from '@components/Buttons/Checkbox' 9 | 10 | export interface IProps { 11 | onClickHeader: (action: TITLEBAR_ACTION) => void 12 | onClickClose: () => void 13 | onClickInstallOpeniris: () => void 14 | onClickCheckbox: () => void 15 | isActive: boolean 16 | checked: boolean 17 | } 18 | 19 | const BeforeFlashingModal: Component = (props) => { 20 | return ( 21 | 26 |
27 | 28 |
29 | 30 | Before flashing 31 | 32 | 33 | Make sure to follow the steps below 👇 34 | 35 |
36 | 37 | • hold B button while plugging the board in 38 | 39 | 40 | • Make sure you have the antenna and camera plugged into the 41 | board if you plan on using them wirelessly 42 | 43 | 44 | • Make sure your password and ssid do not have special characters 45 | 46 | 47 | • Make sure you have a stable internet connection 48 | 49 |
50 |
51 |
52 | 57 |
64 |
65 |
66 | ) 67 | } 68 | 69 | export default BeforeFlashingModal 70 | -------------------------------------------------------------------------------- /src/pages/Modals/BeforeSelectBoard.tsx: -------------------------------------------------------------------------------- 1 | import { Component } from 'solid-js' 2 | import { Footer } from '@components/Footer' 3 | import { Modal } from '@components/Modal' 4 | import ModalHeader from '@components/ModalHeader' 5 | import { TITLEBAR_ACTION } from '@interfaces/enums' 6 | import { beforeSelectBoardModalID } from '@src/static' 7 | import Typography from '@components/Typography' 8 | 9 | export interface IProps { 10 | onClickHeader: (action: TITLEBAR_ACTION) => void 11 | onClickConfirmBoard: () => void 12 | onClickClose: () => void 13 | isActive: boolean 14 | } 15 | 16 | const BeforeSelectBoard: Component = (props) => { 17 | return ( 18 | 23 |
24 | 25 |
26 | 27 | Before selecting the board 28 | 29 | 30 | _Release is meant to be flashed when everything 31 | was confirmed working with the regular version first. It has a lot less 32 | logging, and debugging features are missing which makes it harder to 33 | diagnose what's wrong when issues arise. 34 | 35 |
36 |
43 |
44 |
45 | ) 46 | } 47 | 48 | export default BeforeSelectBoard 49 | -------------------------------------------------------------------------------- /src/pages/Modals/WifiModal.tsx: -------------------------------------------------------------------------------- 1 | import { Component } from 'solid-js' 2 | import { Button } from '@components/Buttons/Button' 3 | import { Modal } from '@components/Modal' 4 | import ModalHeader from '@components/ModalHeader' 5 | import { TITLEBAR_ACTION } from '@interfaces/enums' 6 | import { wifiModalID } from '@src/static' 7 | import Typography from '@components/Typography' 8 | 9 | export interface IProps { 10 | onClickHeader: (action: TITLEBAR_ACTION) => void 11 | onClickClose: () => void 12 | onClick: () => void 13 | isActive: boolean 14 | isSending: boolean 15 | } 16 | 17 | const WifiModal: Component = (props) => { 18 | return ( 19 | 25 |
26 | 27 |
28 | 29 | Important! 30 | 31 |
32 | 33 | Before proceeding, you must first restart your 34 | board. Simply disconnect it and plug it back in, no buttons need to be 35 | held. 36 | 37 | 38 | Once done, press Send Credentials and give it 39 | a couple of seconds until it finishes. 40 | 41 | 42 | This should setup your board proper wifi and mdns name
check if 43 | it connected! 44 |
45 |
46 |
47 |
48 |
57 |
58 |
59 | ) 60 | } 61 | 62 | export default WifiModal 63 | -------------------------------------------------------------------------------- /src/pages/NetworkManagement/index.tsx: -------------------------------------------------------------------------------- 1 | import { Component, createMemo, createSignal, onMount } from 'solid-js' 2 | import { Footer } from '@components/Footer' 3 | import { SelectNetwork } from '@components/SelectNetwork' 4 | 5 | export interface IProps { 6 | onClickSkip: () => void 7 | onSubmit: (ssid: string, password: string, mdns: string) => void 8 | isLoading: boolean 9 | ssid: string 10 | mdns: string 11 | password: string 12 | } 13 | 14 | export const NetworkManagement: Component = (props) => { 15 | const [ssid, setSSID] = createSignal('') 16 | const [password, setPassword] = createSignal('') 17 | const [mdns, setMdns] = createSignal('') 18 | 19 | const isNotActive = createMemo(() => !ssid() || !password()) 20 | 21 | onMount(() => { 22 | setSSID(props.ssid) 23 | setPassword(props.password) 24 | setMdns(props.mdns) 25 | }) 26 | 27 | return ( 28 |
29 |
30 |
31 | { 38 | if (/[^A-Za-z\d]/.test(value)) return 39 | setMdns(value.toLocaleLowerCase()) 40 | }} 41 | /> 42 |
43 |
{ 47 | if (isNotActive()) return 48 | props.onSubmit(ssid(), password(), mdns()) 49 | }} 50 | isPrimaryActive={isNotActive()} 51 | isSecondActive={false} 52 | primaryLabel="Confirm" 53 | secondLabel="Select board" 54 | /> 55 |
56 |
57 | ) 58 | } 59 | 60 | export default NetworkManagement 61 | -------------------------------------------------------------------------------- /src/pages/VirtualList/index.tsx: -------------------------------------------------------------------------------- 1 | import { Component, createMemo, createSignal, For, onCleanup, onMount } from 'solid-js' 2 | 3 | export interface IProps { 4 | items: string[] 5 | } 6 | 7 | export const VirtualList: Component = (props) => { 8 | const [scrollTop, setScrollTop] = createSignal(0) 9 | 10 | let parentRef 11 | const itemHeight = 24 12 | const viewportHeight = 1200 13 | 14 | const startIdx = () => { 15 | return Math.floor(scrollTop() / itemHeight) 16 | } 17 | 18 | const endIdx = () => { 19 | return Math.min(props.items.length, Math.ceil((scrollTop() + viewportHeight) / itemHeight)) 20 | } 21 | 22 | const handleScroll = (e) => { 23 | setScrollTop(e.target.scrollTop) 24 | } 25 | 26 | onMount(() => { 27 | if (parentRef) { 28 | parentRef.addEventListener('scroll', handleScroll) 29 | } 30 | 31 | onCleanup(() => { 32 | if (parentRef) { 33 | parentRef.removeEventListener('scroll', handleScroll) 34 | } 35 | }) 36 | }) 37 | 38 | const items = createMemo(() => { 39 | return props.items.slice(startIdx(), endIdx()) 40 | }) 41 | 42 | const height = createMemo(() => { 43 | const height = props.items.length * itemHeight 44 | return height > 600 ? '600px' : 'auto' 45 | }) 46 | 47 | return ( 48 |
(parentRef = el)} 51 | style={{ height: height(), width: '100%', overflow: 'auto' }}> 52 |
59 | 60 | {(item, index) => { 61 | return ( 62 |
71 |
77 |                                     {item}
78 |                                 
79 |
80 | ) 81 | }} 82 |
83 |
84 |
85 | ) 86 | } 87 | -------------------------------------------------------------------------------- /src/routes/Routes.tsx: -------------------------------------------------------------------------------- 1 | import { Router } from '@solidjs/router' 2 | 3 | import { isEqual } from 'lodash' 4 | import { createEffect, onMount, type Component } from 'solid-js' 5 | import { useEventListener, useInterval } from 'solidjs-use' 6 | import { debug } from 'tauri-plugin-log-api' 7 | import { routes } from '.' 8 | import type { PersistentSettings } from '@static/types' 9 | import { ENotificationAction } from '@src/static/types/enums' 10 | import { useAppAPIContext } from '@store/context/api' 11 | import { useAppContext } from '@store/context/app' 12 | import { useAppNotificationsContext } from '@store/context/notifications' 13 | import { useAppUIContext } from '@store/context/ui' 14 | import { usePersistentStore } from '@store/tauriStore' 15 | import { Header } from '@containers/Header' 16 | 17 | const AppRoutes: Component = () => { 18 | const { get, set } = usePersistentStore() 19 | const { doGHRequest, channelMode } = useAppAPIContext() 20 | const { setDebugMode, getDebugMode } = useAppContext() 21 | const { setContextMenuAnchor } = useAppUIContext() 22 | const { 23 | setEnableNotifications, 24 | setEnableNotificationsSounds, 25 | setGlobalNotificationsType, 26 | getEnableNotificationsSounds, 27 | getEnableNotifications, 28 | getGlobalNotificationsType, 29 | checkPermission, 30 | } = useAppNotificationsContext() 31 | 32 | onMount(() => { 33 | setContextMenuAnchor('custom-context-menu') 34 | //* load the app settings from the persistent store and assign to the global state 35 | get('settings').then((settings) => { 36 | if (settings) { 37 | debug('loading settings') 38 | 39 | setEnableNotifications(settings.enableNotifications) 40 | setEnableNotificationsSounds(settings.enableNotificationsSounds) 41 | setGlobalNotificationsType( 42 | settings.globalNotificationsType ?? ENotificationAction.APP, 43 | ) 44 | 45 | setDebugMode(settings.debugMode) 46 | } 47 | }) 48 | //* Check notification permissions 49 | checkPermission() 50 | //* Grab the github release info for OpenIris 51 | }) 52 | 53 | createEffect(() => { 54 | doGHRequest(channelMode()) 55 | }) 56 | 57 | const createSettingsObject = () => { 58 | const settings: PersistentSettings = { 59 | enableNotifications: getEnableNotifications(), 60 | enableNotificationsSounds: getEnableNotificationsSounds(), 61 | globalNotificationsType: getGlobalNotificationsType(), 62 | debugMode: getDebugMode(), 63 | } 64 | return settings 65 | } 66 | 67 | const handleSaveSettings = async () => { 68 | // check if the settings have changed and save to the store if they have 69 | get('settings').then((storedSettings) => { 70 | if (!isEqual(storedSettings, createSettingsObject())) { 71 | debug(`[Routes]: Saving Settings - ${JSON.stringify(createSettingsObject())}`) 72 | set('settings', createSettingsObject()) 73 | } 74 | }) 75 | } 76 | 77 | createEffect(() => { 78 | const { resume, pause } = useInterval(30000, { 79 | controls: true, 80 | callback: handleSaveSettings, 81 | }) 82 | 83 | useEventListener(window, 'blur', () => { 84 | pause() 85 | debug(`[Routes]: Saving Settings - ${JSON.stringify(createSettingsObject())}`) 86 | set('settings', createSettingsObject()) 87 | resume() 88 | }) 89 | }) 90 | 91 | return ( 92 | { 94 | return ( 95 |
96 |
97 |
{data.children}
98 |
99 | ) 100 | }}> 101 | {routes} 102 |
103 | ) 104 | } 105 | 106 | export default AppRoutes 107 | -------------------------------------------------------------------------------- /src/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import { lazy } from 'solid-js' 2 | import type { RouteDefinition } from '@solidjs/router' 3 | 4 | const NetworkConfigurator = lazy(() => import('@containers/ManageNetwork')) 5 | const BoardConfigurator = lazy(() => import('@containers/ManageBoard')) 6 | const FlashFirmware = lazy(() => import('@containers/FlashFirmware')) 7 | const page404 = lazy(() => import('@containers/404/[...404]')) 8 | 9 | export const routes: RouteDefinition[] = [ 10 | { path: '/flashFirmware', component: FlashFirmware }, 11 | { path: '/network', component: NetworkConfigurator }, 12 | { path: '/', component: BoardConfigurator }, 13 | { path: '**', component: page404 }, 14 | ] 15 | -------------------------------------------------------------------------------- /src/static/endpoints.ts: -------------------------------------------------------------------------------- 1 | import { CHANNEL_TYPE } from '@interfaces/enums' 2 | 3 | export const GHEndpoints: Record = { 4 | [CHANNEL_TYPE.OFFICIAL]: 'https://api.github.com/repos/EyeTrackVR/OpenIris/releases/latest', 5 | [CHANNEL_TYPE.BETA]: 'https://api.github.com/repos/EyeTrackVR/OpenIris/releases', 6 | } 7 | -------------------------------------------------------------------------------- /src/static/index.ts: -------------------------------------------------------------------------------- 1 | import { BOARD_TYPE, CHANNEL_TYPE, STEP_STATUS_ENUM } from './types/enums' 2 | import { type IChannelOptions } from '@interfaces/interfaces' 3 | 4 | export const supportedBoards: string[] = [BOARD_TYPE.XIAOSENSES_3, BOARD_TYPE.XIAOSENSES_3_USB] 5 | export const debugModes: string[] = ['off', 'error', 'warn', 'info', 'debug', 'trace'] 6 | export const defaultMdnsLength = 24 7 | export const portBaudRate = 115200 8 | export const mdnsLength = 12 9 | export const radius = 24 10 | export const usb = 'USB' 11 | export const questionModalId = 'questionModal' 12 | export const apModalID = 'apMode' 13 | export const logsModalID = 'logs' 14 | export const wifiModalID = 'wifiMode' 15 | export const beforeFlashingModalID = 'beforeFlashingMode' 16 | export const beforeSelectBoardModalID = 'BeforeSelectBoardMode' 17 | export const debugModalId = 'debugModal' 18 | export const staticMdns = 'openiristracker' 19 | export const DEVICE_LOST = 'The device has been lost.' 20 | export const STREAM_IS_UNDER = 'The stream is under' 21 | export const SSID_MISSING = 'ssid missing' 22 | export const AP_IP_ADDRESS = 'AP IP address:' 23 | export const DEFAULT_PORT_NAME = 'auto' 24 | 25 | const circleSize = Math.PI * (radius * 2) 26 | 27 | export const stepStatus: { 28 | [key in STEP_STATUS_ENUM]: { 29 | description: string 30 | dashoffset: string 31 | index: string 32 | } 33 | } = { 34 | [STEP_STATUS_ENUM.SELECT_BOARD]: { 35 | index: '1', 36 | description: 'Select board', 37 | dashoffset: ((105 / 100) * circleSize).toString(), 38 | }, 39 | [STEP_STATUS_ENUM.CONFIGURE_WIFI]: { 40 | index: '2', 41 | description: 'Configure wifi network', 42 | dashoffset: (((105 - 50) / 100) * circleSize).toString(), 43 | }, 44 | [STEP_STATUS_ENUM.FLASH_FIRMWARE]: { 45 | index: '3', 46 | description: 'Flash firmware assets', 47 | dashoffset: (((100 - 100) / 100) * circleSize).toString(), 48 | }, 49 | } 50 | 51 | export const BoardDescription: { 52 | [key in BOARD_TYPE]: string 53 | } = { 54 | [BOARD_TYPE.BABBLE_WROOMS_S3]: 'Official Babble tracker board (wireless mode)', 55 | [BOARD_TYPE.BABBLE_WROOMS_S3_RELEASE]: 'Official Babble tracker board (wireless mode)', 56 | [BOARD_TYPE.BABBLE_USB_WROOMS_S3]: 'Official Babble tracker board (wired mode)', 57 | [BOARD_TYPE.BABBLE_USB_WROOMS_S3_RELEASE]: 'Official Babble tracker board (wired mode)', 58 | [BOARD_TYPE.ESP_32_AI_THINKER]: 'Default for ESP32-AI-THINKER and ESP CAM boards.', 59 | [BOARD_TYPE.ESP_32]: 60 | 'Special ESP32-CAM, it is unlikely that you will need to use this environment.', 61 | [BOARD_TYPE.ESP_32_M_5_STACK]: 'ESP32M5Stack.', 62 | [BOARD_TYPE.ESP_32_W_ROVER]: 'ESP32WRover.', 63 | [BOARD_TYPE.ESP_EYE]: 'TESP-EYE (not the S3 variant)', 64 | [BOARD_TYPE.WROOMS_3]: 'FREENOVE-ESP32-S3 (wireless mode)', 65 | [BOARD_TYPE.WROOMS_3_QIO]: 'FREENOVE-ESP32-S3 (wireless mode, for boards with octal flash)', 66 | [BOARD_TYPE.WROOMS_3_USB]: 'FREENOVE-ESP32-S3 (wired mode)', 67 | [BOARD_TYPE.WROOMS_3QIOUSB]: 'FREENOVE-ESP32-S3 (wired mode, for boards with octal flash)', 68 | [BOARD_TYPE.XIAOSENSES_3]: "SeedStudio's XIAO ESP32-S3 Sense (wireless mode)", 69 | [BOARD_TYPE.XIAOSENSES_3_USB]: "SeedStudio's XIAO ESP32-S3 Sense (wired mode)", 70 | } 71 | 72 | export const BoardConnectionMethod: { 73 | [key in BOARD_TYPE]: string 74 | } = { 75 | [BOARD_TYPE.BABBLE_WROOMS_S3]: 'wireless mode', 76 | [BOARD_TYPE.BABBLE_WROOMS_S3_RELEASE]: 'wireless mode', 77 | [BOARD_TYPE.BABBLE_USB_WROOMS_S3_RELEASE]: 'wired mode', 78 | [BOARD_TYPE.BABBLE_USB_WROOMS_S3]: 'wired mode', 79 | [BOARD_TYPE.ESP_32_AI_THINKER]: 'wireless mode', 80 | [BOARD_TYPE.ESP_32]: 'wireless mode', 81 | [BOARD_TYPE.ESP_32_M_5_STACK]: 'wireless mode', 82 | [BOARD_TYPE.ESP_32_W_ROVER]: 'wireless mode', 83 | [BOARD_TYPE.ESP_EYE]: 'wireless mode', 84 | [BOARD_TYPE.WROOMS_3]: 'wireless mode', 85 | [BOARD_TYPE.WROOMS_3_QIO]: 'wireless mode', 86 | [BOARD_TYPE.WROOMS_3_USB]: 'wired mode', 87 | [BOARD_TYPE.WROOMS_3QIOUSB]: 'wired mode', 88 | [BOARD_TYPE.XIAOSENSES_3]: 'wireless mode', 89 | [BOARD_TYPE.XIAOSENSES_3_USB]: 'wired mode', 90 | } 91 | 92 | export const ChannelOptions: Record = { 93 | [CHANNEL_TYPE.OFFICIAL]: { 94 | label: CHANNEL_TYPE.OFFICIAL, 95 | description: 'Official channel for official releases.', 96 | }, 97 | [CHANNEL_TYPE.BETA]: { 98 | label: CHANNEL_TYPE.BETA, 99 | description: 100 | 'This channel is for testing purposes only. It is not recommended for day to day usage', 101 | }, 102 | } 103 | -------------------------------------------------------------------------------- /src/static/types/enums.ts: -------------------------------------------------------------------------------- 1 | //********************************* UI *************************************/ 2 | 3 | export enum POPOVER_ID { 4 | GRIP = 'grip-popover', 5 | LIST = 'list-popover', 6 | TRACKER_MANAGER = 'tracker-manager-popover', 7 | SETTINGS_POPOVER = 'settings-popover', 8 | } 9 | 10 | export enum ANIMATION_MODE { 11 | GRIP = 'grip-popover', 12 | LIST = 'list-popover', 13 | NONE = 'NONE', 14 | } 15 | 16 | export enum TITLEBAR_ACTION { 17 | MINIMIZE = 'minimize', 18 | MAXIMIZE = 'maximize', 19 | CLOSE = 'close', 20 | } 21 | 22 | export enum ENotificationType { 23 | ERROR = 'ERROR', 24 | SUCCESS = 'SUCCESS', 25 | INFO = 'INFO', 26 | WARNING = 'WARNING', 27 | DEFAULT = 'DEFAULT', 28 | } 29 | 30 | export enum ENotificationAction { 31 | OS = 'OS', 32 | APP = 'APP', 33 | NULL = 'null', 34 | } 35 | 36 | export enum RANGE_INPUT_FORMAT { 37 | EYE_POSITION_SCALAR = 'Eye position scalar', 38 | THRESHOLD = 'Threshold', 39 | ROTATION = 'Rotation', 40 | } 41 | 42 | export enum RANGE_INPUT_FORMAT_APP_SETTINGS { 43 | MIN_FREQUENCY_CUTOFF = 'Min frequency cutoff', 44 | SPEED_COEFFICIENT = 'Speed coefficient', 45 | } 46 | 47 | //********************************* Network and App *************************************/ 48 | 49 | export enum ExitCodes { 50 | USER_EXIT = 0, 51 | ERROR = 1, 52 | ERROR_UNKNOWN = 2, 53 | } 54 | 55 | export enum RESTStatus { 56 | ACTIVE = 'ACTIVE', 57 | COMPLETE = 'COMPLETE', 58 | LOADING = 'LOADING', 59 | FAILED = 'FAILED', 60 | NO_CAMERA = 'NO_CAMERA', 61 | NO_CONFIG = 'NO_CONFIG', 62 | } 63 | 64 | export enum RESTType { 65 | GET = 'GET', 66 | POST = 'POST', 67 | PUT = 'PUT', 68 | DELETE = 'DELETE', 69 | } 70 | 71 | export enum ESPEndpoints { 72 | //? Default 73 | PING = '/control/builtin/command/ping', 74 | SAVE = '/control/builtin/command/save', 75 | RESET_CONFIG = '/control/builtin/command/resetConfig', 76 | REBOOT_DEVICE = '/control/builtin/command/rebootDevice', 77 | RESTART_CAMERA = '/control/builtin/command/restartCamera', 78 | GET_STORED_CONFIG = '/control/builtin/command/getStoredConfig', 79 | SET_TX_POWER = '/control/builtin/command/setTxPower', 80 | SET_DEVICE = '/control/builtin/command/setDevice', 81 | //? Network 82 | WIFI = '/control/builtin/command/wifi', 83 | WIFI_STRENGTH = '/control/builtin/command/wifiStrength', 84 | OTA = '/update', 85 | } 86 | 87 | export enum STEP_STATUS_ENUM { 88 | CONFIGURE_WIFI = 'CONFIGURE_WIFI', 89 | SELECT_BOARD = 'SELECT_BOARD', 90 | FLASH_FIRMWARE = 'FLASH_FIRMWARE', 91 | } 92 | 93 | export enum DIRECTION { 94 | '/' = STEP_STATUS_ENUM.SELECT_BOARD, 95 | '/network' = STEP_STATUS_ENUM.CONFIGURE_WIFI, 96 | '/flashFirmware' = STEP_STATUS_ENUM.FLASH_FIRMWARE, 97 | } 98 | 99 | export enum BOARD_TYPE { 100 | BABBLE_WROOMS_S3 = 'Babble_wrooms_s3', 101 | BABBLE_WROOMS_S3_RELEASE = 'Babble_wrooms_s3_release', 102 | BABBLE_USB_WROOMS_S3 = 'Babble_USB_wrooms_s3', 103 | BABBLE_USB_WROOMS_S3_RELEASE = 'Babble_USB_wrooms_s3_release', 104 | ESP_32_AI_THINKER = 'esp32AIThinker', 105 | ESP_32 = 'esp32Cam', 106 | ESP_32_M_5_STACK = 'esp32M5Stack', 107 | ESP_32_W_ROVER = 'esp32WRover', 108 | ESP_EYE = 'esp_eye', 109 | WROOMS_3 = 'wrooms3', 110 | WROOMS_3_QIO = 'wrooms3QIO', 111 | WROOMS_3_USB = 'wrooms3USB', 112 | WROOMS_3QIOUSB = 'wrooms3QIOUSB', 113 | XIAOSENSES_3 = 'xiaosenses3', 114 | XIAOSENSES_3_USB = 'xiaosenses3_USB', 115 | } 116 | 117 | export enum CHANNEL_TYPE { 118 | OFFICIAL = 'Official', 119 | BETA = 'Beta', 120 | } 121 | 122 | //********************************* flash firmware state *************************************/ 123 | 124 | export const enum FLASH_STEP { 125 | BOARD_CONNECTION = 'BOARD_CONNECTION', 126 | OPEN_PORT = 'OPEN_PORT', 127 | LOGS = 'LOGS', 128 | MANIFEST_PATH = 'MANIFEST_PATH', 129 | REQUEST_PORT = 'REQUEST_PORT', 130 | INITIALIZE = 'INITIALIZE', 131 | CHIP_FAMILY = 'CHIP_FAMILY', 132 | FLASH_FIRMWARE = 'FLASH_FIRMWARE', 133 | BUILD = 'BUILD', 134 | DOWNLOAD_FILES = 'FILES', 135 | } 136 | 137 | export const enum FLASH_STATUS { 138 | NONE = 'NONE', 139 | SUCCESS = 'SUCCESS', 140 | FAILED = 'FAILED', 141 | UNKNOWN = 'UNKNOWN', 142 | ABORTED = 'ABORTED', 143 | } 144 | 145 | export enum MODAL_TYPE { 146 | BEFORE_SELECT_BOARD = 'BEFORE_SELECT_BOARD', 147 | UPDATE_NETWORK = 'UPDATE_NETWORK', 148 | BEFORE_FLASHING = 'BEFORE_FLASHING', 149 | AP_MODE = 'AP_MODE', 150 | NONE = 'NONE', 151 | } 152 | -------------------------------------------------------------------------------- /src/static/types/index.ts: -------------------------------------------------------------------------------- 1 | export * as O from 'fp-ts/Option' 2 | import type { ENotificationAction } from './enums' 3 | import type { JSXElement } from 'solid-js' 4 | 5 | //********************************* Utility *************************************/ 6 | export type Context = { 7 | [key: string]: JSXElement 8 | } 9 | 10 | //********************************* Settings *************************************/ 11 | 12 | //********************************* Config *************************************/ 13 | 14 | /** 15 | * @description Debug mode levels 16 | * @export typedef {string} DebugMode 17 | * @property {'off'} off 18 | * @property {'error'} error 19 | * @property {'warn'} warn 20 | * @property {'info'} info 21 | * @property {'debug'} debug 22 | * @property {'trace'} trace 23 | */ 24 | export type DebugMode = 'off' | 'error' | 'warn' | 'info' | 'debug' | 'trace' 25 | 26 | /** 27 | * @description This is the export type that is passed to the Tauri Store instance to handle persistent data within the app. 28 | * @export typedef {Object} PersistentSettings 29 | * @property {boolean} enableNotificationsSounds 30 | * @property {boolean} enableNotifications 31 | * @property {ENotificationAction} globalNotificationsType 32 | * @property {boolean} enableMDNS 33 | * @property {boolean} scanForCamerasOnStartup 34 | * @property {CameraSettings} cameraSettings 35 | * @property {AlgorithmSettings} algorithmSettings 36 | * @property {FilterParams} filterParams 37 | * @property {OSCSettings} oscSettings 38 | */ 39 | export type PersistentSettings = { 40 | user?: string 41 | enableNotificationsSounds?: boolean 42 | enableNotifications?: boolean 43 | globalNotificationsType?: ENotificationAction 44 | debugMode?: DebugMode 45 | } 46 | 47 | /** 48 | * @description Backend Config 49 | */ 50 | export type BackendConfig = { 51 | version?: number | string 52 | debug?: DebugMode 53 | } 54 | -------------------------------------------------------------------------------- /src/static/types/types.ts: -------------------------------------------------------------------------------- 1 | export type IEventType = FocusEvent & { 2 | currentTarget: HTMLDivElement 3 | target: Element 4 | } 5 | -------------------------------------------------------------------------------- /src/static/types/utils.ts: -------------------------------------------------------------------------------- 1 | import { CHANNEL_TYPE } from './enums' 2 | 3 | export const isValidChannel = (value: string): value is CHANNEL_TYPE => { 4 | return Object.values(CHANNEL_TYPE).includes(value as CHANNEL_TYPE) 5 | } 6 | -------------------------------------------------------------------------------- /src/store/context/app.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useContext, createMemo, type Component, Accessor } from 'solid-js' 2 | import { createStore, produce } from 'solid-js/store' 3 | import { attachConsole } from 'tauri-plugin-log-api' 4 | import { AppAPIProvider } from './api' 5 | import { AppNotificationProvider } from './notifications' 6 | import { AppUIProvider } from './ui' 7 | import type { AppStore } from '@src/static/types/interfaces' 8 | import type { Context, DebugMode } from '@static/types' 9 | import type { UnlistenFn } from '@tauri-apps/api/event' 10 | 11 | interface AppContext { 12 | getDetachConsole: Accessor> 13 | getDebugMode: Accessor 14 | setDebugMode: (mode: DebugMode | undefined) => void 15 | } 16 | 17 | const AppContext = createContext() 18 | export const AppProvider: Component = (props) => { 19 | const detachConsole = attachConsole() 20 | 21 | //#region Store 22 | const defaultState: AppStore = { 23 | debugMode: 'off', 24 | } 25 | 26 | const [state, setState] = createStore(defaultState) 27 | 28 | const setDebugMode = (mode: DebugMode | undefined) => { 29 | setState( 30 | produce((s) => { 31 | s.debugMode = mode || 'info' 32 | }), 33 | ) 34 | } 35 | 36 | const appState = createMemo(() => state) 37 | const getDebugMode = createMemo(() => appState().debugMode) 38 | const getDetachConsole = createMemo(() => detachConsole) 39 | //#endregion 40 | 41 | return ( 42 | 48 | 49 | 50 | {props.children} 51 | 52 | 53 | 54 | ) 55 | } 56 | 57 | export const useAppContext = () => { 58 | const context = useContext(AppContext) 59 | if (context === undefined) { 60 | throw new Error('useAppContext must be used within a AppProvider') 61 | } 62 | return context 63 | } 64 | -------------------------------------------------------------------------------- /src/store/context/main.tsx: -------------------------------------------------------------------------------- 1 | import { exit } from '@tauri-apps/api/process' 2 | import { invoke } from '@tauri-apps/api/tauri' 3 | import { appWindow } from '@tauri-apps/api/window' 4 | import { createContext, useContext, createMemo, type Component, Accessor } from 'solid-js' 5 | import { useEventListener } from 'solidjs-use' 6 | import { attachConsole } from 'tauri-plugin-log-api' 7 | import type { Context } from '@static/types' 8 | import type { UnlistenFn } from '@tauri-apps/api/event' 9 | import { ExitCodes } from '@src/static/types/enums' 10 | import { usePersistentStore } from '@src/store/tauriStore' 11 | 12 | interface AppContextMain { 13 | getDetachConsole: Accessor> 14 | handleAppBoot: () => void 15 | handleTitlebar: (main?: boolean) => void 16 | } 17 | 18 | const AppContextMain = createContext() 19 | export const AppContextMainProvider: Component = (props) => { 20 | const detachConsole = attachConsole() 21 | 22 | const getDetachConsole = createMemo(() => detachConsole) 23 | //#region Global Hooks 24 | const handleAppExit = async (main = false) => { 25 | 26 | await invoke('handle_save_window_state') 27 | console.log('[App Close]: saved window state') 28 | 29 | if (main) { 30 | const { save } = usePersistentStore() 31 | await save() 32 | 33 | // stopMDNS() 34 | await exit(ExitCodes.USER_EXIT) 35 | } 36 | await appWindow.close() 37 | } 38 | 39 | const handleAppBoot = () => { 40 | //const { set, get } = usePersistentStore() 41 | 42 | console.log('[App Boot]: Frontend Initialization Starting') 43 | useEventListener(document, 'DOMContentLoaded', () => { 44 | // check if the window state is saved and restore it if it is 45 | invoke('handle_save_window_state').then(() => { 46 | console.log('[App Boot]: saved window state') 47 | }) 48 | }) 49 | 50 | //TODO: Start mdns client 51 | } 52 | 53 | const handleTitlebar = (main = false) => { 54 | const titlebar = document.getElementsByClassName('titlebar') 55 | if (titlebar) { 56 | useEventListener(document.getElementById('titlebar-minimize'), 'click', () => { 57 | appWindow.minimize() 58 | }) 59 | useEventListener(document.getElementById('titlebar-maximize'), 'click', () => { 60 | appWindow.toggleMaximize() 61 | }) 62 | useEventListener(document.getElementById('titlebar-close'), 'click', async () => { 63 | await handleAppExit(main) 64 | }) 65 | } 66 | } 67 | //#endregion 68 | 69 | return ( 70 | 76 | {props.children} 77 | 78 | ) 79 | } 80 | 81 | export const useAppContextMain = () => { 82 | const context = useContext(AppContextMain) 83 | if (context === undefined) { 84 | throw new Error('useAppContextMain must be used within a AppContextMainProvider') 85 | } 86 | return context 87 | } 88 | -------------------------------------------------------------------------------- /src/store/context/ui.tsx: -------------------------------------------------------------------------------- 1 | import { Accessor, createContext, createMemo, useContext, type Component } from 'solid-js' 2 | import { createStore, produce } from 'solid-js/store' 3 | import type { Context } from '@static/types' 4 | import type { IOpenModal, MenuOpen, UiStore } from '@static/types/interfaces' 5 | import { MODAL_TYPE } from '@interfaces/enums' 6 | 7 | interface AppUIContext { 8 | modal: Accessor 9 | menuOpenStatus: Accessor 10 | getContextAnchor: Accessor 11 | showNotifications: Accessor 12 | hideModal: Accessor 13 | setOpenModal: (data: IOpenModal) => void 14 | setMenu: (menuOpen: MenuOpen | null) => void 15 | setContextMenuAnchor: (id: string) => void 16 | setHideModal: () => void 17 | } 18 | 19 | const AppUIContext = createContext() 20 | export const AppUIProvider: Component = (props) => { 21 | const defaultState: UiStore = { 22 | openModal: { 23 | open: false, 24 | type: MODAL_TYPE.NONE, 25 | }, 26 | showNotifications: true, 27 | menuOpen: null, 28 | hideModal: false, 29 | } 30 | 31 | const [state, setState] = createStore(defaultState) 32 | 33 | const setMenu = (menuOpen: MenuOpen | null) => { 34 | setState( 35 | produce((s) => { 36 | s.menuOpen = menuOpen || null 37 | }), 38 | ) 39 | } 40 | 41 | const setContextMenuAnchor = (id: string) => { 42 | const anchor = document.getElementById(id) 43 | if (anchor) { 44 | setState( 45 | produce((s) => { 46 | s.contextAnchor = anchor 47 | }), 48 | ) 49 | } 50 | } 51 | 52 | const setOpenModal = (data: IOpenModal) => { 53 | setState( 54 | produce((s) => { 55 | s.openModal = data 56 | }), 57 | ) 58 | } 59 | 60 | const setHideModal = () => { 61 | setState( 62 | produce((s) => { 63 | s.hideModal = !s.hideModal 64 | }), 65 | ) 66 | } 67 | 68 | const uiState = createMemo(() => state) 69 | 70 | const modal = createMemo(() => uiState().openModal) 71 | const showNotifications = createMemo(() => uiState().showNotifications) 72 | const menuOpenStatus = createMemo(() => uiState().menuOpen) 73 | const getContextAnchor = createMemo(() => uiState().contextAnchor) 74 | const hideModal = createMemo(() => uiState().hideModal) 75 | 76 | return ( 77 | 89 | {props.children} 90 | 91 | ) 92 | } 93 | 94 | export const useAppUIContext = () => { 95 | const context = useContext(AppUIContext) 96 | if (context === undefined) { 97 | throw new Error('useAppUIContext must be used within an AppUIProvider') 98 | } 99 | return context 100 | } 101 | -------------------------------------------------------------------------------- /src/store/tauriStore/index.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/tauri-apps/plugins-workspace/tree/dev/plugins/store 2 | 3 | //* Global app settings stores 4 | import { Store } from 'tauri-plugin-store-api' 5 | import type { PersistentSettings } from '@src/static/types' 6 | 7 | const persistentStore = new Store('.app-settings.bin') 8 | 9 | export const usePersistentStore = () => { 10 | const save = async () => { 11 | await persistentStore.save() 12 | } 13 | 14 | const load = async () => { 15 | await persistentStore.load() 16 | } 17 | 18 | const has = async (key: string) => { 19 | return await persistentStore.has(key) 20 | } 21 | 22 | const get = async (key: string) => { 23 | const value = await persistentStore.get(key) 24 | if (!value) return null 25 | return value 26 | } 27 | 28 | const set = async (key: string, value: PersistentSettings) => { 29 | // check if the key exists 30 | if (await has(key)) { 31 | // if it does, get the current value 32 | const currentValue = await get(key) 33 | // if the current value is the same as the new value, don't save 34 | if (currentValue === value[key]) return 35 | } 36 | 37 | await persistentStore.set(key, value) 38 | } 39 | 40 | const reset = async () => { 41 | await persistentStore.reset() 42 | } 43 | 44 | const clear = async () => { 45 | await persistentStore.clear() 46 | } 47 | 48 | const remove = async (key: string) => { 49 | await persistentStore.delete(key) 50 | } 51 | 52 | const keys = async () => { 53 | return await persistentStore.keys() 54 | } 55 | 56 | const listen = async (callback: (key: string, value: PersistentSettings | null) => void) => { 57 | return await persistentStore.onChange(callback) 58 | } 59 | 60 | return { 61 | save, 62 | load, 63 | get, 64 | set, 65 | reset, 66 | clear, 67 | remove, 68 | has, 69 | keys, 70 | listen, 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/store/terminal/selectors.ts: -------------------------------------------------------------------------------- 1 | import { Accessor, createMemo } from 'solid-js' 2 | import { ITerminalStore, terminalState } from './terminal' 3 | 4 | export const { 5 | firmwareState, 6 | percentageProgress, 7 | detailedLogs, 8 | isActiveProcess, 9 | logs, 10 | isSoftwareDownloaded, 11 | simulationAbortController, 12 | } = [ 13 | 'firmwareState', 14 | 'percentageProgress', 15 | 'detailedLogs', 16 | 'isActiveProcess', 17 | 'logs', 18 | 'isSoftwareDownloaded', 19 | 'simulationAbortController', 20 | ].reduce( 21 | (acc, k) => { 22 | acc[k] = createMemo(() => terminalState()[k]) 23 | return acc 24 | }, 25 | {} as { [k in keyof ITerminalStore]: Accessor }, 26 | ) 27 | -------------------------------------------------------------------------------- /src/store/terminal/terminal.ts: -------------------------------------------------------------------------------- 1 | import { createMemo } from 'solid-js' 2 | import { createStore, produce } from 'solid-js/store' 3 | import { FLASH_STATUS, FLASH_STEP } from '@interfaces/enums' 4 | import { type IFlashState } from '@interfaces/interfaces' 5 | export interface ITerminalStore { 6 | simulationAbortController: AbortController 7 | isSoftwareDownloaded: boolean 8 | firmwareState: Record | object 9 | percentageProgress: number // % 10 | isActiveProcess: boolean 11 | detailedLogs: string[] 12 | logs: Record | object 13 | } 14 | 15 | export interface IFirmwareState { 16 | step: FLASH_STEP 17 | object: IFlashState 18 | } 19 | 20 | export const defaultLogsState = { 21 | status: FLASH_STATUS.NONE, 22 | label: '', 23 | } 24 | 25 | const defaultState: ITerminalStore = { 26 | simulationAbortController: new AbortController(), 27 | isSoftwareDownloaded: false, 28 | isActiveProcess: false, 29 | percentageProgress: 0, // % 30 | firmwareState: {}, 31 | detailedLogs: [], 32 | logs: {}, 33 | } 34 | 35 | const [state, setState] = createStore(defaultState) 36 | 37 | export const updateFirmwareState = ({ step, object }: IFirmwareState) => { 38 | setState( 39 | produce((s) => { 40 | s.firmwareState[step] = object 41 | }), 42 | ) 43 | } 44 | 45 | export const deleteFirmwareState = (step: FLASH_STEP) => { 46 | setState( 47 | produce((s) => { 48 | delete s.firmwareState[step] 49 | }), 50 | ) 51 | } 52 | 53 | export const setProcessStatus = (status: boolean) => { 54 | setState( 55 | produce((s) => { 56 | s.isActiveProcess = status 57 | }), 58 | ) 59 | } 60 | 61 | export const setLogs = (step: FLASH_STEP, log: string[], limitLogs?: boolean) => { 62 | setState( 63 | produce((s) => { 64 | if (!limitLogs) { 65 | s.logs[step] = [...(s.logs[step] ?? []), ...log] 66 | } else { 67 | const existingLogs = s.logs[step] ?? [] 68 | const totalLogs = existingLogs.length + log.length 69 | const maxLogs = 5000 70 | 71 | s.logs[step] = 72 | totalLogs > maxLogs 73 | ? [...existingLogs.slice(totalLogs - maxLogs), ...log] 74 | : [...existingLogs, ...log] 75 | } 76 | }), 77 | ) 78 | } 79 | 80 | export const restartFirmwareState = () => { 81 | setState( 82 | produce((s) => { 83 | s.firmwareState = {} 84 | s.percentageProgress = 0 85 | s.detailedLogs = [] 86 | }), 87 | ) 88 | } 89 | 90 | export const restartLogsState = () => { 91 | setState( 92 | produce((s) => { 93 | s.detailedLogs = [] 94 | }), 95 | ) 96 | } 97 | 98 | export const clearLogs = () => { 99 | setState( 100 | produce((s) => { 101 | s.firmwareState = {} 102 | s.logs = {} 103 | }), 104 | ) 105 | } 106 | 107 | export const setInstallationProgress = (percentageProgress: number) => { 108 | setState( 109 | produce((s) => { 110 | s.percentageProgress = percentageProgress 111 | }), 112 | ) 113 | } 114 | 115 | export const setDetailedLogs = (log: string) => { 116 | setState( 117 | produce((s) => { 118 | s.detailedLogs = [...s.detailedLogs, log] 119 | }), 120 | ) 121 | } 122 | 123 | export const setIsSoftwareDownloaded = (status: boolean) => { 124 | setState( 125 | produce((s) => { 126 | s.isSoftwareDownloaded = status 127 | }), 128 | ) 129 | } 130 | 131 | export const setAbortController = (description?: string) => { 132 | setState( 133 | produce((s) => { 134 | s.simulationAbortController.abort(description) 135 | s.simulationAbortController = new AbortController() 136 | }), 137 | ) 138 | } 139 | 140 | export const terminalState = createMemo(() => state) 141 | -------------------------------------------------------------------------------- /src/styles/docs-imports.css: -------------------------------------------------------------------------------- 1 | /** Import all project related CSS files here */ 2 | /*! Do not put raw CSS code in this file */ 3 | 4 | /* TailWindCSS Files */ 5 | @import 'tailwindcss/base'; 6 | @import 'tailwindcss/components'; 7 | @import 'tailwindcss/utilities'; 8 | 9 | /* Fonts */ 10 | @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@200;300;400;500;600;700;800;900&display=swap'); 11 | @import url('https://fonts.googleapis.com/css2?family=Lato:wght@300;400;700;900&display=swap'); 12 | @import url('https://fonts.googleapis.com/css?family=Ubuntu:500'); 13 | 14 | /* Custom Imports */ 15 | @import 'titlebar.css'; 16 | @import 'scrollbar.css'; 17 | 18 | body { 19 | margin: 0; 20 | font-family: Ubuntu, 'times new roman', times, roman, serif; 21 | cursor: default; 22 | background-color: transparent !important; 23 | width: 100%; 24 | height: 100%; 25 | text-align: center; 26 | font-size: 14px; 27 | } 28 | 29 | html { 30 | overflow: auto; 31 | } 32 | 33 | html, 34 | body, 35 | div, 36 | iframe { 37 | margin: 0px; 38 | padding: 0px; 39 | height: 100%; 40 | border: none; 41 | } 42 | -------------------------------------------------------------------------------- /src/styles/imports.css: -------------------------------------------------------------------------------- 1 | /** Import all project related CSS files here */ 2 | /*! Do not put raw CSS code in this file */ 3 | 4 | /* TailWindCSS Files */ 5 | @import 'tailwindcss/base'; 6 | @import 'tailwindcss/components'; 7 | @import 'tailwindcss/utilities'; 8 | 9 | /* Fonts */ 10 | @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@200;300;400;500;600;700;800;900&display=swap'); 11 | @import url('https://fonts.googleapis.com/css2?family=Lato:wght@300;400;700;900&display=swap'); 12 | @import url('https://fonts.googleapis.com/css?family=Ubuntu:500'); 13 | 14 | /* Custom Imports */ 15 | @import 'index.css'; 16 | @import 'titlebar.css'; 17 | @import 'scrollbar.css'; 18 | -------------------------------------------------------------------------------- /src/styles/index.css: -------------------------------------------------------------------------------- 1 | input[type='password']::-ms-reveal, 2 | input[type='password']::-ms-clear { 3 | display: none; 4 | } 5 | 6 | #root { 7 | text-align: center; 8 | background-color: transparent; 9 | padding: 40px 24px 24px; 10 | margin: 0px; 11 | box-sizing: border-box; 12 | height: 100vh; 13 | /* overflow: hidden; */ 14 | border-radius: 0.4rem; 15 | /* Clothoid Gradient */ 16 | background: #000f1a; 17 | } 18 | 19 | :root { 20 | font-family: 'Prompt', sans-serif; 21 | font-size: 16px; 22 | line-height: 24px; 23 | font-weight: 400; 24 | 25 | font-synthesis: none; 26 | text-rendering: optimizeLegibility; 27 | -webkit-font-smoothing: antialiased; 28 | -moz-osx-font-smoothing: grayscale; 29 | -webkit-text-size-adjust: 100%; 30 | } 31 | 32 | :root::before { 33 | content: ''; 34 | width: 100%; 35 | height: 100%; 36 | background-image: var(--user-image); 37 | background-size: contain; 38 | background-position: center center; 39 | background-repeat: no-repeat; 40 | opacity: var(--image-opacity); 41 | transition: background-image 300ms ease; 42 | } 43 | 44 | /* This style is REQUIRED due to how kobalte handles focus-visible state */ 45 | * { 46 | outline: none; 47 | } 48 | 49 | *:focus-visible { 50 | outline: 0px; 51 | } 52 | 53 | body { 54 | margin: 0; 55 | font-family: 'Prompt', sans-serif; 56 | cursor: default; 57 | background-color: transparent !important; 58 | width: 100%; 59 | height: 100vh; 60 | text-align: center; 61 | font-size: 14px; 62 | } 63 | 64 | .App { 65 | text-align: center; 66 | height: 100vh; 67 | display: flex; 68 | flex-direction: column; 69 | flex-grow: 1; 70 | } 71 | 72 | .code { 73 | border-radius: 4px; 74 | padding: 3px 6px; 75 | margin-left: 2px; 76 | margin-right: 2px; 77 | background-color: #65758529; 78 | transition: 79 | color 0.25s, 80 | background-color 0.5s; 81 | color: #a8b1ff; 82 | } 83 | -------------------------------------------------------------------------------- /src/styles/scrollbar.css: -------------------------------------------------------------------------------- 1 | ::-webkit-scrollbar { 2 | width: 0px; 3 | } 4 | 5 | .scrollbar::-webkit-scrollbar { 6 | width: 4px; 7 | } 8 | 9 | /* Track */ 10 | .scrollbar::-webkit-scrollbar-track { 11 | border-radius: 10px; 12 | } 13 | 14 | /* Handle */ 15 | .scrollbar::-webkit-scrollbar-thumb { 16 | margin-left: 2px; 17 | background: #9092ff; 18 | border-radius: 12px; 19 | } 20 | 21 | .scrollbarx { 22 | overflow-y: auto; 23 | overflow-x: hidden; 24 | } 25 | 26 | .scrollbar::-webkit-scrollbar { 27 | width: 6px; 28 | } 29 | 30 | .scrollbarx::-webkit-scrollbar:horizontal { 31 | height: 0px; 32 | } 33 | 34 | .scrollbarx::-webkit-scrollbar-thumb:vertical { 35 | background: #9092ff; 36 | border-radius: 12px; 37 | } 38 | 39 | .scrollbarx::-webkit-scrollbar-track:vertical { 40 | border-radius: 10px; 41 | } 42 | -------------------------------------------------------------------------------- /src/styles/titlebar.css: -------------------------------------------------------------------------------- 1 | .titlebar, 2 | .customTitlebar { 3 | background: #000f1a; 4 | z-index: 999; 5 | height: 30px; 6 | user-select: none; 7 | display: flex; 8 | padding: 5px; 9 | justify-content: flex-end; 10 | position: fixed; 11 | border-top-right-radius: 0.4rem; 12 | border-top-left-radius: 0.4rem; 13 | top: 3px; 14 | left: 3px; 15 | right: 3px; 16 | } 17 | 18 | .customTitlebar { 19 | background: #000910; 20 | } 21 | 22 | #app { 23 | border-radius: 50px; 24 | height: 100vh; 25 | background-color: #f3f3f3; 26 | border-radius: 5px; 27 | } 28 | 29 | .titlebar-button { 30 | cursor: pointer; 31 | display: inline-flex; 32 | justify-content: center; 33 | align-items: center; 34 | width: 30px; 35 | height: 30px; 36 | } 37 | 38 | .titlebar-button:hover { 39 | background: #03233a; 40 | } 41 | 42 | .titlebar-docs { 43 | z-index: 999; 44 | height: 30px; 45 | background: #329ea3; /* #9a6998- alternative really nice pink*/ 46 | user-select: none; 47 | display: flex; 48 | justify-content: flex-end; 49 | position: fixed; 50 | border-top-right-radius: 0.4rem; 51 | border-top-left-radius: 0.4rem; 52 | top: 0; 53 | left: 0; 54 | right: 0; 55 | } 56 | 57 | .titlebar-button-docs { 58 | display: inline-flex; 59 | justify-content: center; 60 | align-items: center; 61 | width: 30px; 62 | height: 30px; 63 | } 64 | 65 | .titlebar-button-docs:hover { 66 | background: #5bbec3; 67 | } 68 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { defaultMdnsLength, mdnsLength } from '@src/static' 2 | 3 | export const CapitalizeFirstLetter = (letter: string) => { 4 | return letter.charAt(0).toUpperCase() + letter.slice(1) 5 | } 6 | 7 | export const classNames = (...classes: (string | boolean | undefined)[]): string => { 8 | return classes.filter(Boolean).join(' ') 9 | } 10 | 11 | export const isEmpty = (obj: object | Array) => { 12 | if (!Array.isArray(obj)) { 13 | // ⇒ do not attempt to process array 14 | return Object.keys(obj).length === 0 15 | } 16 | return !obj.length 17 | } 18 | 19 | export const shortMdnsAddress = (text: string) => { 20 | if (text.length < defaultMdnsLength) return text 21 | const firstHalf = text.slice(0, mdnsLength) 22 | const secondHalf = text.slice(text.length - mdnsLength, text.length) 23 | return `${firstHalf}...${secondHalf}` 24 | } 25 | 26 | export const sleep = (ms: number) => { 27 | return new Promise((resolve) => setTimeout(resolve, ms)) 28 | } 29 | export const download = (data: string, filename: string) => { 30 | const blob = new Blob([data], { type: 'text/plain' }) 31 | const anchor = document.createElement('a') 32 | 33 | anchor.download = filename 34 | anchor.href = window.URL.createObjectURL(blob) 35 | anchor.target = '_blank' 36 | anchor.style.display = 'none' 37 | document.body.appendChild(anchor) 38 | anchor.click() 39 | document.body.removeChild(anchor) 40 | } 41 | 42 | export const trimLogsByTextLength = (logs: string, maxLength: number): string[] => { 43 | if (!logs.trim().length) return [] 44 | if (logs.length <= maxLength) return [logs] 45 | 46 | const validLogs: string[] = [] 47 | let buffer = '' 48 | let start = 0 49 | 50 | while (start < logs.length) { 51 | const end = Math.min(start + maxLength, logs.length) 52 | 53 | let lastSpaceIndex = logs.lastIndexOf(' ', end) 54 | if (lastSpaceIndex === -1 || lastSpaceIndex < start) { 55 | lastSpaceIndex = end 56 | } 57 | 58 | const substring = logs.slice(start, lastSpaceIndex) 59 | 60 | buffer += substring.trim() 61 | 62 | if (buffer.length >= maxLength) { 63 | validLogs.push(buffer) 64 | buffer = '' 65 | } else { 66 | buffer += ' ' 67 | } 68 | 69 | start = lastSpaceIndex + 1 70 | } 71 | 72 | if (buffer.trim().length > 0) { 73 | validLogs.push(buffer.trim()) 74 | } 75 | 76 | return validLogs 77 | } 78 | 79 | export const shortName = (label: string, size: number = 12): string => { 80 | if (label.length <= size * 2) return label 81 | return `${label.slice(0, size)}...${label.slice(-size)}` 82 | } 83 | 84 | export const stringToHex = (str: string) => { 85 | let hex = '' 86 | for (let i = 0; i < str.length; i++) { 87 | // Convert each character to its UTF-16 code unit and then to hex 88 | hex += str.charCodeAt(i).toString(16).padStart(2, '0') // pad to 2 digits 89 | } 90 | return hex 91 | } 92 | 93 | export const formatDeviceName = (filename: string): string => { 94 | return filename 95 | .replace(/\.zip$/, '') 96 | .split('-v')[0] 97 | .replace(/-/g, '_') 98 | } 99 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | /* interface ImportMeta { 4 | } */ 5 | 6 | declare module '*.scss' 7 | declare module '*.png' 8 | declare module '*.jpeg' 9 | declare module '*.svg' 10 | -------------------------------------------------------------------------------- /src/windows/docs/docs.tsx: -------------------------------------------------------------------------------- 1 | /* @refresh reload */ 2 | import { Router } from '@solidjs/router' 3 | import { onMount, Suspense } from 'solid-js' 4 | import { render } from 'solid-js/web' 5 | import { useAppContextMain, AppContextMainProvider } from '@src/store/context/main' 6 | import '@styles/docs-imports.css' 7 | 8 | const App = () => { 9 | const { handleTitlebar } = useAppContextMain() 10 | 11 | onMount(() => { 12 | handleTitlebar() 13 | }) 14 | 15 | return ( 16 |
17 | 18 |
19 |