diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index c2dd9e6..448d260 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -302,8 +302,8 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - tagName: app-v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version. - releaseName: "App v__VERSION__" + tagName: SDREHub-v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version. + releaseName: "SDR-E Hub v__VERSION__" releaseBody: "See the assets to download this version and install." releaseDraft: false prerelease: false @@ -333,6 +333,7 @@ jobs: get_version_method: cargo_toml_file_in_repo:file=/Cargo.toml cache_enabled: true cache_path: ./bin/ + cache_key: ${{ github.run_id }} secrets: ghcr_token: ${{ secrets.GITHUB_TOKEN }} @@ -360,6 +361,7 @@ jobs: get_version_method: cargo_toml_file_in_repo:file=/Cargo.toml cache_enabled: true cache_path: ./bin/ + cache_key: ${{ github.run_id }} docker_latest_tag: test secrets: ghcr_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/on_pr.yaml b/.github/workflows/on_pr.yaml index 728f6f4..3f80a15 100644 --- a/.github/workflows/on_pr.yaml +++ b/.github/workflows/on_pr.yaml @@ -316,9 +316,9 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - tagName: app-v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version. - releaseName: "App v__VERSION__" - releaseBody: "See the assets to download this version and install." - releaseDraft: true - prerelease: false + # tagName: app-v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version. + # releaseName: "App v__VERSION__" + # releaseBody: "See the assets to download this version and install." + # releaseDraft: true + # prerelease: false args: ${{ matrix.args }} diff --git a/Cargo.lock b/Cargo.lock index b9aa825..89d172e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -933,6 +933,15 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "directories" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" +dependencies = [ + "dirs-sys", +] + [[package]] name = "dirs-next" version = "2.0.0" @@ -943,6 +952,18 @@ dependencies = [ "dirs-sys-next", ] +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "dirs-sys-next" version = "0.1.2" @@ -2604,6 +2625,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "ordered-stream" version = "0.2.0" @@ -3454,9 +3481,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.198" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" +checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" dependencies = [ "serde_derive", ] @@ -3474,9 +3501,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.198" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" +checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" dependencies = [ "proc-macro2", "quote", @@ -3621,6 +3648,7 @@ dependencies = [ name = "sh-config" version = "4.0.0-alpha.1" dependencies = [ + "directories", "figment", "log", "sdre-rust-logging", @@ -4040,14 +4068,14 @@ dependencies = [ [[package]] name = "tauri-build" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9914a4715e0b75d9f387a285c7e26b5bbfeb1249ad9f842675a82481565c532" +checksum = "ab30cba12974d0f9b09794f61e72cad6da2142d3ceb81e519321bab86ce53312" dependencies = [ "anyhow", "cargo_toml", "dirs-next", - "heck 0.4.1", + "heck 0.5.0", "json-patch", "semver", "serde", @@ -4141,15 +4169,15 @@ dependencies = [ [[package]] name = "tauri-utils" -version = "1.5.3" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75ad0bbb31fccd1f4c56275d0a5c3abdf1f59999f72cb4ef8b79b4ed42082a21" +checksum = "450b17a7102e5d46d4bdabae0d1590fd27953e704e691fc081f06c06d2253b35" dependencies = [ "brotli", "ctor", "dunce", "glob", - "heck 0.4.1", + "heck 0.5.0", "html5ever", "infer", "json-patch", diff --git a/Cargo.toml b/Cargo.toml index 5939c45..af37a85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ homepage = "https://github.com/sdr-enthusiasts/sdre-hub" repository = "https://github.com/sdr-enthusiasts/sdre-hub" readme = "README.MD" license = "MIT" -rust-version = "1.77.2" +rust-version = "1.78.0" keywords = [ "acars", "vdlm", diff --git a/Dockerfile b/Dockerfile index 14347b3..48cf4f6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ COPY ./bin/sdre-hub.armv7/sdre-hub /opt/sdre-hub.armv7 COPY ./bin/sdre-hub.arm64/sdre-hub /opt/sdre-hub.arm64 COPY ./bin/sdre-hub.amd64/sdre-hub /opt/sdre-hub.amd64 COPY ./bin/sh-frontend /opt/sh-frontend - +ENV SH_DATA_PATH="/opt/sdre-hub-data" # hadolint ignore=DL3008,DL3003,SC1091 RUN set -x && \ KEPT_PACKAGES=() && \ diff --git a/Dockerfile.build_binary b/Dockerfile.build_binary index c29c34f..b527d8b 100644 --- a/Dockerfile.build_binary +++ b/Dockerfile.build_binary @@ -1,4 +1,4 @@ -FROM rust:1.77.2 as builder +FROM rust:1.78.0 as builder ENV CARGO_NET_GIT_FETCH_WITH_CLI=true WORKDIR /tmp/sdre-hub # hadolint ignore=DL3008,DL3003,SC1091,DL4006,DL3009 diff --git a/Dockerfile.build_frontend b/Dockerfile.build_frontend index 6f8b9cb..6d60405 100644 --- a/Dockerfile.build_frontend +++ b/Dockerfile.build_frontend @@ -1,4 +1,4 @@ -FROM rust:1.77.2 as builder +FROM rust:1.78.0 as builder WORKDIR /tmp/sdre-hub # hadolint ignore=DL3008,DL3003,SC1091,DL4006,DL3009 RUN set -x && \ diff --git a/Dockerfile.local b/Dockerfile.local index 9799389..54f50a2 100644 --- a/Dockerfile.local +++ b/Dockerfile.local @@ -1,9 +1,9 @@ -FROM rust:1.77.2 as builder +FROM rust:1.78.0 as builder WORKDIR /tmp/sdre-hub # hadolint ignore=DL3008,DL3003,SC1091,DL4006 RUN set -x && \ apt-get update && \ - apt-get install -y --no-install-recommends libzmq3-dev \ + apt-get install -y --no-install-recommends libzmq3-dev && \ curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash && \ curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh && \ cargo binstall trunk wasm-bindgen-cli --no-confirm && \ @@ -23,6 +23,7 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"] COPY rootfs / COPY --from=builder /tmp/sdre-hub/target/release/sdre-hub /opt/sdre-hub COPY --from=builder /tmp/sdre-hub/sh-frontend/dist /opt/sh-frontend +ENV SH_DATA_PATH="/opt/sdre-hub-data" # hadolint ignore=DL3008,DL3003,SC1091 RUN set -x && \ KEPT_PACKAGES=() && \ diff --git a/README.md b/README.md index 0a28008..25c4c5b 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ Sorry, there is no migration path from ACARS Hub to SDR-E Hub. The data that ACA - [x] Application build - [ ] Manage settings from the web interface - [x] GitHub CI + - [ ] FIXME/TODO Cleanup - [ ] Alpha 2 - [ ] Connect to data providers - [ ] ADSB @@ -39,13 +40,16 @@ Sorry, there is no migration path from ACARS Hub to SDR-E Hub. The data that ACA - [ ] Satellite ACARS - [ ] Combine data from data providers and provide a state machine to manage data - [ ] Display data on the web interface + - [ ] FIXME/TODO Cleanup - [ ] Alpha 3 - [ ] Implement database storage - [ ] RRD style data storage for relevant stats - [ ] SQL storage for historical data - [ ] General stats + - [ ] FIXME/TODO Cleanup - [ ] Beta 1 - [ ] Focus on interface; clean it up, mobile functionality, etc + - [ ] FIXME/TODO Cleanup ## Thank you diff --git a/rootfs/etc/nginx.sdrehub/sites-enabled/sdrehub b/rootfs/etc/nginx.sdrehub/sites-enabled/sdrehub index 69f3aa4..1b16c89 100644 --- a/rootfs/etc/nginx.sdrehub/sites-enabled/sdrehub +++ b/rootfs/etc/nginx.sdrehub/sites-enabled/sdrehub @@ -19,7 +19,7 @@ server { access_log off; # cache all of the files - location ~ \.(css|js|png|jpg|jpeg|gif|ico|woff|woff2|ttf|svg|eot|otf|map|mjs|mp3|wasm)$ { + location ~ \.(css|js|png|jpg|jpeg|gif|ico|woff|woff2|ttf|svg|eot|otf|map|mjs|mp3|wasm|webmanifest)$ { expires 30d; add_header Cache-Control "public"; root /opt/sh-frontend; diff --git a/rootfs/etc/s6-overlay/scripts/01-config-check b/rootfs/etc/s6-overlay/scripts/01-config-check index 96c1ba4..ccac118 100755 --- a/rootfs/etc/s6-overlay/scripts/01-config-check +++ b/rootfs/etc/s6-overlay/scripts/01-config-check @@ -1,5 +1,6 @@ -#!/command/with-contenv bash -# shellcheck shell=bash +#!/bin/bash +## !/command/with-contenv bash +## shellcheck shell=bash # check the environment and see if there are any SH_* variables set # if so, warn the user that they really should be mapping a file @@ -7,6 +8,10 @@ SH_DATA=$(env | grep '^SH_') +# remove SH_DATA_PATH, if it exists in SH_DATA from the list of variables to check + +SH_DATA=$(echo "$SH_DATA" | grep -v '^SH_DATA_PATH') + if [ -n "$SH_DATA" ]; then echo "[01-config-check] WARNING: You have SH_* environment variables set. You should be using a config file instead." echo " Please map a directory to /opt/sdre-hub-data and use /opt/sdre-hub-data/sh_config.toml" diff --git a/rootfs/etc/s6-overlay/scripts/nginx b/rootfs/etc/s6-overlay/scripts/nginx index 4bce988..f824e59 100755 --- a/rootfs/etc/s6-overlay/scripts/nginx +++ b/rootfs/etc/s6-overlay/scripts/nginx @@ -1,13 +1,9 @@ #!/command/with-contenv bash #shellcheck shell=bash -# shellcheck disable=SC2016 -echo "Starting web proxy service" | stdbuf -oL awk '{print "[nginx ] " strftime("%Y/%m/%d %H:%M:%S", systime()) " " $0}' +source /scripts/common +s6wrap=(s6wrap --quiet --timestamps --prepend="$(basename "$0")" --args) - -# shellcheck disable=SC2016 mkdir -p /var/log/nginx -# TODO: Run this via wiede's script -# shellcheck disable=SC2016 -exec \ -/usr/sbin/nginx 2>&1 | stdbuf -oL awk '{print "[nginx ] " strftime("%Y/%m/%d %H:%M:%S", systime()) " " $0}' + +"${s6wrap[@]}" /usr/sbin/nginx diff --git a/sh-frontend/Cargo.lock b/sh-frontend/Cargo.lock index afd8a3d..c977c77 100644 --- a/sh-frontend/Cargo.lock +++ b/sh-frontend/Cargo.lock @@ -839,6 +839,19 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "leaflet" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1ca4f4ecc791314ac38d50a5af6dc6506977aa7c7f0231b1bfd66989a9c4be3" +dependencies = [ + "js-sys", + "paste", + "url", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "libc" version = "0.2.153" @@ -976,6 +989,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1245,9 +1264,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.198" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" +checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" dependencies = [ "serde_derive", ] @@ -1276,9 +1295,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.198" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" +checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" dependencies = [ "proc-macro2", "quote", @@ -1308,6 +1327,24 @@ dependencies = [ "serde", ] +[[package]] +name = "sh-frontend" +version = "4.0.0-alpha.1" +dependencies = [ + "gloo-utils 0.2.0", + "leaflet", + "log", + "reqwest", + "serde", + "wasm-bindgen", + "wasm-bindgen-test", + "wasm-logger", + "web-sys", + "yew", + "yew-hooks", + "yew-router", +] + [[package]] name = "slab" version = "0.4.9" @@ -1921,23 +1958,6 @@ dependencies = [ "yew-macro", ] -[[package]] -name = "yew-app" -version = "0.1.0" -dependencies = [ - "gloo-utils 0.2.0", - "log", - "reqwest", - "serde", - "wasm-bindgen", - "wasm-bindgen-test", - "wasm-logger", - "web-sys", - "yew", - "yew-hooks", - "yew-router", -] - [[package]] name = "yew-hooks" version = "0.3.1" diff --git a/sh-frontend/Cargo.toml b/sh-frontend/Cargo.toml index 553df43..280a5ed 100644 --- a/sh-frontend/Cargo.toml +++ b/sh-frontend/Cargo.toml @@ -1,31 +1,60 @@ [package] -authors = ["You "] -categories = ["wasm"] -description = "My awesome Yew app." +name = "sh-frontend" +version = "4.0.0-alpha.1" edition = "2021" -license = "Apache-2.0/MIT" -name = "yew-app" -readme = "./README.md" -repository = "https://github.com/jetli/create-yew-app.git" -version = "0.1.0" +authors = ["Fred Clausen"] +description = "SDR-E Hub is a consumer of ACARS messages from many sources, matching them up with ADSB and other data, and then displaying them in a web interface." +documentation = "https://github.com/sdr-enthusiasts/sdre-hub/README.md" +homepage = "https://github.com/sdr-enthusiasts/sdre-hub" +repository = "https://github.com/sdr-enthusiasts/sdre-hub" +readme = "README.MD" +license = "MIT" +rust-version = "1.78.0" +keywords = [ + "acars", + "vdlm", + "vdlm2", + "vdl-m2", + "hfdl", + "adsb", + "docker", + "web", + "sdr", + "rtl-sdr", + "aircraft", + "airplane", + "airline", + "flight", + "tracking", + "hub", + "server", + "client", + "frontend", + "backend", + "api", +] +categories = ["aerospace"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +gloo-utils = "0.2.0" +leaflet = "0.4.0" log = "0.4.21" -serde = "1.0.198" +serde = "1.0.200" reqwest = { version = "0.12.4", features = ["json"] } yew = { version = "0.21.0", features = ["csr"] } yew-router = "0.18.0" yew-hooks = "0.3.1" wasm-bindgen = "0.2.92" wasm-logger = "0.2.0" - -[dev-dependencies] -wasm-bindgen-test = "0.3.42" -gloo-utils = "0.2.0" web-sys = { version = "0.3.69", features = [ "Document", "Element", "HtmlCollection", + "KeyboardEvent", ] } + +[dev-dependencies] +wasm-bindgen-test = "0.3.42" +gloo-utils = "0.2.0" diff --git a/sh-frontend/LICENSE-APACHE b/sh-frontend/LICENSE-APACHE deleted file mode 100644 index 261eeb9..0000000 --- a/sh-frontend/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/sh-frontend/LICENSE-MIT b/sh-frontend/LICENSE-MIT deleted file mode 100644 index 140aafe..0000000 --- a/sh-frontend/LICENSE-MIT +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (c) 2018 The Rust Project Developers - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/sh-frontend/index.html b/sh-frontend/index.html index 2da7f94..947c418 100644 --- a/sh-frontend/index.html +++ b/sh-frontend/index.html @@ -3,14 +3,35 @@ - Yew App + SDR Enthusiasts Hub - - + + + + + + + + + + + + + - + + diff --git a/sh-frontend/notes.txt b/sh-frontend/notes.txt new file mode 100644 index 0000000..030a3ea --- /dev/null +++ b/sh-frontend/notes.txt @@ -0,0 +1,3 @@ +CHARTJS! https://tms-dev-blog.com/css-and-javascript-wasm-rust-yew-how-to-p2/ +https://github.com/jetli/awesome-yew?tab=readme-ov-file various stuff, including rust wrapper for plotly.js +darker purple than diff --git a/sh-frontend/pallet-initial.png b/sh-frontend/pallet-initial.png new file mode 100644 index 0000000..c2b596a Binary files /dev/null and b/sh-frontend/pallet-initial.png differ diff --git a/sh-frontend/pallet.png b/sh-frontend/pallet.png new file mode 100644 index 0000000..041ccee Binary files /dev/null and b/sh-frontend/pallet.png differ diff --git a/sh-frontend/public/android-chrome-192x192.png b/sh-frontend/public/android-chrome-192x192.png new file mode 100644 index 0000000..a119558 Binary files /dev/null and b/sh-frontend/public/android-chrome-192x192.png differ diff --git a/sh-frontend/public/android-chrome-512x512.png b/sh-frontend/public/android-chrome-512x512.png new file mode 100644 index 0000000..d505396 Binary files /dev/null and b/sh-frontend/public/android-chrome-512x512.png differ diff --git a/sh-frontend/public/apple-touch-icon.png b/sh-frontend/public/apple-touch-icon.png new file mode 100644 index 0000000..42572d9 Binary files /dev/null and b/sh-frontend/public/apple-touch-icon.png differ diff --git a/sh-frontend/public/favicon-16x16.png b/sh-frontend/public/favicon-16x16.png new file mode 100644 index 0000000..7ac3654 Binary files /dev/null and b/sh-frontend/public/favicon-16x16.png differ diff --git a/sh-frontend/public/favicon-32x32.png b/sh-frontend/public/favicon-32x32.png new file mode 100644 index 0000000..2997b6e Binary files /dev/null and b/sh-frontend/public/favicon-32x32.png differ diff --git a/sh-frontend/public/favicon.ico b/sh-frontend/public/favicon.ico index b8ad237..7986b2d 100644 Binary files a/sh-frontend/public/favicon.ico and b/sh-frontend/public/favicon.ico differ diff --git a/sh-frontend/public/logo.svg b/sh-frontend/public/logo.svg index e838a2c..dd6f4ca 100644 --- a/sh-frontend/public/logo.svg +++ b/sh-frontend/public/logo.svg @@ -1,7 +1,18 @@ - - - - - - + + + + + + + + + + + + + + + + + diff --git a/sh-frontend/public/site.webmanifest b/sh-frontend/public/site.webmanifest new file mode 100644 index 0000000..fa99de7 --- /dev/null +++ b/sh-frontend/public/site.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "", + "short_name": "", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/sh-frontend/src/app/about.rs b/sh-frontend/src/app/about.rs deleted file mode 100644 index 90a50ea..0000000 --- a/sh-frontend/src/app/about.rs +++ /dev/null @@ -1,136 +0,0 @@ -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use yew::prelude::*; -use yew_hooks::prelude::*; - -/// About page -#[function_component(About)] -pub fn about() -> Html { - let state = - use_async(async move { fetch_repo(("jetli/create-yew-app".to_string()).clone()).await }); - - let onclick = { - let state = state.clone(); - Callback::from(move |_| { - state.run(); - }) - }; - - html! { -
-
-

- - { "Create Yew App" } - - { ", Set up a modern yew web app by running one command." } -

-

- -

-

- { - if state.loading { - html! { "Loading, wait a sec..." } - } else { - html! {} - } - } -

- { - if let Some(repo) = &state.data { - html! { - <> -

{ "Repo name: " }{ &repo.name }

-

{ "Repo full name: " }{ &repo.full_name }

-

{ "Repo description: " }{ &repo.description }

- - } - } else { - html! {} - } - } -

- { - if let Some(error) = &state.error { - match error { - Error::DeserializeError => html! { "DeserializeError" }, - Error::RequestError => html! { "RequestError" }, - } - } else { - html! {} - } - } -

-

- { "Edit " } { "src/app/about.rs" } { " and save to reload." } -

-
-
- } -} - -async fn fetch_repo(repo: String) -> Result { - fetch::(format!("https://api.github.com/repos/{}", repo)).await -} - -/// You can use reqwest or other crates to fetch your api. -async fn fetch(url: String) -> Result -where - T: DeserializeOwned, -{ - let response = reqwest::get(url).await; - if let Ok(data) = response { - if let Ok(repo) = data.json::().await { - Ok(repo) - } else { - Err(Error::DeserializeError) - } - } else { - Err(Error::RequestError) - } -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -struct Repo { - id: i32, - name: String, - full_name: String, - description: String, -} - -// You can use thiserror to define your errors. -#[derive(Clone, Debug, PartialEq)] -enum Error { - RequestError, - DeserializeError, - // etc. -} - -#[cfg(test)] -mod tests { - use std::time::Duration; - use wasm_bindgen_test::*; - use yew::platform::time::sleep; - - wasm_bindgen_test_configure!(run_in_browser); - - use super::About; - - #[wasm_bindgen_test] - async fn about_page_has_an_app_link() { - yew::Renderer::::with_root( - gloo_utils::document().get_element_by_id("output").unwrap(), - ) - .render(); - - sleep(Duration::ZERO).await; - - let app_links = gloo_utils::document().get_elements_by_tag_name("a"); - - assert_eq!(app_links.length(), 1); - - let link = app_links.item(0).expect("No app-link").inner_html(); - assert_eq!(link, "Create Yew App"); - } -} diff --git a/sh-frontend/src/app/home.rs b/sh-frontend/src/app/home.rs deleted file mode 100644 index 59c088e..0000000 --- a/sh-frontend/src/app/home.rs +++ /dev/null @@ -1,40 +0,0 @@ -use yew::prelude::*; -use yew_hooks::prelude::*; - -/// Home page -#[function_component(Home)] -pub fn home() -> Html { - let counter = use_counter(0); - - let onincrease = { - let counter = counter.clone(); - Callback::from(move |_| counter.increase()) - }; - let ondecrease = { - let counter = counter.clone(); - Callback::from(move |_| counter.decrease()) - }; - - html! { -
-
-

- - Yew - -

-

- { "Learn Yew" } -

-

- - { *counter } - -

-

- { "Edit " } { "src/app/home.rs" } { " and save to reload." } -

-
-
- } -} diff --git a/sh-frontend/src/app/live.rs b/sh-frontend/src/app/live.rs new file mode 100644 index 0000000..b3ad5b8 --- /dev/null +++ b/sh-frontend/src/app/live.rs @@ -0,0 +1,161 @@ +// Copyright (C) 2024 Fred Clausen +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use crate::components::acars_messages::AcarsMessages; +use crate::components::map_display::ShMap; +use yew::prelude::*; +use yew_hooks::{use_event_with_window, use_window_size}; + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +enum Panels { + Messages, + Map, + Settings, + Help, + None, +} + +impl Panels { + fn next(&self, skip: Panels) -> Panels { + // go to the next panel, skipping the one we're currently on + + match self { + Panels::Messages => { + if skip == Panels::Map { + Panels::Settings + } else { + Panels::Map + } + } + Panels::Map => { + if skip == Panels::Settings { + Panels::Help + } else { + Panels::Settings + } + } + Panels::Settings => { + if skip == Panels::Help { + Panels::Messages + } else { + Panels::Help + } + } + Panels::Help => { + if skip == Panels::Messages { + Panels::Map + } else { + Panels::Messages + } + } + Panels::None => Panels::None, + } + } + + fn previous(&self, skip: Panels) -> Panels { + // go to the previous panel, skipping the one we're currently on + + match self { + Panels::Messages => { + if skip == Panels::Help { + Panels::Settings + } else { + Panels::Help + } + } + Panels::Map => { + if skip == Panels::Messages { + Panels::Help + } else { + Panels::Messages + } + } + Panels::Settings => { + if skip == Panels::Map { + Panels::Messages + } else { + Panels::Map + } + } + Panels::Help => { + if skip == Panels::Settings { + Panels::Map + } else { + Panels::Settings + } + } + Panels::None => Panels::None, + } + } +} + +/// Home page +#[function_component(Live)] +pub fn live() -> Html { + log::debug!("Rendering Live page"); + // TODO: Grab current state from local storage + let left_panel = use_state(|| Panels::Messages); + + let right_panel = use_state(|| Panels::Map); + + let right_panel_status = { + let right_panel = right_panel.clone(); + match *right_panel { + Panels::Messages => html! { }, + Panels::Map => html! { }, + Panels::Settings => html! {
{"Settings"}
}, + Panels::Help => html! {
{"Help"}
}, + Panels::None => html! {
{"None"}
}, + } + }; + + let left_panel_show = { + match *left_panel { + Panels::Messages => html! { }, + Panels::Map => html! { }, + Panels::Settings => html! {
{"Settings"}
}, + Panels::Help => html! {
{"Help"}
}, + Panels::None => html! {
{"None"}
}, + } + }; + + use_event_with_window("keydown", move |e: KeyboardEvent| { + log::debug!("Key pressed: {}", e.key()); + + let right_panel = right_panel.clone(); + let left_panel = left_panel.clone(); + + // if control is pressed, with left arrow, go to the previous panel + if e.key() == "F1" { + right_panel.set(right_panel.previous(*left_panel)); + } + + // if control is pressed, with right arrow, go to the next panel + if e.key() == "F2" { + right_panel.set(right_panel.next(*left_panel)); + } + + // if alt is pressed, with left arrow, go to the previous panel + if e.key() == "F3" { + left_panel.set(left_panel.previous(*right_panel)); + } + + // if alt is pressed, with right arrow, go to the next panel + if e.key() == "F4" { + left_panel.set(left_panel.next(*right_panel)); + } + }); + + html! { +
+
+ { left_panel_show.clone() } +
+ +
+ } +} diff --git a/sh-frontend/src/app/mod.rs b/sh-frontend/src/app/mod.rs index 62d270c..e23044c 100644 --- a/sh-frontend/src/app/mod.rs +++ b/sh-frontend/src/app/mod.rs @@ -1,31 +1,33 @@ +// Copyright (C) 2024 Fred Clausen +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + use yew::prelude::*; use yew_router::prelude::*; -pub mod about; -pub mod home; +pub mod live; use crate::components::nav::Nav; -use about::About; -use home::Home; +use live::Live; /// App routes #[derive(Routable, Debug, Clone, PartialEq, Eq)] -pub enum AppRoute { - #[at("/about")] - About, +pub enum ShAppRoute { #[not_found] #[at("/page-not-found")] PageNotFound, #[at("/")] - Home, + Live, } /// Switch app routes -pub fn switch(routes: AppRoute) -> Html { - match routes.clone() { - AppRoute::Home => html! { }, - AppRoute::About => html! { }, - AppRoute::PageNotFound => html! { "Page not found" }, +// we need this to stop clippy whinging about something we can't control. +#[allow(clippy::needless_pass_by_value)] +pub fn switch(routes: ShAppRoute) -> Html { + match routes { + ShAppRoute::Live => html! { }, + ShAppRoute::PageNotFound => html! { "Page not found" }, } } @@ -34,9 +36,11 @@ pub fn switch(routes: AppRoute) -> Html { pub fn app() -> Html { html! { -
+
} diff --git a/sh-frontend/src/components/acars_messages.rs b/sh-frontend/src/components/acars_messages.rs new file mode 100644 index 0000000..3b9d0e3 --- /dev/null +++ b/sh-frontend/src/components/acars_messages.rs @@ -0,0 +1,16 @@ +// Copyright (C) 2024 Fred Clausen +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use yew::prelude::*; + +#[function_component(AcarsMessages)] +pub fn acars_messages() -> Html { + html! { +
+

{"ACARS Messages"}

+

{"This is a placeholder for the ACARS messages page."}

+
+ } +} diff --git a/sh-frontend/src/components/control.rs b/sh-frontend/src/components/control.rs new file mode 100644 index 0000000..18ac6e5 --- /dev/null +++ b/sh-frontend/src/components/control.rs @@ -0,0 +1,71 @@ +// Copyright (C) 2024 Fred Clausen +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use super::map::City; +use yew::{html::ImplicitClone, prelude::*}; + +pub enum Msg { + CityChosen(City), +} + +pub struct Control { + cities: Vec, +} + +#[derive(PartialEq, Clone)] +pub struct Cities { + pub list: Vec, +} + +impl ImplicitClone for Cities {} + +#[derive(PartialEq, Properties, Clone)] +pub struct Props { + pub cities: Cities, + pub select_city: Callback, +} + +impl Control { + fn button(&self, ctx: &Context, city: City) -> Html { + let name = city.name.clone(); + let cb = ctx.link().callback(move |_| Msg::CityChosen(city.clone())); + html! { + + } + } +} + +impl Component for Control { + type Message = Msg; + type Properties = Props; + + fn create(ctx: &Context) -> Self { + Control { + cities: ctx.props().cities.list.clone(), + } + } + + fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { + match msg { + Msg::CityChosen(city) => { + log::info!("Update: {:?}", city.name); + ctx.props().select_city.emit(city); + } + } + true + } + + fn view(&self, ctx: &Context) -> Html { + html! { +
+

{"Choose a city"}

+
+ {for self.cities.iter().map(|city| Self::button(self, ctx, city.clone()))} +
+ +
+ } + } +} diff --git a/sh-frontend/src/components/help.rs b/sh-frontend/src/components/help.rs new file mode 100644 index 0000000..240edcd --- /dev/null +++ b/sh-frontend/src/components/help.rs @@ -0,0 +1,14 @@ +// Copyright (C) 2024 Fred Clausen +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use yew::prelude::*; + +/// About page +#[function_component(Help)] +pub fn help() -> Html { + html! { + {"Placeholder"} + } +} diff --git a/sh-frontend/src/components/map.rs b/sh-frontend/src/components/map.rs new file mode 100644 index 0000000..faf3c5e --- /dev/null +++ b/sh-frontend/src/components/map.rs @@ -0,0 +1,97 @@ +// Copyright (C) 2024 Fred Clausen +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use gloo_utils::document; +use leaflet::{LatLng, Map, MapOptions, TileLayer}; +use wasm_bindgen::JsCast; +use web_sys::{Element, HtmlElement, Node}; +use yew::{html::ImplicitClone, prelude::*}; + +pub enum Msg {} + +pub struct MapComponent { + map: Map, + lat: Point, + container: HtmlElement, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct Point(pub f64, pub f64); + +#[derive(PartialEq, Clone, Debug)] +pub struct City { + pub name: String, + pub lat: Point, +} + +impl ImplicitClone for City {} + +#[derive(PartialEq, Properties, Clone)] +pub struct Props { + pub city: City, +} + +impl MapComponent { + fn render_map(&self) -> Html { + let node: &Node = &self.container.clone().into(); + Html::VRef(node.clone()) + } +} + +impl Component for MapComponent { + type Message = Msg; + type Properties = Props; + + fn create(ctx: &Context) -> Self { + let props = ctx.props(); + + let container: Element = document().create_element("div").unwrap(); + let container: HtmlElement = container.dyn_into().unwrap(); + container.set_class_name("map"); + let leaflet_map = Map::new_with_element(&container, &MapOptions::default()); + Self { + map: leaflet_map, + container, + lat: props.city.lat, + } + } + + fn rendered(&mut self, _ctx: &Context, first_render: bool) { + if first_render { + self.map + .set_view(&LatLng::new(self.lat.0, self.lat.1), 11.0); + add_tile_layer(&self.map); + } + } + + fn update(&mut self, _ctx: &Context, _msg: Self::Message) -> bool { + false + } + + fn changed(&mut self, ctx: &Context, _old_props: &Self::Properties) -> bool { + let props = ctx.props(); + + if self.lat == props.city.lat { + false + } else { + self.lat = props.city.lat; + self.map + .set_view(&LatLng::new(self.lat.0, self.lat.1), 11.0); + true + } + } + + fn view(&self, _ctx: &Context) -> Html { + html! { +
+ {self.render_map()} +
+ } + } +} + +fn add_tile_layer(map: &Map) { + TileLayer::new("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png").add_to(map); +} diff --git a/sh-frontend/src/components/map_display.rs b/sh-frontend/src/components/map_display.rs new file mode 100644 index 0000000..ee10510 --- /dev/null +++ b/sh-frontend/src/components/map_display.rs @@ -0,0 +1,63 @@ +// Copyright (C) 2024 Fred Clausen +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use crate::components::{ + control::Cities, + map::{City, MapComponent, Point}, +}; +use yew::prelude::*; + +pub enum Msg { + SelectCity(City), +} + +pub struct ShMap { + city: City, + cities: Cities, +} + +impl Component for ShMap { + type Message = Msg; + type Properties = (); + + fn create(_ctx: &Context) -> Self { + let aachen = City { + name: "Aachen".to_string(), + lat: Point(50.7597f64, 6.0967f64), + }; + let stuttgart = City { + name: "Stuttgart".to_string(), + lat: Point(48.7784f64, 9.1742f64), + }; + let cities: Cities = Cities { + list: vec![aachen, stuttgart], + }; + let city = cities.list[0].clone(); + Self { city, cities } + } + + fn update(&mut self, _ctx: &Context, msg: Self::Message) -> bool { + match msg { + Msg::SelectCity(city) => { + self.city = self + .cities + .list + .iter() + .find(|c| c.name == city.name) + .unwrap() + .clone(); + } + } + true + } + + fn view(&self, _ctx: &Context) -> Html { + html! { + <> + + + } + } +} diff --git a/sh-frontend/src/components/mod.rs b/sh-frontend/src/components/mod.rs index 8b3f5a2..883dcda 100644 --- a/sh-frontend/src/components/mod.rs +++ b/sh-frontend/src/components/mod.rs @@ -1 +1,11 @@ +// Copyright (C) 2024 Fred Clausen +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +pub mod acars_messages; +pub mod control; +pub mod map; +pub mod map_display; pub mod nav; +pub mod search; diff --git a/sh-frontend/src/components/nav.rs b/sh-frontend/src/components/nav.rs index bd86c09..6c4709c 100644 --- a/sh-frontend/src/components/nav.rs +++ b/sh-frontend/src/components/nav.rs @@ -1,20 +1,94 @@ +// Copyright (C) 2024 Fred Clausen +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use std::{fmt, ops::Not}; + use yew::prelude::*; use yew_router::prelude::*; -use crate::app::AppRoute; +use super::search::Search; +use crate::app::ShAppRoute; + +// This enum seems a bit overkill. Originally, I wrote this to just use a standard bool, but I wanted to +// But the code's readability is improved by using this enum. Also, for state management in Yew, it's better to use +// To control the state of the menu, and more specifically to just know the state which now we do. + +#[derive(Debug, Clone, Copy)] +enum Checked { + True, + False, +} + +impl Not for Checked { + type Output = Self; + + fn not(self) -> Self { + match self { + Self::True => Self::False, + Self::False => Self::True, + } + } +} + +impl From for bool { + fn from(checked: Checked) -> Self { + match checked { + Checked::True => true, + Checked::False => false, + } + } +} + +impl fmt::Display for Checked { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::True => write!(f, "True"), + Self::False => write!(f, "False"), + } + } +} /// Nav component #[function_component(Nav)] pub fn nav() -> Html { + let menu_state = use_state(|| Checked::False); + + let mouse_hide_menu = { + let menu_state = menu_state.clone(); + Callback::from(move |_: MouseEvent| menu_state.set(Checked::False)) + }; + + let mouse_show_menu = { + let menu_state = menu_state.clone(); + let current_state = *menu_state; + Callback::from(move |_: MouseEvent| menu_state.set(!current_state)) + }; + + let hidden_menu = { + let menu_state = *menu_state; + log::info!("Menu state: {menu_state}"); + menu_state + }; + html! { -
- -
+
+ + + + + // FIXME: I think we should be fixing the menu link stuff to a width based on container size? + +
} } diff --git a/sh-frontend/src/components/search.rs b/sh-frontend/src/components/search.rs new file mode 100644 index 0000000..7a59cc0 --- /dev/null +++ b/sh-frontend/src/components/search.rs @@ -0,0 +1,16 @@ +// Copyright (C) 2024 Fred Clausen +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use yew::prelude::*; + +// Search component +#[function_component(Search)] +pub fn search() -> Html { + html! { +
+ +
+ } +} diff --git a/sh-frontend/src/components/settings.rs b/sh-frontend/src/components/settings.rs new file mode 100644 index 0000000..69a8ea4 --- /dev/null +++ b/sh-frontend/src/components/settings.rs @@ -0,0 +1,16 @@ +// Copyright (C) 2024 Fred Clausen +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use yew::prelude::*; + +/// Home page +#[function_component(Settings)] +pub fn settings() -> Html { + html! { + { + "Settings" + } + } +} diff --git a/sh-frontend/src/lib.rs b/sh-frontend/src/lib.rs index 13545c4..87db832 100644 --- a/sh-frontend/src/lib.rs +++ b/sh-frontend/src/lib.rs @@ -1,2 +1,16 @@ +// Copyright (C) 2024 Fred Clausen +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +#![deny( + clippy::pedantic, + //clippy::cargo, + clippy::nursery, + clippy::style, + clippy::correctness, + clippy::all +)] + pub mod app; pub mod components; diff --git a/sh-frontend/src/main.rs b/sh-frontend/src/main.rs index 985e5e8..0367092 100644 --- a/sh-frontend/src/main.rs +++ b/sh-frontend/src/main.rs @@ -1,4 +1,18 @@ -use yew_app::app::App; +// Copyright (C) 2024 Fred Clausen +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +#![deny( + clippy::pedantic, + //clippy::cargo, + clippy::nursery, + clippy::style, + clippy::correctness, + clippy::all +)] + +use sh_frontend::app::App; // This is the entry point for the web app fn main() { diff --git a/sh-frontend/tailwind.css b/sh-frontend/tailwind.css index b5c61c9..c1668f2 100644 --- a/sh-frontend/tailwind.css +++ b/sh-frontend/tailwind.css @@ -1,3 +1,129 @@ @tailwind base; @tailwind components; @tailwind utilities; + +h2 { + vertical-align: center; + text-align: center; +} + +.top-nav { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + background-color: #00baf0; + background: linear-gradient(to left, #8963ba, #3c2f66); + /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */ + color: #fff; + /* height: 50px; */ + padding: 1em; +} + +.menu { + display: flex; + flex-direction: row; + list-style-type: none; + margin: 0; + padding: 0; +} + +.menu > li { + margin: 0 1rem; + overflow: hidden; +} + +.menu-button-container { + display: none; + height: 100%; + width: 30px; + cursor: pointer; + flex-direction: column; + justify-content: center; + align-items: center; +} + +#menu-toggle { + display: none; +} + +.menu-button, +.menu-button::before, +.menu-button::after { + display: block; + background-color: #fff; + position: absolute; + height: 4px; + width: 30px; + transition: transform 400ms cubic-bezier(0.23, 1, 0.32, 1); + border-radius: 2px; +} + +.menu-button::before { + content: ""; + margin-top: -8px; +} + +.menu-button::after { + content: ""; + margin-top: 8px; +} + +#menu-toggle:checked + .menu-button-container .menu-button::before { + margin-top: 0px; + transform: rotate(405deg); +} + +#menu-toggle:checked + .menu-button-container .menu-button { + background: rgba(255, 255, 255, 0); +} + +#menu-toggle:checked + .menu-button-container .menu-button::after { + margin-top: 0px; + transform: rotate(-405deg); +} + +@media (max-width: 700px) { + .menu-button-container { + display: flex; + } + .menu { + position: absolute; + top: 0; + margin-top: 50px; + left: 0; + flex-direction: column; + width: 100%; + justify-content: center; + align-items: center; + } + #menu-toggle ~ .menu li { + height: 0; + margin: 0; + padding: 0; + border: 0; + transition: height 400ms cubic-bezier(0.23, 1, 0.32, 1); + } + #menu-toggle:checked ~ .menu li { + border: 1px solid #333; + height: 2.5em; + padding: 0.5em; + transition: height 400ms cubic-bezier(0.23, 1, 0.32, 1); + } + .menu > li { + display: flex; + justify-content: center; + margin: 0; + padding: 0.5em 0; + width: 100%; + color: white; + background-color: #222; + } + .menu > li:not(:last-child) { + border-bottom: 1px solid #444; + } +} + +.map { + height: 100%; +} diff --git a/sh-frontend/tests/web.rs b/sh-frontend/tests/web.rs index fd0449c..e19ecac 100644 --- a/sh-frontend/tests/web.rs +++ b/sh-frontend/tests/web.rs @@ -4,7 +4,7 @@ use yew::platform::time::sleep; wasm_bindgen_test_configure!(run_in_browser); -use yew_app::app::App; +use sh_frontend::app::App; #[wasm_bindgen_test] async fn app_has_a_home_page() { diff --git a/src/bin/sh-tauri/Cargo.toml b/src/bin/sh-tauri/Cargo.toml index c21bf5a..727f84a 100644 --- a/src/bin/sh-tauri/Cargo.toml +++ b/src/bin/sh-tauri/Cargo.toml @@ -16,11 +16,11 @@ keywords.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [build-dependencies] -tauri-build = { version = "1.5.1", features = [] } +tauri-build = { version = "1.5.2", features = [] } [dependencies] serde_json = "1.0.116" -serde = { version = "1.0.198", features = ["derive"] } +serde = { version = "1.0.200", features = ["derive"] } sdrehub = { path = "../../libraries/sdrehub" } sh-config = { path = "../../libraries/sh-config" } tauri = { version = "1.6.2", features = ["api-all"] } diff --git a/src/bin/sh-tauri/SDR Enthusiasts Icon SVG.svg b/src/bin/sh-tauri/SDR Enthusiasts Icon SVG.svg new file mode 100644 index 0000000..dd6f4ca --- /dev/null +++ b/src/bin/sh-tauri/SDR Enthusiasts Icon SVG.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/bin/sh-tauri/icons/128x128.png b/src/bin/sh-tauri/icons/128x128.png index 6be5e50..8bc9489 100644 Binary files a/src/bin/sh-tauri/icons/128x128.png and b/src/bin/sh-tauri/icons/128x128.png differ diff --git a/src/bin/sh-tauri/icons/128x128@2x.png b/src/bin/sh-tauri/icons/128x128@2x.png index e81bece..e090f7a 100644 Binary files a/src/bin/sh-tauri/icons/128x128@2x.png and b/src/bin/sh-tauri/icons/128x128@2x.png differ diff --git a/src/bin/sh-tauri/icons/32x32.png b/src/bin/sh-tauri/icons/32x32.png index a437dd5..d34c4dc 100644 Binary files a/src/bin/sh-tauri/icons/32x32.png and b/src/bin/sh-tauri/icons/32x32.png differ diff --git a/src/bin/sh-tauri/icons/Square107x107Logo.png b/src/bin/sh-tauri/icons/Square107x107Logo.png index 0ca4f27..2e7a338 100644 Binary files a/src/bin/sh-tauri/icons/Square107x107Logo.png and b/src/bin/sh-tauri/icons/Square107x107Logo.png differ diff --git a/src/bin/sh-tauri/icons/Square142x142Logo.png b/src/bin/sh-tauri/icons/Square142x142Logo.png index b81f820..1613223 100644 Binary files a/src/bin/sh-tauri/icons/Square142x142Logo.png and b/src/bin/sh-tauri/icons/Square142x142Logo.png differ diff --git a/src/bin/sh-tauri/icons/Square150x150Logo.png b/src/bin/sh-tauri/icons/Square150x150Logo.png index 624c7bf..91d734d 100644 Binary files a/src/bin/sh-tauri/icons/Square150x150Logo.png and b/src/bin/sh-tauri/icons/Square150x150Logo.png differ diff --git a/src/bin/sh-tauri/icons/Square284x284Logo.png b/src/bin/sh-tauri/icons/Square284x284Logo.png index c021d2b..90787ae 100644 Binary files a/src/bin/sh-tauri/icons/Square284x284Logo.png and b/src/bin/sh-tauri/icons/Square284x284Logo.png differ diff --git a/src/bin/sh-tauri/icons/Square30x30Logo.png b/src/bin/sh-tauri/icons/Square30x30Logo.png index 6219700..27be99d 100644 Binary files a/src/bin/sh-tauri/icons/Square30x30Logo.png and b/src/bin/sh-tauri/icons/Square30x30Logo.png differ diff --git a/src/bin/sh-tauri/icons/Square310x310Logo.png b/src/bin/sh-tauri/icons/Square310x310Logo.png index f9bc048..6a7215d 100644 Binary files a/src/bin/sh-tauri/icons/Square310x310Logo.png and b/src/bin/sh-tauri/icons/Square310x310Logo.png differ diff --git a/src/bin/sh-tauri/icons/Square44x44Logo.png b/src/bin/sh-tauri/icons/Square44x44Logo.png index d5fbfb2..6f9b64d 100644 Binary files a/src/bin/sh-tauri/icons/Square44x44Logo.png and b/src/bin/sh-tauri/icons/Square44x44Logo.png differ diff --git a/src/bin/sh-tauri/icons/Square71x71Logo.png b/src/bin/sh-tauri/icons/Square71x71Logo.png index 63440d7..9d81992 100644 Binary files a/src/bin/sh-tauri/icons/Square71x71Logo.png and b/src/bin/sh-tauri/icons/Square71x71Logo.png differ diff --git a/src/bin/sh-tauri/icons/Square89x89Logo.png b/src/bin/sh-tauri/icons/Square89x89Logo.png index f3f705a..74786ab 100644 Binary files a/src/bin/sh-tauri/icons/Square89x89Logo.png and b/src/bin/sh-tauri/icons/Square89x89Logo.png differ diff --git a/src/bin/sh-tauri/icons/StoreLogo.png b/src/bin/sh-tauri/icons/StoreLogo.png index 4556388..b1f8ab7 100644 Binary files a/src/bin/sh-tauri/icons/StoreLogo.png and b/src/bin/sh-tauri/icons/StoreLogo.png differ diff --git a/src/bin/sh-tauri/icons/icon.icns b/src/bin/sh-tauri/icons/icon.icns index 8254645..1e9676f 100644 Binary files a/src/bin/sh-tauri/icons/icon.icns and b/src/bin/sh-tauri/icons/icon.icns differ diff --git a/src/bin/sh-tauri/icons/icon.ico b/src/bin/sh-tauri/icons/icon.ico index b3636e4..539bc27 100644 Binary files a/src/bin/sh-tauri/icons/icon.ico and b/src/bin/sh-tauri/icons/icon.ico differ diff --git a/src/bin/sh-tauri/icons/icon.png b/src/bin/sh-tauri/icons/icon.png index e1cd261..9f6564e 100644 Binary files a/src/bin/sh-tauri/icons/icon.png and b/src/bin/sh-tauri/icons/icon.png differ diff --git a/src/bin/sh-tauri/sdre.png b/src/bin/sh-tauri/sdre.png new file mode 100644 index 0000000..33c7184 Binary files /dev/null and b/src/bin/sh-tauri/sdre.png differ diff --git a/src/libraries/sh-config/Cargo.toml b/src/libraries/sh-config/Cargo.toml index 794fe90..7b6804b 100644 --- a/src/libraries/sh-config/Cargo.toml +++ b/src/libraries/sh-config/Cargo.toml @@ -19,7 +19,8 @@ keywords.workspace = true figment = { version = "0.10.18", features = ["toml", "env"] } sdre-rust-logging = "0.3.1" log = "0.4.21" -serde = { version = "1.0.198", features = ["derive"] } +serde = { version = "1.0.200", features = ["derive"] } serde-inline-default = "0.2.0" toml = "0.8.12" void = "1.0.2" +directories = "5.0.1" diff --git a/src/libraries/sh-config/src/lib.rs b/src/libraries/sh-config/src/lib.rs index b259422..c1dc093 100644 --- a/src/libraries/sh-config/src/lib.rs +++ b/src/libraries/sh-config/src/lib.rs @@ -12,6 +12,9 @@ clippy::all )] +use std::env; + +use directories::ProjectDirs; use figment::{ providers::{Env, Format, Toml}, Figment, @@ -31,7 +34,7 @@ pub mod adsb_source; pub mod map; pub mod sdrehub; pub mod source; -// TODO: Implement command line arguments. The config crate doesn't do this out of the box + // FIXME: env variables require a dot between the prefix and the variable name. This is not ideal. Should be able to use underscores #[serde_inline_default] @@ -53,15 +56,67 @@ impl ShConfig { Self::get_and_validate_config() } - fn get_file_path() -> String { - "./sh_config.toml".to_string() // FIXME: commented out the logic below to make clippy happy until we featurize the code - // match std::env::consts::OS { - // "linux" => "./sh_config.toml", - // "macos" => "./sh_config.toml", - // "windows" => "./sh_config.toml", - // _ => "./sh_config.toml", - // } - // .to_string() + fn get_application_data_path() -> String { + if env::var("SH_DATA_PATH").is_ok() { + let path = env::var("SH_DATA_PATH").unwrap(); + if std::path::Path::new(&path).exists() { + // canonicalize the path + match std::fs::canonicalize(&path) { + Ok(canonical_path) => { + if let Some(canonical_path_str) = canonical_path.to_str() { + return canonical_path_str.to_string(); + } + } + Err(e) => { + println!("Error getting config file path: {e}"); + println!("Exiting"); + std::process::exit(1); + } + } + } + + println!("Error getting config file path"); + println!("Exiting"); + std::process::exit(1); + } + + // Otherwise, use the OS default pathing + ProjectDirs::from("org", "sdre-e", "sdr-e-hub").map_or_else( + || { + println!("Error getting config file path"); + println!("Exiting"); + std::process::exit(1); + }, + |proj_dirs| { + proj_dirs.config_dir().to_str().map_or_else( + || { + println!("Error getting config file path"); + println!("Exiting"); + std::process::exit(1); + }, + |path| { + // make the directory if it doesn't exist + if std::path::Path::new(&path).exists() { + path.to_string() + } else { + match std::fs::create_dir_all(path) { + Ok(()) => path.to_string(), + Err(e) => { + println!("Error creating config directory: {e}"); + println!("Exiting"); + std::process::exit(1); + } + } + } + }, + ) + }, + ) + } + + fn get_config_file_path() -> String { + let path = Self::get_application_data_path(); + format!("{path}/sh_config.toml") } fn get_config(file_path: &str) -> Self { @@ -80,7 +135,7 @@ impl ShConfig { } fn get_and_validate_config() -> Self { - let file_path = Self::get_file_path(); + let file_path = Self::get_config_file_path(); Self::get_config(&file_path) } @@ -97,8 +152,9 @@ impl ShConfig { } pub fn write_config(&self) { - let file_path = Self::get_file_path(); + let file_path = Self::get_config_file_path(); let config = self.get_config_as_toml_string(); + println!("Writing config file to: {file_path}"); match std::fs::write(file_path, config) { Ok(()) => (), diff --git a/src/libraries/sh-config/src/map.rs b/src/libraries/sh-config/src/map.rs index 75b4523..b82b80b 100644 --- a/src/libraries/sh-config/src/map.rs +++ b/src/libraries/sh-config/src/map.rs @@ -8,11 +8,11 @@ use serde::{Deserialize, Serialize}; /// `MapConfig` is a struct for storing global map values #[derive(Debug, Serialize, Deserialize, Default)] pub struct ShMapConfig { - /// center_latitude is the latitude of the center of the map + /// `center_latitude` is the latitude of the center of the map /// This value will be used to center the map on the web interface /// Default value is 0.0 center_latitude: f64, - /// center_longitude is the longitude of the center of the map + /// `center_longitude` is the longitude of the center of the map /// This value will be used to center the map on the web interface /// Default value is 0.0 center_longitude: f64, diff --git a/src/libraries/sh-config/src/sdrehub.rs b/src/libraries/sh-config/src/sdrehub.rs index 8ae5567..55f298a 100644 --- a/src/libraries/sh-config/src/sdrehub.rs +++ b/src/libraries/sh-config/src/sdrehub.rs @@ -15,20 +15,21 @@ pub struct SDREHub { pub database_url: String, #[serde_inline_default("info".to_string())] pub log_level: String, - #[serde_inline_default("./data".to_string())] + #[serde_inline_default(ShConfig::get_application_data_path())] pub data_path: String, - #[serde_inline_default(ShConfig::get_file_path())] + #[serde_inline_default(ShConfig::get_config_file_path())] #[serde(skip_serializing)] pub config_file: String, } impl Default for SDREHub { fn default() -> Self { + let path = ShConfig::get_application_data_path(); Self { - database_url: "sqlite://sdre-hub.db".to_string(), - data_path: "./data".to_string(), + database_url: format!("sqlite://{path}sdre-hub.db"), + data_path: path, log_level: "info".to_string(), - config_file: ShConfig::get_file_path(), + config_file: ShConfig::get_config_file_path(), } } }