mirror of
https://github.com/DJ2LS/FreeDATA
synced 2024-05-14 10:04:33 +02:00
Merge pull request #473 from DJ2LS/ls-gui-single-pager
GUI sidebar & GUI single pager & VueJS + Vite + Typescript
This commit is contained in:
commit
4f2671c359
9
.github/dependabot.yml
vendored
9
.github/dependabot.yml
vendored
|
@ -13,7 +13,14 @@ updates:
|
|||
directory: "/gui"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
||||
|
||||
# Maintain dependencies for npm
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/gui"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
target-branch: "ls-gui-single-pager"
|
||||
|
||||
# Maintain dependencies for pip
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
|
|
129
.github/workflows/build_multiplatform.yml
vendored
129
.github/workflows/build_multiplatform.yml
vendored
|
@ -8,7 +8,7 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-20.04, ubuntu-22.04, macos-11, macos-12]
|
||||
os: [ubuntu-20.04, ubuntu-22.04, macos-latest, macos-12]
|
||||
platform: [{name: "native"}, {name: "Windows", file: "dll"}]
|
||||
architecture: [i686-w64-mingw32, x86_64-w64-mingw32]
|
||||
include:
|
||||
|
@ -27,9 +27,9 @@ jobs:
|
|||
generator: Unix Makefiles
|
||||
shell: bash
|
||||
|
||||
- os: macos-11
|
||||
- os: macos-latest
|
||||
libcodec2_name: libcodec2.1.2.dylib
|
||||
libcodec2_os_name: libcodec2_macos-11
|
||||
libcodec2_os_name: libcodec2_macos-latest
|
||||
libcodec2_filetype: dylib
|
||||
generator: Unix Makefiles
|
||||
shell: bash
|
||||
|
@ -194,27 +194,27 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-20.04, macos-11, windows-latest]
|
||||
os: [ubuntu-20.04, macos-latest, windows-latest]
|
||||
include:
|
||||
- os: ubuntu-20.04
|
||||
zip_name: ubuntu_tnc
|
||||
zip_name: ubuntu_modem
|
||||
generator: Unix Makefiles
|
||||
daemon_binary_name: freedata-daemon
|
||||
tnc_binary_name: freedata-tnc
|
||||
modem_binary_name: freedata-modem
|
||||
electron_parameters: "-p always"
|
||||
|
||||
- os: macos-11
|
||||
zip_name: macos_tnc
|
||||
- os: macos-latest
|
||||
zip_name: macos_modem
|
||||
generator: Unix Makefiles
|
||||
daemon_binary_name: freedata-daemon
|
||||
tnc_binary_name: freedata-tnc
|
||||
modem_binary_name: freedata-modem
|
||||
electron_parameters: "-p always"
|
||||
|
||||
- os: windows-latest
|
||||
zip_name: windows_tnc
|
||||
zip_name: windows_modem
|
||||
generator: Visual Studio 16 2019
|
||||
daemon_binary_name: freedata-daemon.exe
|
||||
tnc_binary_name: freedata-tnc.exe
|
||||
modem_binary_name: freedata-modem.exe
|
||||
electron_parameters: "-p always --x64 --ia32"
|
||||
steps:
|
||||
- name: Checkout code for ${{ matrix.platform.name }}
|
||||
|
@ -232,30 +232,30 @@ jobs:
|
|||
with:
|
||||
node-version: 18.17
|
||||
|
||||
- name: Create tnc/dist
|
||||
working-directory: tnc
|
||||
- name: Create modem/dist
|
||||
working-directory: modem
|
||||
run: |
|
||||
mkdir -p dist
|
||||
|
||||
- name: Create tnc/dist/tnc
|
||||
working-directory: tnc
|
||||
- name: Create modem/dist/modem
|
||||
working-directory: modem
|
||||
run: |
|
||||
mkdir -p dist/tnc
|
||||
mkdir -p dist/modem
|
||||
|
||||
##- name: Download libcodec2 artifact TNC DIST
|
||||
##- name: Download libcodec2 artifact Modem DIST
|
||||
## uses: actions/download-artifact@v3
|
||||
## with:
|
||||
## path: tnc/dist/codec2
|
||||
## path: modem/dist/codec2
|
||||
|
||||
- name: create tnc/lib/codec2
|
||||
working-directory: tnc/lib/
|
||||
- name: create modem/lib/codec2
|
||||
working-directory: modem/lib/
|
||||
run: |
|
||||
mkdir codec2
|
||||
|
||||
- name: Download libcodec2 artifact TNC LIB
|
||||
- name: Download libcodec2 artifact Modem LIB
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
path: tnc/lib/codec2
|
||||
path: modem/lib/codec2
|
||||
|
||||
|
||||
- name: Install Linux dependencies
|
||||
|
@ -285,50 +285,50 @@ jobs:
|
|||
|
||||
- name: Build binaries macOS
|
||||
if: ${{startsWith(matrix.os, 'macos')}}
|
||||
working-directory: tnc
|
||||
working-directory: modem
|
||||
run: |
|
||||
# now build tnc binaries
|
||||
# now build modem binaries
|
||||
pyinstaller -y freedata.spec
|
||||
# and to some final cleanup
|
||||
# cp -r -f dist/tnc/* dist/
|
||||
# rm -r dist/tnc
|
||||
# cp -r -f dist/modem/* dist/
|
||||
# rm -r dist/modem
|
||||
|
||||
- name: Build binaries Linux and Windows
|
||||
if: ${{!startsWith(matrix.os, 'macos')}}
|
||||
working-directory: tnc
|
||||
working-directory: modem
|
||||
run: |
|
||||
# pyinstaller freedata.spec
|
||||
# python3 -m nuitka --enable-plugin=numpy --assume-yes-for-downloads --onefile daemon.py -o ${{ matrix.daemon_binary_name }}
|
||||
# python3 -m nuitka --enable-plugin=numpy --assume-yes-for-downloads --onefile main.py -o ${{ matrix.tnc_binary_name }}
|
||||
# python3 -m nuitka --enable-plugin=numpy --assume-yes-for-downloads --onefile main.py -o ${{ matrix.modem_binary_name }}
|
||||
python3 -m nuitka --enable-plugin=numpy --assume-yes-for-downloads --standalone daemon.py
|
||||
python3 -m nuitka --enable-plugin=numpy --assume-yes-for-downloads --standalone main.py
|
||||
|
||||
- name: Copy binaries - Linux
|
||||
if: ${{startsWith(matrix.os, 'ubuntu')}}
|
||||
working-directory: tnc
|
||||
working-directory: modem
|
||||
run: |
|
||||
cp -r -f daemon.dist/* dist/tnc
|
||||
cp -r -f main.dist/* dist/tnc
|
||||
cp -r -f daemon.dist/* dist/modem
|
||||
cp -r -f main.dist/* dist/modem
|
||||
|
||||
- name: Copy binaries - Windows
|
||||
if: ${{startsWith(matrix.os, 'windows')}}
|
||||
working-directory: tnc
|
||||
working-directory: modem
|
||||
# These are powershell aliases, not UNIX commands.
|
||||
run: |
|
||||
cp -r -Force daemon.dist/* dist/tnc
|
||||
cp -r -Force main.dist/* dist/tnc
|
||||
cp -r -Force daemon.dist/* dist/modem
|
||||
cp -r -Force main.dist/* dist/modem
|
||||
|
||||
- name: Rename tnc binaries
|
||||
- name: Rename modem binaries
|
||||
# we don't need renaming for pyinstaller builds as output name is defined
|
||||
if: ${{!startsWith(matrix.os, 'macos')}}
|
||||
working-directory: tnc
|
||||
working-directory: modem
|
||||
run: |
|
||||
mv dist/tnc/daemon* dist/tnc/${{ matrix.daemon_binary_name }}
|
||||
mv dist/tnc/main* dist/tnc/${{ matrix.tnc_binary_name }}
|
||||
mv dist/modem/daemon* dist/modem/${{ matrix.daemon_binary_name }}
|
||||
mv dist/modem/main* dist/modem/${{ matrix.modem_binary_name }}
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
path: tnc/dist/tnc
|
||||
path: modem/dist/modem
|
||||
|
||||
|
||||
- name: LIST ALL FILES
|
||||
|
@ -336,20 +336,20 @@ jobs:
|
|||
|
||||
- name: Download Portaudio binaries Linux macOS
|
||||
if: ${{!startsWith(matrix.os, 'windows')}}
|
||||
working-directory: tnc
|
||||
working-directory: modem
|
||||
run: |
|
||||
if ! test -d "dist/tnc/_sounddevice_data"; then
|
||||
git clone https://github.com/spatialaudio/portaudio-binaries dist/tnc/_sounddevice_data/portaudio-binaries
|
||||
if ! test -d "dist/modem/_sounddevice_data"; then
|
||||
git clone https://github.com/spatialaudio/portaudio-binaries dist/modem/_sounddevice_data/portaudio-binaries
|
||||
fi
|
||||
|
||||
- name: Download Portaudio binaries Windows
|
||||
if: ${{startsWith(matrix.os, 'windows')}}
|
||||
working-directory: tnc
|
||||
working-directory: modem
|
||||
run: |
|
||||
if(Test-Path -Path "dist/tnc/_sounddevice_data"){
|
||||
if(Test-Path -Path "dist/modem/_sounddevice_data"){
|
||||
echo "sounddevice folder already exists"
|
||||
} else {
|
||||
git clone https://github.com/spatialaudio/portaudio-binaries dist/tnc/_sounddevice_data/portaudio-binaries
|
||||
git clone https://github.com/spatialaudio/portaudio-binaries dist/modem/_sounddevice_data/portaudio-binaries
|
||||
}
|
||||
|
||||
- name: LIST ALL FILES
|
||||
|
@ -362,53 +362,50 @@ jobs:
|
|||
# find . -type d -name .git -exec rm -r {} \;
|
||||
find . -type d -o -name ".git" -delete
|
||||
|
||||
- name: Build/release Electron app
|
||||
uses: coparse-inc/action-electron-builder@v1.0.0
|
||||
env:
|
||||
- name: Electron Builder
|
||||
env: # Setting environment variables for the entire job
|
||||
GH_TOKEN: ${{ secrets.github_token }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||
with:
|
||||
package_root: "./gui/"
|
||||
github_token: ${{ secrets.github_token }}
|
||||
# If the commit is tagged with a version (e.g. "v1.0.0"),
|
||||
# release the app after building
|
||||
release: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
args: ${{ matrix.electron_parameters }}
|
||||
max_attempts: 3
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
working-directory: gui
|
||||
run: |
|
||||
npm i
|
||||
npm run build
|
||||
|
||||
- name: Compress TNC
|
||||
- name: Compress Modem
|
||||
uses: thedoctor0/zip-release@master
|
||||
with:
|
||||
type: 'zip'
|
||||
filename: '${{ matrix.zip_name }}.zip'
|
||||
# directory: ./tnc/dist/tnc
|
||||
directory: ./tnc/dist/tnc
|
||||
# directory: ./modem/dist/modem
|
||||
directory: ./modem/dist/modem
|
||||
path: .
|
||||
# exclusions: '*.git* /*node_modules/* .editorconfig'
|
||||
|
||||
- name: Release TNC
|
||||
- name: Release Modem
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
with:
|
||||
files: ./tnc/dist/tnc/${{ matrix.zip_name }}.zip
|
||||
#files: ./tnc/dist/${{ matrix.zip_name }}.zip
|
||||
files: ./modem/dist/modem/${{ matrix.zip_name }}.zip
|
||||
#files: ./modem/dist/${{ matrix.zip_name }}.zip
|
||||
|
||||
- name: LIST ALL FILES
|
||||
run: ls -R
|
||||
|
||||
#- name: Upload TNC artifacts
|
||||
#- name: Upload Modem artifacts
|
||||
# uses: actions/upload-artifact@v3
|
||||
# if: ${{!startsWith(github.ref, 'refs/tags/v')}}
|
||||
# with:
|
||||
# name: ${{ matrix.zip_name }}.zip
|
||||
# # path: ./tnc/dist/tnc/${{ matrix.zip_name }}.zip
|
||||
# path: ./tnc/dist/tnc/${{ matrix.zip_name }}.zip#
|
||||
# # path: ./modem/dist/modem/${{ matrix.zip_name }}.zip
|
||||
# path: ./modem/dist/modem/${{ matrix.zip_name }}.zip#
|
||||
|
||||
#- name: Upload App bundle artifacts
|
||||
# uses: actions/upload-artifact@v3
|
||||
# if: ${{!startsWith(github.ref, 'refs/tags/v')}}
|
||||
# with:
|
||||
# name: app_bundle_${{ matrix.os }}.zip
|
||||
# # path: ./tnc/dist/tnc/${{ matrix.zip_name }}.zip
|
||||
# # path: ./modem/dist/modem/${{ matrix.zip_name }}.zip
|
||||
# path: ./gui/dist/*
|
||||
|
|
12
.gitignore
vendored
12
.gitignore
vendored
|
@ -1,5 +1,5 @@
|
|||
# possible installation of codec2 within tnc
|
||||
tnc/codec2
|
||||
# possible installation of codec2 within modem
|
||||
modem/codec2
|
||||
|
||||
# temporary test artifacts
|
||||
**/build
|
||||
|
@ -22,3 +22,11 @@ package-lock.json
|
|||
*.raw
|
||||
coverage.sh
|
||||
coverage.xml
|
||||
|
||||
#ignore node_modules
|
||||
/gui/node_modules/
|
||||
|
||||
#Ignore gui build items
|
||||
/gui/dist
|
||||
/gui/release
|
||||
/gui/dist-electron
|
|
@ -24,43 +24,43 @@ set(TESTFRAMES 3)
|
|||
|
||||
add_test(NAME audio_buffer
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
export PYTHONPATH=../tnc;
|
||||
export PYTHONPATH=../modem;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
python3 test_audiobuffer.py")
|
||||
set_tests_properties(audio_buffer PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
|
||||
|
||||
add_test(NAME resampler
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
export PYTHONPATH=../tnc;
|
||||
export PYTHONPATH=../modem;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
python3 test_resample_48_8.py")
|
||||
set_tests_properties(resampler PROPERTIES PASS_REGULAR_EXPRESSION "PASS")
|
||||
|
||||
add_test(NAME tnc_state_machine
|
||||
add_test(NAME modem_state_machine
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
export PYTHONPATH=../tnc;
|
||||
export PYTHONPATH=../modem;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
python3 test_tnc_states.py")
|
||||
set_tests_properties(tnc_state_machine PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
|
||||
python3 test_modem_states.py")
|
||||
set_tests_properties(modem_state_machine PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
|
||||
|
||||
add_test(NAME tnc_irs_iss
|
||||
add_test(NAME modem_irs_iss
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
export PYTHONPATH=../tnc;
|
||||
export PYTHONPATH=../modem;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
python3 test_tnc.py")
|
||||
set_tests_properties(tnc_irs_iss PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
|
||||
python3 test_modem.py")
|
||||
set_tests_properties(modem_irs_iss PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
|
||||
|
||||
# disabled this test as its actually broken since we introduced session IDs
|
||||
#add_test(NAME chat_text
|
||||
# COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
# export PYTHONPATH=../tnc;
|
||||
# export PYTHONPATH=../modem;
|
||||
# cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
# python3 test_chat_text.py")
|
||||
# set_tests_properties(chat_text PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
|
||||
|
||||
add_test(NAME datac13_frames
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
export PYTHONPATH=../tnc;
|
||||
export PYTHONPATH=../modem;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
python3 test_datac13.py")
|
||||
set_tests_properties(datac13_frames PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
|
||||
|
@ -68,21 +68,21 @@ add_test(NAME datac13_frames
|
|||
# disabled this test as its actually broken since we introduced dataclasses
|
||||
#add_test(NAME datac13_frames_negative
|
||||
# COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
# export PYTHONPATH=../tnc;
|
||||
# export PYTHONPATH=../modem;
|
||||
# cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
# python3 test_datac13_negative.py")
|
||||
# set_tests_properties(datac13_frames_negative PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
|
||||
|
||||
add_test(NAME helper_routines
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
export PYTHONPATH=../tnc;
|
||||
export PYTHONPATH=../modem;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
python3 test_helpers.py")
|
||||
set_tests_properties(helper_routines PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
|
||||
|
||||
add_test(NAME py_highsnr_stdio_P_P_multi
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
export PYTHONPATH=../tnc;
|
||||
export PYTHONPATH=../modem;
|
||||
export BURSTS=${BURSTS};
|
||||
export FRAMESPERBURST=${FRAMESPERBURST};
|
||||
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||
|
@ -92,7 +92,7 @@ add_test(NAME py_highsnr_stdio_P_P_multi
|
|||
|
||||
add_test(NAME py_highsnr_stdio_P_P_datacx
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
export PYTHONPATH=../tnc;
|
||||
export PYTHONPATH=../modem;
|
||||
export BURSTS=${BURSTS};
|
||||
export FRAMESPERBURST=${FRAMESPERBURST};
|
||||
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||
|
@ -102,7 +102,7 @@ add_test(NAME py_highsnr_stdio_P_P_datacx
|
|||
|
||||
add_test(NAME py_highsnr_stdio_P_C_datacx
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
export PYTHONPATH=../tnc;
|
||||
export PYTHONPATH=../modem;
|
||||
export BURSTS=${BURSTS};
|
||||
export FRAMESPERBURST=${FRAMESPERBURST};
|
||||
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||
|
@ -112,7 +112,7 @@ add_test(NAME py_highsnr_stdio_P_C_datacx
|
|||
|
||||
add_test(NAME py_highsnr_stdio_C_P_datacx
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
export PYTHONPATH=../tnc;
|
||||
export PYTHONPATH=../modem;
|
||||
export BURSTS=${BURSTS};
|
||||
export FRAMESPERBURST=${FRAMESPERBURST};
|
||||
export TESTFRAMES=${TESTFRAMES};
|
||||
|
|
23
gui/.eslintrc.json
Normal file
23
gui/.eslintrc.json
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/vue3-essential",
|
||||
"plugin:prettier/recommended",
|
||||
"eslint:recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module",
|
||||
"project": "./tsconfig.json"
|
||||
},
|
||||
"plugins": [
|
||||
"vue"
|
||||
],
|
||||
"ignorePatterns": ["**/src/assets/*", "**/src/js/deprecated*", "**/node_modules"],
|
||||
"rules": {
|
||||
}
|
||||
}
|
104
gui/.gitignore
vendored
104
gui/.gitignore
vendored
|
@ -1,104 +0,0 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and *not* Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
18
gui/README.md
Normal file
18
gui/README.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
# Vue 3 + TypeScript + Vite
|
||||
|
||||
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
|
||||
|
||||
## Type Support For `.vue` Imports in TS
|
||||
|
||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
|
||||
|
||||
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
|
||||
|
||||
1. Disable the built-in TypeScript Extension
|
||||
1. Run `Extensions: Show Built-in Extensions` from VSCode's command palette
|
||||
2. Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
|
||||
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
|
23
gui/build/notarize_macos.js
Normal file
23
gui/build/notarize_macos.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
const { notarize } = require('@electron/notarize');
|
||||
|
||||
async function notarizing(context) {
|
||||
const { electronPlatformName, appOutDir } = context;
|
||||
console.log("platform:" + electronPlatformName)
|
||||
if (electronPlatformName !== 'darwin') {
|
||||
console.log("not a APPLE system")
|
||||
return;
|
||||
}
|
||||
|
||||
const appName = context.packager.appInfo.productFilename;
|
||||
|
||||
return await notarize({
|
||||
tool: 'notarytool',
|
||||
appBundleId: 'app.freedata',
|
||||
appPath: `${appOutDir}/${appName}.app`,
|
||||
appleId: process.env.APPLE_ID,
|
||||
appleIdPassword: process.env.APPLE_ID_PASSWORD,
|
||||
teamId: process.env.APPLE_TEAM_ID
|
||||
});
|
||||
}
|
||||
|
||||
exports.default = notarizing;
|
351
gui/daemon.js
351
gui/daemon.js
|
@ -1,351 +0,0 @@
|
|||
var net = require("net");
|
||||
const path = require("path");
|
||||
const { ipcRenderer } = require("electron");
|
||||
const log = require("electron-log");
|
||||
const daemonLog = log.scope("daemon");
|
||||
|
||||
// https://stackoverflow.com/a/26227660
|
||||
var appDataFolder =
|
||||
process.env.APPDATA ||
|
||||
(process.platform == "darwin"
|
||||
? process.env.HOME + "/Library/Application Support"
|
||||
: process.env.HOME + "/.config");
|
||||
var configFolder = path.join(appDataFolder, "FreeDATA");
|
||||
var configPath = path.join(configFolder, "config.json");
|
||||
const config = require(configPath);
|
||||
|
||||
var daemon = new net.Socket();
|
||||
var socketchunk = ""; // Current message, per connection.
|
||||
|
||||
// global to keep track of daemon connection error emissions
|
||||
var daemonShowConnectStateError = 1;
|
||||
|
||||
// global for storing ip information
|
||||
var daemon_port = config.daemon_port;
|
||||
var daemon_host = config.daemon_host;
|
||||
|
||||
setTimeout(connectDAEMON, 500);
|
||||
|
||||
function connectDAEMON() {
|
||||
if (daemonShowConnectStateError == 1) {
|
||||
daemonLog.info("connecting to daemon");
|
||||
}
|
||||
|
||||
//clear message buffer after reconnecting or initial connection
|
||||
socketchunk = "";
|
||||
|
||||
if (config.tnclocation == "localhost") {
|
||||
daemon.connect(3001, "127.0.0.1");
|
||||
} else {
|
||||
daemon.connect(daemon_port, daemon_host);
|
||||
}
|
||||
|
||||
//client.setTimeout(5000);
|
||||
}
|
||||
|
||||
daemon.on("connect", function (err) {
|
||||
daemonLog.info("daemon connection established");
|
||||
let Data = {
|
||||
daemon_connection: daemon.readyState,
|
||||
};
|
||||
ipcRenderer.send("request-update-daemon-connection", Data);
|
||||
|
||||
daemonShowConnectStateError = 1;
|
||||
});
|
||||
|
||||
daemon.on("error", function (err) {
|
||||
if (daemonShowConnectStateError == 1) {
|
||||
daemonLog.error("daemon connection error");
|
||||
daemonLog.info("Make sure the daemon is started.");
|
||||
daemonLog.info('Run "python daemon.py" in the tnc directory.');
|
||||
|
||||
daemonShowConnectStateError = 0;
|
||||
}
|
||||
setTimeout(connectDAEMON, 500);
|
||||
daemon.destroy();
|
||||
let Data = {
|
||||
daemon_connection: daemon.readyState,
|
||||
};
|
||||
ipcRenderer.send("request-update-daemon-connection", Data);
|
||||
});
|
||||
|
||||
/*
|
||||
client.on('close', function(data) {
|
||||
console.log(' TNC connection closed');
|
||||
setTimeout(connectTNC, 2000)
|
||||
let Data = {
|
||||
daemon_connection: daemon.readyState,
|
||||
};
|
||||
ipcRenderer.send('request-update-daemon-connection', Data);
|
||||
});
|
||||
*/
|
||||
|
||||
daemon.on("end", function (data) {
|
||||
daemonLog.warn("daemon connection ended");
|
||||
daemon.destroy();
|
||||
setTimeout(connectDAEMON, 500);
|
||||
let Data = {
|
||||
daemon_connection: daemon.readyState,
|
||||
};
|
||||
ipcRenderer.send("request-update-daemon-connection", Data);
|
||||
});
|
||||
|
||||
//exports.writeCommand = function(command){
|
||||
writeDaemonCommand = function (command) {
|
||||
// we use the writingCommand function to update our TCPIP state because we are calling this function a lot
|
||||
// if socket opened, we are able to run commands
|
||||
if (daemon.readyState == "open") {
|
||||
//uiMain.setDAEMONconnection('open')
|
||||
daemon.write(command + "\n");
|
||||
}
|
||||
|
||||
if (daemon.readyState == "closed") {
|
||||
//uiMain.setDAEMONconnection('closed')
|
||||
}
|
||||
|
||||
if (daemon.readyState == "opening") {
|
||||
//uiMain.setDAEMONconnection('opening')
|
||||
}
|
||||
|
||||
let Data = {
|
||||
daemon_connection: daemon.readyState,
|
||||
};
|
||||
ipcRenderer.send("request-update-daemon-connection", Data);
|
||||
};
|
||||
|
||||
// "https://stackoverflow.com/questions/9070700/nodejs-net-createserver-large-amount-of-data-coming-in"
|
||||
|
||||
daemon.on("data", function (socketdata) {
|
||||
/*
|
||||
inspired by:
|
||||
stackoverflow.com questions 9070700 nodejs-net-createserver-large-amount-of-data-coming-in
|
||||
*/
|
||||
|
||||
socketdata = socketdata.toString("utf8"); // convert data to string
|
||||
socketchunk += socketdata; // append data to buffer so we can stick long data together
|
||||
|
||||
// check if we received begin and end of json data
|
||||
if (socketchunk.startsWith('{"') && socketchunk.endsWith('"}\n')) {
|
||||
var data = "";
|
||||
|
||||
// split data into chunks if we received multiple commands
|
||||
socketchunk = socketchunk.split("\n");
|
||||
data = JSON.parse(socketchunk[0]);
|
||||
|
||||
// search for empty entries in socketchunk and remove them
|
||||
for (i = 0; i < socketchunk.length; i++) {
|
||||
if (socketchunk[i] === "") {
|
||||
socketchunk.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
//iterate through socketchunks array to execute multiple commands in row
|
||||
for (i = 0; i < socketchunk.length; i++) {
|
||||
//check if data is not empty
|
||||
if (socketchunk[i].length > 0) {
|
||||
//try to parse JSON
|
||||
try {
|
||||
data = JSON.parse(socketchunk[i]);
|
||||
} catch (e) {
|
||||
console.log(e); // "SyntaxError
|
||||
daemonLog.error(e);
|
||||
daemonLog.debug(socketchunk[i]);
|
||||
socketchunk = "";
|
||||
}
|
||||
}
|
||||
|
||||
if (data["command"] == "daemon_state") {
|
||||
let Data = {
|
||||
input_devices: data["input_devices"],
|
||||
output_devices: data["output_devices"],
|
||||
python_version: data["python_version"],
|
||||
hamlib_version: data["hamlib_version"],
|
||||
serial_devices: data["serial_devices"],
|
||||
tnc_running_state: data["daemon_state"][0]["status"],
|
||||
ram_usage: data["ram"],
|
||||
cpu_usage: data["cpu"],
|
||||
version: data["version"],
|
||||
};
|
||||
ipcRenderer.send("request-update-daemon-state", Data);
|
||||
}
|
||||
|
||||
if (data["command"] == "test_hamlib") {
|
||||
let Data = {
|
||||
hamlib_result: data["result"],
|
||||
};
|
||||
ipcRenderer.send("request-update-hamlib-test", Data);
|
||||
}
|
||||
}
|
||||
|
||||
//finally delete message buffer
|
||||
socketchunk = "";
|
||||
}
|
||||
});
|
||||
|
||||
function hexToBytes(hex) {
|
||||
for (var bytes = [], c = 0; c < hex.length; c += 2)
|
||||
bytes.push(parseInt(hex.substr(c, 2), 16));
|
||||
return bytes;
|
||||
}
|
||||
|
||||
exports.getDaemonState = function () {
|
||||
//function getDaemonState(){
|
||||
command = '{"type" : "get", "command" : "daemon_state"}';
|
||||
writeDaemonCommand(command);
|
||||
};
|
||||
|
||||
// START TNC
|
||||
// ` `== multi line string
|
||||
|
||||
exports.startTNC = function (
|
||||
mycall,
|
||||
mygrid,
|
||||
rx_audio,
|
||||
tx_audio,
|
||||
radiocontrol,
|
||||
devicename,
|
||||
deviceport,
|
||||
pttprotocol,
|
||||
pttport,
|
||||
serialspeed,
|
||||
data_bits,
|
||||
stop_bits,
|
||||
handshake,
|
||||
rigctld_ip,
|
||||
rigctld_port,
|
||||
enable_fft,
|
||||
enable_scatter,
|
||||
low_bandwidth_mode,
|
||||
tuning_range_fmin,
|
||||
tuning_range_fmax,
|
||||
enable_fsk,
|
||||
tx_audio_level,
|
||||
respond_to_cq,
|
||||
rx_buffer_size,
|
||||
enable_explorer,
|
||||
explorer_stats,
|
||||
auto_tune,
|
||||
tx_delay,
|
||||
tci_ip,
|
||||
tci_port,
|
||||
enable_mesh,
|
||||
) {
|
||||
var json_command = JSON.stringify({
|
||||
type: "set",
|
||||
command: "start_tnc",
|
||||
parameter: [
|
||||
{
|
||||
mycall: mycall,
|
||||
mygrid: mygrid,
|
||||
rx_audio: rx_audio,
|
||||
tx_audio: tx_audio,
|
||||
radiocontrol: radiocontrol,
|
||||
devicename: devicename,
|
||||
deviceport: deviceport,
|
||||
pttprotocol: pttprotocol,
|
||||
pttport: pttport,
|
||||
serialspeed: serialspeed,
|
||||
data_bits: data_bits,
|
||||
stop_bits: stop_bits,
|
||||
handshake: handshake,
|
||||
rigctld_port: rigctld_port,
|
||||
rigctld_ip: rigctld_ip,
|
||||
enable_scatter: enable_scatter,
|
||||
enable_fft: enable_fft,
|
||||
enable_fsk: enable_fsk,
|
||||
low_bandwidth_mode: low_bandwidth_mode,
|
||||
tuning_range_fmin: tuning_range_fmin,
|
||||
tuning_range_fmax: tuning_range_fmax,
|
||||
tx_audio_level: tx_audio_level,
|
||||
respond_to_cq: respond_to_cq,
|
||||
rx_buffer_size: rx_buffer_size,
|
||||
enable_explorer: enable_explorer,
|
||||
enable_stats: explorer_stats,
|
||||
enable_auto_tune: auto_tune,
|
||||
tx_delay: tx_delay,
|
||||
tci_ip: tci_ip,
|
||||
tci_port: tci_port,
|
||||
enable_mesh: enable_mesh,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
daemonLog.debug(json_command);
|
||||
writeDaemonCommand(json_command);
|
||||
};
|
||||
|
||||
// STOP TNC
|
||||
exports.stopTNC = function () {
|
||||
command = '{"type" : "set", "command": "stop_tnc" , "parameter": "---" }';
|
||||
writeDaemonCommand(command);
|
||||
};
|
||||
|
||||
// TEST HAMLIB
|
||||
exports.testHamlib = function (
|
||||
radiocontrol,
|
||||
devicename,
|
||||
deviceport,
|
||||
serialspeed,
|
||||
pttprotocol,
|
||||
pttport,
|
||||
data_bits,
|
||||
stop_bits,
|
||||
handshake,
|
||||
rigctld_ip,
|
||||
rigctld_port,
|
||||
) {
|
||||
var json_command = JSON.stringify({
|
||||
type: "get",
|
||||
command: "test_hamlib",
|
||||
parameter: [
|
||||
{
|
||||
radiocontrol: radiocontrol,
|
||||
devicename: devicename,
|
||||
deviceport: deviceport,
|
||||
pttprotocol: pttprotocol,
|
||||
pttport: pttport,
|
||||
serialspeed: serialspeed,
|
||||
data_bits: data_bits,
|
||||
stop_bits: stop_bits,
|
||||
handshake: handshake,
|
||||
rigctld_port: rigctld_port,
|
||||
rigctld_ip: rigctld_ip,
|
||||
},
|
||||
],
|
||||
});
|
||||
daemonLog.debug(json_command);
|
||||
writeDaemonCommand(json_command);
|
||||
};
|
||||
|
||||
//Save myCall
|
||||
exports.saveMyCall = function (callsign) {
|
||||
command =
|
||||
'{"type" : "set", "command": "mycallsign" , "parameter": "' +
|
||||
callsign +
|
||||
'"}';
|
||||
writeDaemonCommand(command);
|
||||
};
|
||||
|
||||
// Save myGrid
|
||||
exports.saveMyGrid = function (grid) {
|
||||
command =
|
||||
'{"type" : "set", "command": "mygrid" , "parameter": "' + grid + '"}';
|
||||
writeDaemonCommand(command);
|
||||
};
|
||||
|
||||
ipcRenderer.on("action-update-daemon-ip", (event, arg) => {
|
||||
daemon.destroy();
|
||||
let Data = {
|
||||
busy_state: "-",
|
||||
arq_state: "-",
|
||||
//channel_state: "-",
|
||||
frequency: "-",
|
||||
mode: "-",
|
||||
bandwidth: "-",
|
||||
dbfs_level: 0,
|
||||
};
|
||||
ipcRenderer.send("request-update-tnc-state", Data);
|
||||
daemon_port = arg.port;
|
||||
daemon_host = arg.adress;
|
||||
connectDAEMON();
|
||||
});
|
67
gui/electron-builder.json5
Normal file
67
gui/electron-builder.json5
Normal file
|
@ -0,0 +1,67 @@
|
|||
/**
|
||||
* @see https://www.electron.build/configuration/configuration
|
||||
*/
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/electron-userland/electron-builder/master/packages/app-builder-lib/scheme.json",
|
||||
"appId": "app.freedata",
|
||||
"asar": true,
|
||||
"afterSign": "build/notarize_macos.js",
|
||||
"productName": "FreeDATA",
|
||||
"directories": {
|
||||
"output": "release"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"dist-electron",
|
||||
],
|
||||
|
||||
|
||||
"extraResources": [
|
||||
{
|
||||
"from": "../modem/dist/modem/",
|
||||
"to": "modem",
|
||||
"filter": [
|
||||
"**/*",
|
||||
"!**/.git"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
|
||||
"mac": {
|
||||
"target": [
|
||||
"dmg"
|
||||
],
|
||||
"icon": "build/icon.png",
|
||||
"hardenedRuntime": true,
|
||||
"entitlements": "build/entitlements.plist",
|
||||
"entitlementsInherit": "build/entitlements.plist",
|
||||
"gatekeeperAssess": false,
|
||||
"artifactName": "${productName}-Mac-${version}-Installer.${ext}"
|
||||
},
|
||||
"win": {
|
||||
"icon": "build/icon.png",
|
||||
"target": [
|
||||
{
|
||||
"target": "nsis",
|
||||
"arch": [
|
||||
"x64"
|
||||
]
|
||||
}
|
||||
],
|
||||
"artifactName": "${productName}-Windows-${version}-Setup.${ext}"
|
||||
},
|
||||
"nsis": {
|
||||
"oneClick": false,
|
||||
"perMachine": false,
|
||||
"allowToChangeInstallationDirectory": true,
|
||||
"deleteAppDataOnUninstall": true
|
||||
},
|
||||
"linux": {
|
||||
"category": "Development",
|
||||
"target": [
|
||||
"AppImage"
|
||||
],
|
||||
"artifactName": "${productName}-${version}.${ext}"
|
||||
}
|
||||
}
|
11
gui/electron/electron-env.d.ts
vendored
Normal file
11
gui/electron/electron-env.d.ts
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
/// <reference types="vite-plugin-electron/electron-env" />
|
||||
|
||||
declare namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
VSCODE_DEBUG?: 'true'
|
||||
DIST_ELECTRON: string
|
||||
DIST: string
|
||||
/** /dist/ or /public/ */
|
||||
VITE_PUBLIC: string
|
||||
}
|
||||
}
|
289
gui/electron/main/index.ts
Normal file
289
gui/electron/main/index.ts
Normal file
|
@ -0,0 +1,289 @@
|
|||
import { app, BrowserWindow, shell, ipcMain } from "electron";
|
||||
import { release, platform } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { autoUpdater } from "electron-updater";
|
||||
import { existsSync } from "fs";
|
||||
import { spawn } from "child_process";
|
||||
|
||||
// The built directory structure
|
||||
//
|
||||
// ├─┬ dist-electron
|
||||
// │ ├─┬ main
|
||||
// │ │ └── index.js > Electron-Main
|
||||
// │ └─┬ preload
|
||||
// │ └── index.js > Preload-Scripts
|
||||
// ├─┬ dist
|
||||
// │ └── index.html > Electron-Renderer
|
||||
//
|
||||
process.env.DIST_ELECTRON = join(__dirname, "..");
|
||||
process.env.DIST = join(process.env.DIST_ELECTRON, "../dist");
|
||||
process.env.VITE_PUBLIC = process.env.VITE_DEV_SERVER_URL
|
||||
? join(process.env.DIST_ELECTRON, "../public")
|
||||
: process.env.DIST;
|
||||
|
||||
// Disable GPU Acceleration for Windows 7
|
||||
if (release().startsWith("6.1")) app.disableHardwareAcceleration();
|
||||
|
||||
// Set application name for Windows 10+ notifications
|
||||
if (process.platform === "win32") app.setAppUserModelId(app.getName());
|
||||
|
||||
if (!app.requestSingleInstanceLock()) {
|
||||
close_sub_processes();
|
||||
app.quit();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Remove electron security warnings
|
||||
// This warning only shows in development mode
|
||||
// Read more on https://www.electronjs.org/docs/latest/tutorial/security
|
||||
// process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'
|
||||
|
||||
// set daemon process var
|
||||
var daemonProcess = null;
|
||||
let win: BrowserWindow | null = null;
|
||||
// Here, you can also use other preload
|
||||
const preload = join(__dirname, "../preload/index.js");
|
||||
const url = process.env.VITE_DEV_SERVER_URL;
|
||||
const indexHtml = join(process.env.DIST, "index.html");
|
||||
|
||||
async function createWindow() {
|
||||
win = new BrowserWindow({
|
||||
title: "FreeDATA",
|
||||
width: 1200,
|
||||
height: 670,
|
||||
icon: join(process.env.VITE_PUBLIC, "icon_cube_border.png"),
|
||||
autoHideMenuBar: true,
|
||||
webPreferences: {
|
||||
preload,
|
||||
backgroundThrottle: false,
|
||||
|
||||
// Warning: Enable nodeIntegration and disable contextIsolation is not secure in production
|
||||
// Consider using contextBridge.exposeInMainWorld
|
||||
// Read more on https://www.electronjs.org/docs/latest/tutorial/context-isolation
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
},
|
||||
});
|
||||
|
||||
if (process.env.VITE_DEV_SERVER_URL) {
|
||||
// electron-vite-vue#298
|
||||
win.loadURL(url);
|
||||
// Open devTool if the app is not packaged
|
||||
win.webContents.openDevTools();
|
||||
} else {
|
||||
win.loadFile(indexHtml);
|
||||
}
|
||||
|
||||
// Test actively push message to the Electron-Renderer
|
||||
win.webContents.on("did-finish-load", () => {
|
||||
win?.webContents.send("main-process-message", new Date().toLocaleString());
|
||||
});
|
||||
|
||||
// Make all links open with the browser, not with the application
|
||||
win.webContents.setWindowOpenHandler(({ url }) => {
|
||||
if (url.startsWith("https:")) shell.openExternal(url);
|
||||
return { action: "deny" };
|
||||
});
|
||||
// win.webContents.on('will-navigate', (event, url) => { }) #344
|
||||
|
||||
win.once("ready-to-show", () => {
|
||||
//autoUpdater.logger = log.scope("updater");
|
||||
//autoUpdater.channel = config.update_channel;
|
||||
autoUpdater.autoInstallOnAppQuit = false;
|
||||
autoUpdater.autoDownload = true;
|
||||
autoUpdater.checkForUpdatesAndNotify();
|
||||
//autoUpdater.quitAndInstall();
|
||||
});
|
||||
}
|
||||
|
||||
//app.whenReady().then(
|
||||
app.whenReady().then(() => {
|
||||
createWindow();
|
||||
|
||||
//Generate daemon binary path
|
||||
var daemonPath = "";
|
||||
switch (platform().toLowerCase()) {
|
||||
case "darwin":
|
||||
case "linux":
|
||||
daemonPath = join(process.resourcesPath, "modem", "freedata-daemon");
|
||||
|
||||
break;
|
||||
case "win32":
|
||||
case "win64":
|
||||
daemonPath = join(process.resourcesPath, "modem", "freedata-daemon.exe");
|
||||
break;
|
||||
default:
|
||||
console.log("Unhandled OS Platform: ", platform());
|
||||
break;
|
||||
}
|
||||
|
||||
//Start daemon binary if it exists
|
||||
if (existsSync(daemonPath)) {
|
||||
console.log("Starting freedata-daemon binary");
|
||||
daemonProcess = spawn(daemonPath, [], {
|
||||
cwd: join(daemonPath, ".."),
|
||||
});
|
||||
// return process messages
|
||||
daemonProcess.on("error", (err) => {
|
||||
// daemonProcessLog.error(`error when starting daemon: ${err}`);
|
||||
console.log(err);
|
||||
});
|
||||
daemonProcess.on("message", () => {
|
||||
// daemonProcessLog.info(`${data}`);
|
||||
});
|
||||
daemonProcess.stdout.on("data", () => {
|
||||
// daemonProcessLog.info(`${data}`);
|
||||
});
|
||||
daemonProcess.stderr.on("data", () => {
|
||||
// daemonProcessLog.info(`${data}`);
|
||||
});
|
||||
daemonProcess.on("close", (code) => {
|
||||
// daemonProcessLog.warn(`daemonProcess exited with code ${code}`);
|
||||
});
|
||||
} else {
|
||||
daemonProcess = null;
|
||||
daemonPath = null;
|
||||
console.log("Daemon binary doesn't exist--normal for dev environments.");
|
||||
}
|
||||
|
||||
//)
|
||||
});
|
||||
|
||||
app.on("window-all-closed", () => {
|
||||
win = null;
|
||||
if (process.platform !== "darwin") app.quit(close_sub_processes());
|
||||
});
|
||||
|
||||
app.on("second-instance", () => {
|
||||
if (win) {
|
||||
// Focus on the main window if the user tried to open another
|
||||
if (win.isMinimized()) win.restore();
|
||||
win.focus();
|
||||
}
|
||||
});
|
||||
|
||||
app.on("activate", () => {
|
||||
const allWindows = BrowserWindow.getAllWindows();
|
||||
if (allWindows.length) {
|
||||
allWindows[0].focus();
|
||||
} else {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
|
||||
// New window example arg: new windows url
|
||||
ipcMain.handle("open-win", (_, arg) => {
|
||||
const childWindow = new BrowserWindow({
|
||||
webPreferences: {
|
||||
preload,
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
},
|
||||
});
|
||||
|
||||
if (process.env.VITE_DEV_SERVER_URL) {
|
||||
childWindow.loadURL(`${url}#${arg}`);
|
||||
} else {
|
||||
childWindow.loadFile(indexHtml, { hash: arg });
|
||||
}
|
||||
});
|
||||
|
||||
//restart and install udpate
|
||||
ipcMain.on("request-restart-and-install-update", (event, data) => {
|
||||
close_sub_processes();
|
||||
autoUpdater.quitAndInstall();
|
||||
});
|
||||
|
||||
// LISTENER FOR UPDATER EVENTS
|
||||
autoUpdater.on("update-available", (info) => {
|
||||
console.log("update available");
|
||||
|
||||
let arg = {
|
||||
status: "update-available",
|
||||
info: info,
|
||||
};
|
||||
win.webContents.send("action-updater", arg);
|
||||
});
|
||||
|
||||
autoUpdater.on("update-not-available", (info) => {
|
||||
console.log("update not available");
|
||||
let arg = {
|
||||
status: "update-not-available",
|
||||
info: info,
|
||||
};
|
||||
win.webContents.send("action-updater", arg);
|
||||
});
|
||||
|
||||
autoUpdater.on("update-downloaded", (info) => {
|
||||
console.log("update downloaded");
|
||||
let arg = {
|
||||
status: "update-downloaded",
|
||||
info: info,
|
||||
};
|
||||
win.webContents.send("action-updater", arg);
|
||||
// we need to call this at this point.
|
||||
// if an update is available and we are force closing the app
|
||||
// the entire screen crashes...
|
||||
//console.log('quit application and install update');
|
||||
//autoUpdater.quitAndInstall();
|
||||
});
|
||||
|
||||
autoUpdater.on("checking-for-update", () => {
|
||||
console.log("checking for update");
|
||||
let arg = {
|
||||
status: "checking-for-update",
|
||||
version: app.getVersion(),
|
||||
};
|
||||
win.webContents.send("action-updater", arg);
|
||||
});
|
||||
|
||||
autoUpdater.on("download-progress", (progress) => {
|
||||
let arg = {
|
||||
status: "download-progress",
|
||||
progress: progress,
|
||||
};
|
||||
win.webContents.send("action-updater", arg);
|
||||
});
|
||||
|
||||
autoUpdater.on("error", (error) => {
|
||||
console.log("update error");
|
||||
let arg = {
|
||||
status: "error",
|
||||
progress: error,
|
||||
};
|
||||
win.webContents.send("action-updater", arg);
|
||||
console.log("AUTO UPDATER : " + error);
|
||||
});
|
||||
|
||||
function close_sub_processes() {
|
||||
console.log("closing sub processes");
|
||||
|
||||
// closing the modem binary if not closed when closing application and also our daemon which has been started by the gui
|
||||
try {
|
||||
if (daemonProcess != null) {
|
||||
daemonProcess.kill();
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
console.log("closing modem and daemon");
|
||||
try {
|
||||
if (platform() == "win32" || platform() == "win64") {
|
||||
spawn("Taskkill", ["/IM", "freedata-modem.exe", "/F"]);
|
||||
spawn("Taskkill", ["/IM", "freedata-daemon.exe", "/F"]);
|
||||
}
|
||||
|
||||
if (platform() == "linux") {
|
||||
spawn("pkill", ["-9", "freedata-modem"]);
|
||||
spawn("pkill", ["-9", "freedata-daemon"]);
|
||||
}
|
||||
|
||||
if (platform() == "darwin") {
|
||||
spawn("pkill", ["-9", "freedata-modem"]);
|
||||
spawn("pkill", ["-9", "freedata-daemon"]);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
204
gui/electron/preload/index.ts
Normal file
204
gui/electron/preload/index.ts
Normal file
|
@ -0,0 +1,204 @@
|
|||
import { ipcRenderer } from "electron";
|
||||
|
||||
function domReady(
|
||||
condition: DocumentReadyState[] = ["complete", "interactive"],
|
||||
) {
|
||||
return new Promise((resolve) => {
|
||||
if (condition.includes(document.readyState)) {
|
||||
resolve(true);
|
||||
} else {
|
||||
document.addEventListener("readystatechange", () => {
|
||||
if (condition.includes(document.readyState)) {
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const safeDOM = {
|
||||
append(parent: HTMLElement, child: HTMLElement) {
|
||||
if (!Array.from(parent.children).find((e) => e === child)) {
|
||||
return parent.appendChild(child);
|
||||
}
|
||||
},
|
||||
remove(parent: HTMLElement, child: HTMLElement) {
|
||||
if (Array.from(parent.children).find((e) => e === child)) {
|
||||
return parent.removeChild(child);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* https://tobiasahlin.com/spinkit
|
||||
* https://connoratherton.com/loaders
|
||||
* https://projects.lukehaas.me/css-loaders
|
||||
* https://matejkustec.github.io/SpinThatShit
|
||||
*/
|
||||
function useLoading() {
|
||||
const className = `loaders-css__square-spin`;
|
||||
const styleContent = `
|
||||
@keyframes square-spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
background-image: url('icon_cube_border.png'); /* Replace with the URL of your image */
|
||||
background-size: cover; /* Scale the image to cover the entire container */
|
||||
}
|
||||
25% { transform: perspective(100px) rotateX(180deg) rotateY(0);
|
||||
background-image: url('icon_cube_border.png'); /* Replace with the URL of your image */
|
||||
background-size: cover; /* Scale the image to cover the entire container */
|
||||
}
|
||||
|
||||
50% { transform: perspective(100px) rotateX(180deg) rotateY(180deg);
|
||||
background-image: url('icon_cube_border.png'); /* Replace with the URL of your image */
|
||||
background-size: cover; /* Scale the image to cover the entire container */
|
||||
}
|
||||
75% { transform: perspective(100px) rotateX(0) rotateY(180deg);
|
||||
background-image: url('icon_cube_border.png'); /* Replace with the URL of your image */
|
||||
background-size: cover; /* Scale the image to cover the entire container */
|
||||
}
|
||||
100% { transform: perspective(100px) rotateX(0) rotateY(0);
|
||||
background-image: url('icon_cube_border.png'); /* Replace with the URL of your image */
|
||||
background-size: cover; /* Scale the image to cover the entire container */
|
||||
}
|
||||
}
|
||||
.${className} > div {
|
||||
animation-fill-mode: both;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
background: #fff;
|
||||
animation: square-spin 6s 0s cubic-bezier(0.09, 0.57, 0.49, 0.9) infinite;
|
||||
}
|
||||
.app-loading-wrap {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #282c34;
|
||||
z-index: 99999;
|
||||
}
|
||||
`;
|
||||
const oStyle = document.createElement("style");
|
||||
const oDiv = document.createElement("div");
|
||||
|
||||
oStyle.id = "app-loading-style";
|
||||
oStyle.innerHTML = styleContent;
|
||||
oDiv.className = "app-loading-wrap";
|
||||
oDiv.innerHTML = `<div class="${className}"><div></div></div>`;
|
||||
|
||||
return {
|
||||
appendLoading() {
|
||||
safeDOM.append(document.head, oStyle);
|
||||
safeDOM.append(document.body, oDiv);
|
||||
},
|
||||
removeLoading() {
|
||||
safeDOM.remove(document.head, oStyle);
|
||||
safeDOM.remove(document.body, oDiv);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const { appendLoading, removeLoading } = useLoading();
|
||||
domReady().then(appendLoading);
|
||||
|
||||
window.onmessage = (ev) => {
|
||||
ev.data.payload === "removeLoading" && removeLoading();
|
||||
};
|
||||
|
||||
setTimeout(removeLoading, 4999);
|
||||
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
// we are using this area for implementing the electron runUpdater
|
||||
// we need access to DOM for displaying updater results in GUI
|
||||
// close app, update and restart
|
||||
document
|
||||
.getElementById("update_and_install")
|
||||
.addEventListener("click", () => {
|
||||
ipcRenderer.send("request-restart-and-install-update");
|
||||
});
|
||||
});
|
||||
|
||||
// IPC ACTION FOR AUTO UPDATER
|
||||
ipcRenderer.on("action-updater", (event, arg) => {
|
||||
if (arg.status == "download-progress") {
|
||||
var progressinfo =
|
||||
"(" +
|
||||
Math.round(arg.progress.transferred / 1024) +
|
||||
"kB /" +
|
||||
Math.round(arg.progress.total / 1024) +
|
||||
"kB)" +
|
||||
" @ " +
|
||||
Math.round(arg.progress.bytesPerSecond / 1024) +
|
||||
"kByte/s";
|
||||
document.getElementById("UpdateProgressInfo").innerHTML = progressinfo;
|
||||
|
||||
document
|
||||
.getElementById("UpdateProgressBar")
|
||||
.setAttribute("aria-valuenow", arg.progress.percent);
|
||||
document
|
||||
.getElementById("UpdateProgressBar")
|
||||
.setAttribute("style", "width:" + arg.progress.percent + "%;");
|
||||
}
|
||||
|
||||
if (arg.status == "checking-for-update") {
|
||||
//document.title = document.title + ' - v' + arg.version;
|
||||
//updateTitle(
|
||||
// config.myCall,
|
||||
// config.tnc_host,
|
||||
// config.tnc_port,
|
||||
// " -v " + arg.version,
|
||||
//);
|
||||
document.getElementById("updater_status").innerHTML =
|
||||
'<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>';
|
||||
|
||||
document.getElementById("updater_status").className =
|
||||
"btn btn-secondary btn-sm";
|
||||
document.getElementById("update_and_install").style.display = "none";
|
||||
}
|
||||
if (arg.status == "update-downloaded") {
|
||||
document.getElementById("update_and_install").removeAttribute("style");
|
||||
document.getElementById("updater_status").innerHTML =
|
||||
'<i class="bi bi-cloud-download ms-1 me-1" style="color: white;"></i>';
|
||||
document.getElementById("updater_status").className =
|
||||
"btn btn-success btn-sm";
|
||||
|
||||
// HERE WE NEED TO RUN THIS SOMEHOW...
|
||||
//mainLog.info('quit application and install update');
|
||||
//autoUpdater.quitAndInstall();
|
||||
}
|
||||
if (arg.status == "update-not-available") {
|
||||
document.getElementById("updater_last_version").innerHTML =
|
||||
arg.info.releaseName;
|
||||
document.getElementById("updater_last_update").innerHTML =
|
||||
arg.info.releaseDate;
|
||||
document.getElementById("updater_release_notes").innerHTML =
|
||||
arg.info.releaseNotes;
|
||||
|
||||
document.getElementById("updater_status").innerHTML =
|
||||
'<i class="bi bi-check2-square ms-1 me-1" style="color: white;"></i>';
|
||||
document.getElementById("updater_status").className =
|
||||
"btn btn-success btn-sm";
|
||||
document.getElementById("update_and_install").style.display = "none";
|
||||
}
|
||||
if (arg.status == "update-available") {
|
||||
document.getElementById("updater_status").innerHTML =
|
||||
'<i class="bi bi-hourglass-split ms-1 me-1" style="color: white;"></i>';
|
||||
document.getElementById("updater_status").className =
|
||||
"btn btn-warning btn-sm";
|
||||
document.getElementById("update_and_install").style.display = "none";
|
||||
}
|
||||
|
||||
if (arg.status == "error") {
|
||||
document.getElementById("updater_status").innerHTML =
|
||||
'<i class="bi bi-exclamation-square ms-1 me-1" style="color: white;"></i>';
|
||||
document.getElementById("updater_status").className =
|
||||
"btn btn-danger btn-sm";
|
||||
document.getElementById("update_and_install").style.display = "none";
|
||||
}
|
||||
});
|
42
gui/index.html
Normal file
42
gui/index.html
Normal file
|
@ -0,0 +1,42 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
|
||||
<title>FreeDATA</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
|
||||
<script type="module">
|
||||
// debugging code for figuring out correct folder structure in build environment
|
||||
console.log(process.env)
|
||||
|
||||
|
||||
import { readdir } from 'node:fs/promises';
|
||||
import { readdirSync } from 'fs'
|
||||
import { join } from 'path'
|
||||
|
||||
function walk(dir) {
|
||||
return readdirSync(dir, { withFileTypes: true }).flatMap((file) => file.isDirectory() ? walk(join(dir, file.name)) : join(dir, file.name))
|
||||
}
|
||||
|
||||
|
||||
if (process.env["NODE_ENV"] == "production"){
|
||||
|
||||
console.log(walk(process.env["APPDIR"]))
|
||||
console.log(walk(process.env["DIST"]))
|
||||
console.log(walk(process.env["DIST_ELECTRON"]))
|
||||
|
||||
} else {
|
||||
console.log("running in " + process.env["NODE_ENV"])
|
||||
}
|
||||
|
||||
</script>
|
1083
gui/main.js
1083
gui/main.js
File diff suppressed because it is too large
Load diff
106
gui/package.json
106
gui/package.json
|
@ -1,36 +1,38 @@
|
|||
{
|
||||
"name": "FreeDATA",
|
||||
"version": "0.10.2-alpha.1",
|
||||
"description": "FreeDATA ",
|
||||
"main": "main.js",
|
||||
"private": true,
|
||||
"version": "0.11.0-alpha.1",
|
||||
"main": "dist-electron/main/index.js",
|
||||
"scripts": {
|
||||
"start": "electron .",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.17.0",
|
||||
"npm": ">=9.0.0"
|
||||
"start": "git pull && npm i && vite",
|
||||
"dev": "vite",
|
||||
"check": "vue-tsc --noEmit",
|
||||
"build": "vue-tsc --noEmit && vite build && electron-builder",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint --ext .js,.vue src",
|
||||
"lint-fix": "eslint --ext .js,.vue --fix src"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/DJ2LS/FreeDATA.git"
|
||||
},
|
||||
"keywords": [
|
||||
"TNC",
|
||||
"Modem",
|
||||
"GUI",
|
||||
"FreeDATA",
|
||||
"codec2"
|
||||
],
|
||||
"author": "DJ2LS",
|
||||
"license": "LGPL-2.1",
|
||||
"license": "GPL-3.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/DJ2LS/FreeDATA/issues"
|
||||
},
|
||||
"homepage": "https://freedata.app",
|
||||
"dependencies": {
|
||||
"@electron/asar": "^3.2.4",
|
||||
"@electron/osx-sign": "^1.0.4",
|
||||
"@electron/notarize": "^2.1.0",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@vueuse/electron": "^10.4.1",
|
||||
"blob-util": "^2.0.2",
|
||||
"bootstrap": "^5.3.1",
|
||||
"bootstrap-icons": "^1.10.5",
|
||||
|
@ -39,72 +41,42 @@
|
|||
"chart.js": "^4.3.3",
|
||||
"chartjs-plugin-annotation": "^3.0.1",
|
||||
"electron-log": "^4.4.8",
|
||||
"electron-updater": "^6.1.1",
|
||||
"electron-updater": "^6.1.6",
|
||||
"emoji-picker-element": "^1.18.3",
|
||||
"emoji-picker-element-data": "^1.4.0",
|
||||
"express-pouchdb": "^4.2.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"mime": "^3.0.0",
|
||||
"pinia": "^2.1.6",
|
||||
"pouchdb": "^8.0.1",
|
||||
"pouchdb-browser": "^8.0.1",
|
||||
"pouchdb-express-router": "^0.0.11",
|
||||
"pouchdb-find": "^8.0.1",
|
||||
"pouchdb-replication": "^8.0.1",
|
||||
"pouchdb-upsert": "^2.2.0",
|
||||
"qth-locator": "^2.1.0",
|
||||
"utf8": "^3.0.0",
|
||||
"uuid": "^9.0.0"
|
||||
"sass": "^1.66.1",
|
||||
"socket.io": "^4.7.2",
|
||||
"uuid": "^9.0.0",
|
||||
"vue": "^3.3.4",
|
||||
"vue-chartjs": "^5.2.0",
|
||||
"vuemoji-picker": "^0.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron/notarize": "^2.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.4",
|
||||
"@vitejs/plugin-vue": "^4.4.0",
|
||||
"electron": "^27.0.0",
|
||||
"electron-builder": "^24.6.3",
|
||||
"electron-builder-notarize": "^1.5.1"
|
||||
},
|
||||
"build": {
|
||||
"productName": "FreeDATA",
|
||||
"appId": "app.freedata",
|
||||
"afterSign": "electron-builder-notarize",
|
||||
"npmRebuild": "false",
|
||||
"directories": {
|
||||
"buildResources": "build",
|
||||
"output": "dist"
|
||||
},
|
||||
"mac": {
|
||||
"target": [
|
||||
"default"
|
||||
],
|
||||
"icon": "build/icon.png",
|
||||
"hardenedRuntime": true,
|
||||
"entitlements": "build/entitlements.plist",
|
||||
"entitlementsInherit": "build/entitlements.plist",
|
||||
"gatekeeperAssess": false
|
||||
},
|
||||
"win": {
|
||||
"icon": "build/icon.png",
|
||||
"target": [
|
||||
"nsis"
|
||||
]
|
||||
},
|
||||
"linux": {
|
||||
"icon": "build/icon.png",
|
||||
"target": [
|
||||
"AppImage"
|
||||
],
|
||||
"category": "Development"
|
||||
},
|
||||
"publish": {
|
||||
"provider": "github",
|
||||
"releaseType": "release"
|
||||
},
|
||||
"extraResources": [
|
||||
{
|
||||
"from": "../tnc/dist/tnc/",
|
||||
"to": "tnc",
|
||||
"filter": [
|
||||
"**/*",
|
||||
"!**/.git"
|
||||
]
|
||||
}
|
||||
]
|
||||
"eslint": "^8.50.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-config-standard-with-typescript": "^39.1.0",
|
||||
"eslint-plugin-import": "^2.28.1",
|
||||
"eslint-plugin-n": "^16.1.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-vue": "^9.17.0",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^4.3.2",
|
||||
"vite-plugin-electron": "^0.14.0",
|
||||
"vite-plugin-electron-renderer": "^0.14.5",
|
||||
"vue": "^3.3.4",
|
||||
"vue-tsc": "^1.4.2"
|
||||
}
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 590 KiB After Width: | Height: | Size: 590 KiB |
15
gui/setup.md
Normal file
15
gui/setup.md
Normal file
|
@ -0,0 +1,15 @@
|
|||
https://getbootstrap.com/docs/5.3/getting-started/vite/
|
||||
https://vuejs.org/guide/essentials/event-handling.html#inline-handlers
|
||||
https://linuxhint.com/install-use-bootstrap-with-vue-js/
|
||||
https://github.com/electron-vite
|
||||
https://github.com/electron-vite/electron-vite-vue
|
||||
https://github.com/vuejs/create-vue
|
||||
https://vue-community.org/guide/ecosystem/desktop-apps.html#electron
|
||||
https://blog.logrocket.com/building-app-electron-vue/
|
||||
|
||||
|
||||
Folder structure
|
||||
dist-electron: Automatically compiled source from vite
|
||||
electron: Source code folder for Electron stuff
|
||||
public: Public data
|
||||
src: VueJS source code
|
939
gui/sock.js
939
gui/sock.js
|
@ -1,939 +0,0 @@
|
|||
var net = require("net");
|
||||
const path = require("path");
|
||||
const { ipcRenderer } = require("electron");
|
||||
const FD = require("./freedata");
|
||||
const log = require("electron-log");
|
||||
const socketLog = log.scope("tnc");
|
||||
//const utf8 = require("utf8");
|
||||
|
||||
// https://stackoverflow.com/a/26227660
|
||||
var appDataFolder =
|
||||
process.env.APPDATA ||
|
||||
(process.platform == "darwin"
|
||||
? process.env.HOME + "/Library/Application Support"
|
||||
: process.env.HOME + "/.config");
|
||||
var configFolder = path.join(appDataFolder, "FreeDATA");
|
||||
var configPath = path.join(configFolder, "config.json");
|
||||
const config = require(configPath);
|
||||
|
||||
var client = new net.Socket();
|
||||
var socketchunk = ""; // Current message, per connection.
|
||||
|
||||
// split character
|
||||
const split_char = "\0;\1;";
|
||||
|
||||
// globals for getting new data only if available so we are saving bandwidth
|
||||
var rxBufferLengthTnc = 0;
|
||||
var rxBufferLengthGui = 0;
|
||||
//var rxMsgBufferLengthTnc = 0;
|
||||
//var rxMsgBufferLengthGui = 0;
|
||||
|
||||
// global to keep track of TNC connection error emissions
|
||||
var tncShowConnectStateError = 1;
|
||||
|
||||
// global for storing ip information
|
||||
var tnc_port = config.tnc_port;
|
||||
var tnc_host = config.tnc_host;
|
||||
|
||||
// network connection Timeout
|
||||
setTimeout(connectTNC, 2000);
|
||||
|
||||
function connectTNC() {
|
||||
//exports.connectTNC = function(){
|
||||
//socketLog.info('connecting to TNC...')
|
||||
|
||||
//clear message buffer after reconnecting or initial connection
|
||||
socketchunk = "";
|
||||
|
||||
if (config.tnclocation == "localhost") {
|
||||
client.connect(3000, "127.0.0.1");
|
||||
} else {
|
||||
client.connect(tnc_port, tnc_host);
|
||||
}
|
||||
}
|
||||
|
||||
client.on("connect", function (data) {
|
||||
socketLog.info("TNC connection established");
|
||||
let Data = {
|
||||
busy_state: "-",
|
||||
arq_state: "-",
|
||||
//channel_state: "-",
|
||||
frequency: "-",
|
||||
mode: "-",
|
||||
bandwidth: "-",
|
||||
dbfs_level: 0,
|
||||
};
|
||||
ipcRenderer.send("request-update-tnc-state", Data);
|
||||
|
||||
// also update tnc connection state
|
||||
ipcRenderer.send("request-update-tnc-connection", {
|
||||
tnc_connection: client.readyState,
|
||||
});
|
||||
|
||||
tncShowConnectStateError = 1;
|
||||
});
|
||||
|
||||
client.on("error", function (data) {
|
||||
if (tncShowConnectStateError == 1) {
|
||||
socketLog.error("TNC connection error");
|
||||
tncShowConnectStateError = 0;
|
||||
}
|
||||
setTimeout(connectTNC, 500);
|
||||
client.destroy();
|
||||
let Data = {
|
||||
tnc_connection: client.readyState,
|
||||
busy_state: "-",
|
||||
arq_state: "-",
|
||||
//channel_state: "-",
|
||||
frequency: "-",
|
||||
mode: "-",
|
||||
bandwidth: "-",
|
||||
dbfs_level: 0,
|
||||
};
|
||||
ipcRenderer.send("request-update-tnc-state", Data);
|
||||
ipcRenderer.send("request-update-tnc-connection", {
|
||||
tnc_connection: client.readyState,
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
client.on('close', function(data) {
|
||||
socketLog.info(' TNC connection closed');
|
||||
setTimeout(connectTNC, 2000)
|
||||
});
|
||||
*/
|
||||
|
||||
client.on("end", function (data) {
|
||||
socketLog.info("TNC connection ended");
|
||||
ipcRenderer.send("request-update-tnc-connection", {
|
||||
tnc_connection: client.readyState,
|
||||
});
|
||||
client.destroy();
|
||||
|
||||
setTimeout(connectTNC, 500);
|
||||
});
|
||||
|
||||
writeTncCommand = function (command) {
|
||||
//socketLog.info(command)
|
||||
// we use the writingCommand function to update our TCPIP state because we are calling this function a lot
|
||||
// if socket opened, we are able to run commands
|
||||
|
||||
if (client.readyState == "open") {
|
||||
client.write(command + "\n");
|
||||
}
|
||||
|
||||
if (client.readyState == "closed") {
|
||||
socketLog.info("CLOSED!");
|
||||
}
|
||||
|
||||
if (client.readyState == "opening") {
|
||||
socketLog.info("connecting to TNC...");
|
||||
}
|
||||
};
|
||||
|
||||
client.on("data", function (socketdata) {
|
||||
ipcRenderer.send("request-update-tnc-connection", {
|
||||
tnc_connection: client.readyState,
|
||||
});
|
||||
|
||||
/*
|
||||
inspired by:
|
||||
stackoverflow.com questions 9070700 nodejs-net-createserver-large-amount-of-data-coming-in
|
||||
*/
|
||||
|
||||
socketdata = socketdata.toString("utf8"); // convert data to string
|
||||
socketchunk += socketdata; // append data to buffer so we can stick long data together
|
||||
|
||||
// check if we received begin and end of json data
|
||||
if (socketchunk.startsWith('{"') && socketchunk.endsWith('"}\n')) {
|
||||
var data = "";
|
||||
|
||||
// split data into chunks if we received multiple commands
|
||||
socketchunk = socketchunk.split("\n");
|
||||
//don't think this is needed anymore
|
||||
//data = JSON.parse(socketchunk[0])
|
||||
|
||||
// search for empty entries in socketchunk and remove them
|
||||
for (i = 0; i < socketchunk.length; i++) {
|
||||
if (socketchunk[i] === "") {
|
||||
socketchunk.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
//iterate through socketchunks array to execute multiple commands in row
|
||||
for (i = 0; i < socketchunk.length; i++) {
|
||||
//check if data is not empty
|
||||
if (socketchunk[i].length > 0) {
|
||||
//try to parse JSON
|
||||
try {
|
||||
data = JSON.parse(socketchunk[i]);
|
||||
} catch (e) {
|
||||
socketLog.info("Throwing away data!!!!\n" + e); // "SyntaxError
|
||||
//socketLog.info(e); // "SyntaxError
|
||||
socketLog.info(socketchunk[i]);
|
||||
socketchunk = "";
|
||||
//If we're here, I don't think we want to process any data that may be in data variable
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (data["command"] == "tnc_state") {
|
||||
//socketLog.info(data)
|
||||
// set length of RX Buffer to global variable
|
||||
rxBufferLengthTnc = data["rx_buffer_length"];
|
||||
//rxMsgBufferLengthTnc = data["rx_msg_buffer_length"];
|
||||
|
||||
let Data = {
|
||||
mycallsign: data["mycallsign"],
|
||||
mygrid: data["mygrid"],
|
||||
ptt_state: data["ptt_state"],
|
||||
busy_state: data["tnc_state"],
|
||||
arq_state: data["arq_state"],
|
||||
arq_session: data["arq_session"],
|
||||
//channel_state: data['CHANNEL_STATE'],
|
||||
frequency: data["frequency"],
|
||||
speed_level: data["speed_level"],
|
||||
mode: data["mode"],
|
||||
bandwidth: data["bandwidth"],
|
||||
dbfs_level: data["audio_dbfs"],
|
||||
fft: data["fft"],
|
||||
channel_busy: data["channel_busy"],
|
||||
channel_busy_slot: data["channel_busy_slot"],
|
||||
scatter: data["scatter"],
|
||||
info: data["info"],
|
||||
rx_buffer_length: data["rx_buffer_length"],
|
||||
rx_msg_buffer_length: data["rx_msg_buffer_length"],
|
||||
tx_n_max_retries: data["tx_n_max_retries"],
|
||||
arq_tx_n_frames_per_burst: data["arq_tx_n_frames_per_burst"],
|
||||
arq_tx_n_bursts: data["arq_tx_n_bursts"],
|
||||
arq_tx_n_current_arq_frame: data["arq_tx_n_current_arq_frame"],
|
||||
arq_tx_n_total_arq_frames: data["arq_tx_n_total_arq_frames"],
|
||||
arq_rx_frame_n_bursts: data["arq_rx_frame_n_bursts"],
|
||||
arq_rx_n_current_arq_frame: data["arq_rx_n_current_arq_frame"],
|
||||
arq_n_arq_frames_per_data_frame:
|
||||
data["arq_n_arq_frames_per_data_frame"],
|
||||
arq_bytes_per_minute: data["arq_bytes_per_minute"],
|
||||
arq_seconds_until_finish: data["arq_seconds_until_finish"],
|
||||
arq_compression_factor: data["arq_compression_factor"],
|
||||
total_bytes: data["total_bytes"],
|
||||
arq_transmission_percent: data["arq_transmission_percent"],
|
||||
stations: data["stations"],
|
||||
routing_table: data["routing_table"],
|
||||
mesh_signalling_table: data["mesh_signalling_table"],
|
||||
beacon_state: data["beacon_state"],
|
||||
hamlib_status: data["hamlib_status"],
|
||||
listen: data["listen"],
|
||||
audio_recording: data["audio_recording"],
|
||||
speed_list: data["speed_list"],
|
||||
strength: data["strength"],
|
||||
is_codec2_traffic: data["is_codec2_traffic"],
|
||||
//speed_table: [{"bpm" : 5200, "snr": -3, "timestamp":1673555399},{"bpm" : 2315, "snr": 12, "timestamp":1673555500}],
|
||||
};
|
||||
|
||||
ipcRenderer.send("request-update-tnc-state", Data);
|
||||
//continue to next for loop iteration, nothing else needs to be done here
|
||||
continue;
|
||||
}
|
||||
|
||||
// ----------- catch tnc messages START -----------
|
||||
if (data["freedata"] == "tnc-message") {
|
||||
switch (data["fec"]) {
|
||||
case "is_writing":
|
||||
// RX'd FECiswriting
|
||||
ipcRenderer.send("request-show-fec-toast-iswriting", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
|
||||
case "broadcast":
|
||||
// RX'd FEC BROADCAST
|
||||
var encoded_data = FD.atob_FD(data["data"]);
|
||||
var splitted_data = encoded_data.split(split_char);
|
||||
var messageArray = [];
|
||||
if (splitted_data[0] == "m") {
|
||||
messageArray.push(data);
|
||||
console.log(data);
|
||||
}
|
||||
|
||||
let Messages = {
|
||||
data: messageArray,
|
||||
};
|
||||
ipcRenderer.send("request-new-msg-received", Messages);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (data["cq"]) {
|
||||
case "transmitting":
|
||||
// CQ TRANSMITTING
|
||||
ipcRenderer.send("request-show-cq-toast-transmitting", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
|
||||
case "received":
|
||||
// CQ RECEIVED
|
||||
ipcRenderer.send("request-show-cq-toast-received", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
switch (data["qrv"]) {
|
||||
case "transmitting":
|
||||
// QRV TRANSMITTING
|
||||
ipcRenderer.send("request-show-qrv-toast-transmitting", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
|
||||
case "received":
|
||||
// QRV RECEIVED
|
||||
ipcRenderer.send("request-show-qrv-toast-received", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
switch (data["beacon"]) {
|
||||
case "transmitting":
|
||||
// BEACON TRANSMITTING
|
||||
ipcRenderer.send("request-show-beacon-toast-transmitting", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
|
||||
case "received":
|
||||
// BEACON RECEIVED
|
||||
ipcRenderer.send("request-show-beacon-toast-received", {
|
||||
data: [data],
|
||||
});
|
||||
ipcRenderer.send("request-new-msg-received", { data: [data] });
|
||||
break;
|
||||
}
|
||||
|
||||
switch (data["ping"]) {
|
||||
case "transmitting":
|
||||
// PING TRANSMITTING
|
||||
ipcRenderer.send("request-show-ping-toast-transmitting", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
|
||||
case "received":
|
||||
// PING RECEIVED
|
||||
ipcRenderer.send("request-show-ping-toast-received", {
|
||||
data: [data],
|
||||
});
|
||||
ipcRenderer.send("request-new-msg-received", { data: [data] });
|
||||
break;
|
||||
|
||||
case "acknowledge":
|
||||
// PING ACKNOWLEDGE
|
||||
ipcRenderer.send("request-show-ping-toast-received-ack", {
|
||||
data: [data],
|
||||
});
|
||||
ipcRenderer.send("request-new-msg-received", { data: [data] });
|
||||
break;
|
||||
}
|
||||
|
||||
// ARQ SESSION && freedata == tnc-message
|
||||
if (data["arq"] == "session") {
|
||||
switch (data["status"]) {
|
||||
case "connecting":
|
||||
// ARQ Open
|
||||
ipcRenderer.send("request-show-arq-toast-session-connecting", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
|
||||
case "connected":
|
||||
// ARQ Opening
|
||||
ipcRenderer.send("request-show-arq-toast-session-connected", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
|
||||
case "waiting":
|
||||
// ARQ Opening
|
||||
ipcRenderer.send("request-show-arq-toast-session-waiting", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
|
||||
case "close":
|
||||
// ARQ Closing
|
||||
ipcRenderer.send("request-show-arq-toast-session-close", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
|
||||
case "failed":
|
||||
// ARQ Failed
|
||||
ipcRenderer.send("request-show-arq-toast-session-failed", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
// ARQ TRANSMISSION && freedata == tnc-message
|
||||
if (data["arq"] == "transmission") {
|
||||
switch (data["status"]) {
|
||||
case "opened":
|
||||
// ARQ Open
|
||||
ipcRenderer.send("request-show-arq-toast-datachannel-opened", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
|
||||
case "opening":
|
||||
// ARQ Opening IRS/ISS
|
||||
if (data["irs"] == "False") {
|
||||
ipcRenderer.send("request-show-arq-toast-datachannel-opening", {
|
||||
data: [data],
|
||||
});
|
||||
ipcRenderer.send("request-update-transmission-status", {
|
||||
data: [data],
|
||||
});
|
||||
} else {
|
||||
ipcRenderer.send(
|
||||
"request-show-arq-toast-datachannel-received-opener",
|
||||
{ data: [data] },
|
||||
);
|
||||
ipcRenderer.send("request-update-reception-status", {
|
||||
data: [data],
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "waiting":
|
||||
// ARQ waiting
|
||||
ipcRenderer.send("request-show-arq-toast-datachannel-waiting", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
|
||||
case "receiving":
|
||||
// ARQ RX
|
||||
ipcRenderer.send("request-update-reception-status", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
|
||||
case "failed":
|
||||
// ARQ TX Failed
|
||||
if (data["reason"] == "protocol version missmatch") {
|
||||
ipcRenderer.send(
|
||||
"request-show-arq-toast-transmission-failed-ver",
|
||||
{ data: [data] },
|
||||
);
|
||||
} else {
|
||||
ipcRenderer.send("request-show-arq-toast-transmission-failed", {
|
||||
data: [data],
|
||||
});
|
||||
}
|
||||
switch (data["irs"]) {
|
||||
case "True":
|
||||
ipcRenderer.send("request-update-reception-status", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
default:
|
||||
ipcRenderer.send("request-update-transmission-status", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case "received":
|
||||
// ARQ Received
|
||||
ipcRenderer.send("request-show-arq-toast-transmission-received", {
|
||||
data: [data],
|
||||
});
|
||||
|
||||
ipcRenderer.send("request-update-reception-status", {
|
||||
data: [data],
|
||||
});
|
||||
|
||||
dataArray = [];
|
||||
messageArray = [];
|
||||
|
||||
socketLog.info(data);
|
||||
// we need to encode here to do a deep check for checking if file or message
|
||||
//var encoded_data = atob(data['data'])
|
||||
var encoded_data = FD.atob_FD(data["data"]);
|
||||
var splitted_data = encoded_data.split(split_char);
|
||||
|
||||
if (splitted_data[0] == "f") {
|
||||
dataArray.push(data);
|
||||
}
|
||||
|
||||
if (splitted_data[0] == "m") {
|
||||
messageArray.push(data);
|
||||
console.log(data);
|
||||
}
|
||||
|
||||
rxBufferLengthGui = dataArray.length;
|
||||
let Files = {
|
||||
data: dataArray,
|
||||
};
|
||||
ipcRenderer.send("request-update-rx-buffer", Files);
|
||||
ipcRenderer.send("request-new-msg-received", Files);
|
||||
|
||||
//rxMsgBufferLengthGui = messageArray.length;
|
||||
let Messages = {
|
||||
data: messageArray,
|
||||
};
|
||||
ipcRenderer.send("request-new-msg-received", Messages);
|
||||
break;
|
||||
|
||||
case "transmitting":
|
||||
// ARQ transmitting
|
||||
ipcRenderer.send(
|
||||
"request-show-arq-toast-transmission-transmitting",
|
||||
{ data: [data] },
|
||||
);
|
||||
ipcRenderer.send("request-update-transmission-status", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
|
||||
case "transmitted":
|
||||
// ARQ transmitted
|
||||
ipcRenderer.send(
|
||||
"request-show-arq-toast-transmission-transmitted",
|
||||
{ data: [data] },
|
||||
);
|
||||
ipcRenderer.send("request-update-transmission-status", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------- catch tnc info messages END -----------
|
||||
|
||||
// if we manually checking for the rx buffer we are getting an array of multiple data
|
||||
if (data["command"] == "rx_buffer") {
|
||||
socketLog.info(data);
|
||||
// iterate through buffer list and sort it to file or message array
|
||||
dataArray = [];
|
||||
messageArray = [];
|
||||
|
||||
for (i = 0; i < data["data-array"].length; i++) {
|
||||
try {
|
||||
// we need to encode here to do a deep check for checking if file or message
|
||||
//var encoded_data = atob(data['data-array'][i]['data'])
|
||||
var encoded_data = FD.atob_FD(data["data-array"][i]["data"]);
|
||||
var splitted_data = encoded_data.split(split_char);
|
||||
|
||||
if (splitted_data[0] == "f") {
|
||||
dataArray.push(data["data-array"][i]);
|
||||
}
|
||||
|
||||
if (splitted_data[0] == "m") {
|
||||
messageArray.push(data["data-array"][i]);
|
||||
}
|
||||
} catch (e) {
|
||||
socketLog.info(e);
|
||||
}
|
||||
}
|
||||
|
||||
rxBufferLengthGui = dataArray.length;
|
||||
let Files = {
|
||||
data: dataArray,
|
||||
};
|
||||
ipcRenderer.send("request-update-rx-buffer", Files);
|
||||
|
||||
//rxMsgBufferLengthGui = messageArray.length;
|
||||
let Messages = {
|
||||
data: messageArray,
|
||||
};
|
||||
//ipcRenderer.send('request-update-rx-msg-buffer', Messages);
|
||||
ipcRenderer.send("request-new-msg-received", Messages);
|
||||
}
|
||||
}
|
||||
|
||||
//finally delete message buffer
|
||||
socketchunk = "";
|
||||
}
|
||||
});
|
||||
|
||||
function hexToBytes(hex) {
|
||||
for (var bytes = [], c = 0; c < hex.length; c += 2)
|
||||
bytes.push(parseInt(hex.substr(c, 2), 16));
|
||||
return bytes;
|
||||
}
|
||||
|
||||
//Get TNC State
|
||||
exports.getTncState = function () {
|
||||
command = '{"type" : "get", "command" : "tnc_state"}';
|
||||
writeTncCommand(command);
|
||||
};
|
||||
|
||||
//Get DATA State
|
||||
exports.getDataState = function () {
|
||||
command = '{"type" : "get", "command" : "data_state"}';
|
||||
//writeTncCommand(command)
|
||||
};
|
||||
|
||||
// Send Ping
|
||||
exports.sendPing = function (dxcallsign) {
|
||||
command =
|
||||
'{"type" : "ping", "command" : "ping", "dxcallsign" : "' +
|
||||
dxcallsign +
|
||||
'"}';
|
||||
writeTncCommand(command);
|
||||
};
|
||||
|
||||
// Send Mesh Ping
|
||||
exports.sendMeshPing = function (dxcallsign) {
|
||||
command =
|
||||
'{"type" : "mesh", "command" : "ping", "dxcallsign" : "' +
|
||||
dxcallsign +
|
||||
'"}';
|
||||
writeTncCommand(command);
|
||||
};
|
||||
|
||||
// Send CQ
|
||||
exports.sendCQ = function () {
|
||||
command = '{"type" : "broadcast", "command" : "cqcqcq"}';
|
||||
writeTncCommand(command);
|
||||
};
|
||||
|
||||
// Set AUDIO Level
|
||||
exports.setTxAudioLevel = function (value) {
|
||||
command =
|
||||
'{"type" : "set", "command" : "tx_audio_level", "value" : "' + value + '"}';
|
||||
writeTncCommand(command);
|
||||
};
|
||||
|
||||
// Send File
|
||||
exports.sendFile = function (
|
||||
dxcallsign,
|
||||
mode,
|
||||
frames,
|
||||
filename,
|
||||
filetype,
|
||||
data,
|
||||
checksum,
|
||||
) {
|
||||
socketLog.info(data);
|
||||
socketLog.info(filetype);
|
||||
socketLog.info(filename);
|
||||
|
||||
var datatype = "f";
|
||||
|
||||
data =
|
||||
datatype +
|
||||
split_char +
|
||||
filename +
|
||||
split_char +
|
||||
filetype +
|
||||
split_char +
|
||||
checksum +
|
||||
split_char +
|
||||
data;
|
||||
socketLog.info(data);
|
||||
//socketLog.info(btoa(data))
|
||||
//Btoa / atob will not work with charsets > 8 bits (i.e. the emojis); should probably move away from using it
|
||||
//TODO: Will need to update anyother occurences and throughly test
|
||||
//data = btoa(data)
|
||||
data = FD.btoa_FD(data);
|
||||
|
||||
command =
|
||||
'{"type" : "arq", "command" : "send_raw", "parameter" : [{"dxcallsign" : "' +
|
||||
dxcallsign +
|
||||
'", "mode" : "' +
|
||||
mode +
|
||||
'", "n_frames" : "' +
|
||||
frames +
|
||||
'", "data" : "' +
|
||||
data +
|
||||
'"}]}';
|
||||
writeTncCommand(command);
|
||||
};
|
||||
|
||||
// Send Message
|
||||
exports.sendMessage = function (
|
||||
dxcallsign,
|
||||
mode,
|
||||
frames,
|
||||
data,
|
||||
checksum,
|
||||
uuid,
|
||||
command,
|
||||
) {
|
||||
data = FD.btoa_FD(
|
||||
"m" +
|
||||
split_char +
|
||||
command +
|
||||
split_char +
|
||||
checksum +
|
||||
split_char +
|
||||
uuid +
|
||||
split_char +
|
||||
data,
|
||||
);
|
||||
command =
|
||||
'{"type" : "arq", "command" : "send_raw", "uuid" : "' +
|
||||
uuid +
|
||||
'", "parameter" : [{"dxcallsign" : "' +
|
||||
dxcallsign +
|
||||
'", "mode" : "' +
|
||||
mode +
|
||||
'", "n_frames" : "' +
|
||||
frames +
|
||||
'", "data" : "' +
|
||||
data +
|
||||
'", "attempts": "10"}]}';
|
||||
socketLog.info(command);
|
||||
socketLog.info("-------------------------------------");
|
||||
writeTncCommand(command);
|
||||
};
|
||||
|
||||
// Send Request message
|
||||
//It would be then „m + split + request + split + request-type“
|
||||
function sendRequest(dxcallsign, mode, frames, data, command) {
|
||||
data = FD.btoa_FD("m" + split_char + command + split_char + data);
|
||||
command =
|
||||
'{"type" : "arq", "command" : "send_raw", "parameter" : [{"dxcallsign" : "' +
|
||||
dxcallsign +
|
||||
'", "mode" : "' +
|
||||
mode +
|
||||
'", "n_frames" : "' +
|
||||
frames +
|
||||
'", "data" : "' +
|
||||
data +
|
||||
'", "attempts": "10"}]}';
|
||||
socketLog.info(command);
|
||||
socketLog.info("--------------REQ--------------------");
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// Send Response message
|
||||
//It would be then „m + split + request + split + request-type“
|
||||
function sendResponse(dxcallsign, mode, frames, data, command) {
|
||||
data = FD.btoa_FD("m" + split_char + command + split_char + data);
|
||||
command =
|
||||
'{"type" : "arq", "command" : "send_raw", "parameter" : [{"dxcallsign" : "' +
|
||||
dxcallsign +
|
||||
'", "mode" : "' +
|
||||
mode +
|
||||
'", "n_frames" : "' +
|
||||
frames +
|
||||
'", "data" : "' +
|
||||
data +
|
||||
'", "attempts": "10"}]}';
|
||||
socketLog.info(command);
|
||||
socketLog.info("--------------RES--------------------");
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
//Send station info request
|
||||
exports.sendRequestInfo = function (dxcallsign) {
|
||||
//Command 0 = user/station information
|
||||
//Command 1 = shared folder list
|
||||
//Command 2 = shared file transfer
|
||||
sendRequest(dxcallsign, 255, 1, "0", "req");
|
||||
};
|
||||
|
||||
//Send shared folder file list request
|
||||
exports.sendRequestSharedFolderList = function (dxcallsign) {
|
||||
//Command 0 = user/station information
|
||||
//Command 1 = shared folder list
|
||||
//Command 2 = shared file transfer
|
||||
sendRequest(dxcallsign, 255, 1, "1", "req");
|
||||
};
|
||||
|
||||
//Send shared file request
|
||||
exports.sendRequestSharedFile = function (dxcallsign, file) {
|
||||
//Command 0 = user/station information
|
||||
//Command 1 = shared folder list
|
||||
//Command 2 = shared file transfer
|
||||
sendRequest(dxcallsign, 255, 1, "2" + file, "req");
|
||||
};
|
||||
|
||||
//Send station info response
|
||||
exports.sendResponseInfo = function (dxcallsign, userinfo) {
|
||||
//Command 0 = user/station information
|
||||
//Command 1 = shared folder list
|
||||
//Command 2 = shared file transfer
|
||||
sendResponse(dxcallsign, 255, 1, userinfo, "res-0");
|
||||
};
|
||||
|
||||
//Send shared folder response
|
||||
exports.sendResponseSharedFolderList = function (dxcallsign, sharedFolderList) {
|
||||
//Command 0 = user/station information
|
||||
//Command 1 = shared folder list
|
||||
//Command 2 = shared file transfer
|
||||
sendResponse(dxcallsign, 255, 1, sharedFolderList, "res-1");
|
||||
};
|
||||
|
||||
//Send shared file response
|
||||
exports.sendResponseSharedFile = function (
|
||||
dxcallsign,
|
||||
sharedFile,
|
||||
sharedFileData,
|
||||
) {
|
||||
console.log(
|
||||
"In sendResponseSharedFile",
|
||||
dxcallsign,
|
||||
sharedFile,
|
||||
sharedFileData,
|
||||
);
|
||||
//Command 0 = user/station information
|
||||
//Command 1 = shared folder list
|
||||
//Command 2 = shared file transfer
|
||||
sendResponse(dxcallsign, 255, 1, sharedFile + "/" + sharedFileData, "res-2");
|
||||
};
|
||||
|
||||
//STOP TRANSMISSION
|
||||
exports.stopTransmission = function () {
|
||||
command = '{"type" : "arq", "command": "stop_transmission"}';
|
||||
writeTncCommand(command);
|
||||
};
|
||||
|
||||
// Get RX BUffer
|
||||
exports.getRxBuffer = function () {
|
||||
command = '{"type" : "get", "command" : "rx_buffer"}';
|
||||
|
||||
// call command only if new data arrived
|
||||
if (rxBufferLengthGui != rxBufferLengthTnc) {
|
||||
writeTncCommand(command);
|
||||
}
|
||||
};
|
||||
|
||||
// START BEACON
|
||||
exports.startBeacon = function (interval) {
|
||||
command =
|
||||
'{"type" : "broadcast", "command" : "start_beacon", "parameter": "' +
|
||||
interval +
|
||||
'"}';
|
||||
writeTncCommand(command);
|
||||
};
|
||||
|
||||
// STOP BEACON
|
||||
exports.stopBeacon = function () {
|
||||
command = '{"type" : "broadcast", "command" : "stop_beacon"}';
|
||||
writeTncCommand(command);
|
||||
};
|
||||
|
||||
// OPEN ARQ SESSION
|
||||
exports.connectARQ = function (dxcallsign) {
|
||||
command =
|
||||
'{"type" : "arq", "command" : "connect", "dxcallsign": "' +
|
||||
dxcallsign +
|
||||
'", "attempts": "10"}';
|
||||
writeTncCommand(command);
|
||||
};
|
||||
|
||||
// CLOSE ARQ SESSION
|
||||
exports.disconnectARQ = function () {
|
||||
command = '{"type" : "arq", "command" : "disconnect"}';
|
||||
writeTncCommand(command);
|
||||
};
|
||||
|
||||
// SEND TEST FRAME
|
||||
exports.sendTestFrame = function () {
|
||||
command = '{"type" : "set", "command" : "send_test_frame"}';
|
||||
writeTncCommand(command);
|
||||
};
|
||||
|
||||
// SEND FEC
|
||||
exports.sendFEC = function (mode, payload) {
|
||||
command =
|
||||
'{"type" : "fec", "command" : "transmit", "mode" : "' +
|
||||
mode +
|
||||
'", "payload" : "' +
|
||||
payload +
|
||||
'"}';
|
||||
writeTncCommand(command);
|
||||
};
|
||||
|
||||
// SEND FEC IS WRITING
|
||||
exports.sendFecIsWriting = function (mycallsign) {
|
||||
command =
|
||||
'{"type" : "fec", "command" : "transmit_is_writing", "mycallsign" : "' +
|
||||
mycallsign +
|
||||
'"}';
|
||||
writeTncCommand(command);
|
||||
};
|
||||
|
||||
// SEND FEC TO BROADCASTCHANNEL
|
||||
exports.sendBroadcastChannel = function (channel, data_out, uuid) {
|
||||
let checksum = "";
|
||||
let command = "";
|
||||
let data = FD.btoa_FD(
|
||||
"m" +
|
||||
split_char +
|
||||
channel +
|
||||
//split_char +
|
||||
//checksum +
|
||||
split_char +
|
||||
uuid +
|
||||
split_char +
|
||||
data_out,
|
||||
);
|
||||
console.log(data.length);
|
||||
let payload = data;
|
||||
command =
|
||||
'{"type" : "fec", "command" : "transmit", "mode": "datac4", "wakeup": "True", "payload" : "' +
|
||||
payload +
|
||||
'"}';
|
||||
writeTncCommand(command);
|
||||
};
|
||||
|
||||
// RECORD AUDIO
|
||||
exports.record_audio = function () {
|
||||
command = '{"type" : "set", "command" : "record_audio"}';
|
||||
writeTncCommand(command);
|
||||
};
|
||||
|
||||
// SET FREQUENCY
|
||||
exports.set_frequency = function (frequency) {
|
||||
command =
|
||||
'{"type" : "set", "command" : "frequency", "frequency": ' + frequency + "}";
|
||||
writeTncCommand(command);
|
||||
};
|
||||
|
||||
// SET MODE
|
||||
exports.set_mode = function (mode) {
|
||||
command = '{"type" : "set", "command" : "mode", "mode": "' + mode + '"}';
|
||||
console.log(command);
|
||||
writeTncCommand(command);
|
||||
};
|
||||
|
||||
ipcRenderer.on("action-update-tnc-ip", (event, arg) => {
|
||||
client.destroy();
|
||||
let Data = {
|
||||
busy_state: "-",
|
||||
arq_state: "-",
|
||||
//channel_state: "-",
|
||||
frequency: "-",
|
||||
mode: "-",
|
||||
bandwidth: "-",
|
||||
dbfs_level: 0,
|
||||
};
|
||||
ipcRenderer.send("request-update-tnc-state", Data);
|
||||
tnc_port = arg.port;
|
||||
tnc_host = arg.adress;
|
||||
connectTNC();
|
||||
});
|
||||
|
||||
// https://stackoverflow.com/a/50579690
|
||||
// crc32 calculation
|
||||
//console.log(crc32('abc'));
|
||||
//console.log(crc32('abc').toString(16).toUpperCase()); // hex
|
||||
var crc32 = function (r) {
|
||||
for (var a, o = [], c = 0; c < 256; c++) {
|
||||
a = c;
|
||||
for (var f = 0; f < 8; f++) a = 1 & a ? 3988292384 ^ (a >>> 1) : a >>> 1;
|
||||
o[c] = a;
|
||||
}
|
||||
for (var n = -1, t = 0; t < r.length; t++)
|
||||
n = (n >>> 8) ^ o[255 & (n ^ r.charCodeAt(t))];
|
||||
return (-1 ^ n) >>> 0;
|
||||
};
|
7
gui/src/App.vue
Normal file
7
gui/src/App.vue
Normal file
|
@ -0,0 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import FreeDATAMain from "./components/main.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FreeDATAMain />
|
||||
</template>
|
2310
gui/src/assets/waterfall/spectrum.js
Normal file
2310
gui/src/assets/waterfall/spectrum.js
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,843 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en" data-bs-theme="light">
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self';" />
|
||||
<!-- Bootstrap CSS -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
id="bootstrap_theme"
|
||||
href="../node_modules/bootstrap/dist/css/bootstrap.min.css"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="../node_modules/bootstrap-icons/font/bootstrap-icons.css"
|
||||
/>
|
||||
<!-- Custom CSS -->
|
||||
<link rel="stylesheet" type="text/css" href="styles.css" />
|
||||
<title>FreeDATA - CHAT</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- bootstrap -->
|
||||
<script src="../node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<!-- chart.js -->
|
||||
<script src="../node_modules/chart.js/dist/chart.umd.js"></script>
|
||||
<!--<script src="../node_modules/chartjs-plugin-annotation/dist/chartjs-plugin-annotation.min.js"></script>-->
|
||||
<!--<script type="module" src="../node_modules/emoji-picker-element/index.js"></script>-->
|
||||
<script
|
||||
type="module"
|
||||
src="../node_modules/emoji-picker-element/picker.js"
|
||||
></script>
|
||||
<script
|
||||
type="module"
|
||||
src="../node_modules/emoji-picker-element/database.js"
|
||||
></script>
|
||||
<div
|
||||
class="position-absolute container w-100 h-100 bottom-0 end-0 mb-5"
|
||||
style="z-index: 100; display: none"
|
||||
id="emojipickercontainer"
|
||||
>
|
||||
<emoji-picker
|
||||
locale="en"
|
||||
class="position-absolute bottom-0 end-0 p-1 mb-2"
|
||||
data-source="../node_modules/emoji-picker-element-data/en/emojibase/data.json"
|
||||
></emoji-picker>
|
||||
</div>
|
||||
<div class="container-fluid">
|
||||
<div class="row h-100">
|
||||
<div class="col-4 p-2">
|
||||
<! ------Chats area ---------------------------------------------------------------------->
|
||||
<div class="container-fluid m-0 p-0">
|
||||
<div class="input-group bottom-0 m-0 w-100">
|
||||
<input
|
||||
class="form-control w-50"
|
||||
maxlength="9"
|
||||
style="text-transform: uppercase"
|
||||
id="chatModuleNewDxCall"
|
||||
placeholder="DX CALL"
|
||||
/>
|
||||
<button
|
||||
class="btn btn-sm btn-success"
|
||||
id="createNewChatButton"
|
||||
type="button"
|
||||
title="Start a new chat (enter dx call sign first)"
|
||||
>
|
||||
<i class="bi bi-pencil-square" style="font-size: 1.2rem"></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
id="userModalButton"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#userModal"
|
||||
class="btn btn-sm btn-primary ms-2"
|
||||
title="My station info"
|
||||
>
|
||||
<i class="bi bi-person" style="font-size: 1.2rem"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
id="sharedFolderButton"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#sharedFolderModal"
|
||||
class="btn btn-sm btn-primary"
|
||||
title="My shared folder"
|
||||
>
|
||||
<i class="bi bi-files" style="font-size: 1.2rem"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="m-2" />
|
||||
<div class="overflow-auto vh-100">
|
||||
<div
|
||||
class="list-group overflow-auto"
|
||||
id="list-tab"
|
||||
role="tablist"
|
||||
style="height: calc(100vh - 70px)"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-8 border vh-100 p-0">
|
||||
<! ------ chat navbar ---------------------------------------------------------------------->
|
||||
<div class="container-fluid m-2 p-0">
|
||||
<div class="input-group bottom-0">
|
||||
<button
|
||||
class="btn btn-sm btn-outline-secondary me"
|
||||
id="ping"
|
||||
type="button"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="false"
|
||||
title="Ping remote station"
|
||||
>
|
||||
Ping
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
id="userModalDXButton"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#userModalDX"
|
||||
class="btn btn-sm btn-outline-secondary"
|
||||
title="Request remote station's information"
|
||||
>
|
||||
<i class="bi bi-person" style="font-size: 1.2rem"></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
id="sharedFolderDXButton"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#sharedFolderModalDX"
|
||||
class="btn btn-sm btn-outline-secondary me-2"
|
||||
title="Request remote station's shared files"
|
||||
>
|
||||
<i class="bi bi-files" style="font-size: 1.2rem"></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-small btn-outline-primary dropdown-toggle me-2"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
data-bs-auto-close="outside"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="false"
|
||||
title="Message filter"
|
||||
>
|
||||
<i class="bi bi-funnel-fill"></i>
|
||||
</button>
|
||||
<form class="dropdown-menu p-4" id="frmFilter">
|
||||
<div class="mb-1">
|
||||
<div class="form-check">
|
||||
<input
|
||||
checked="true"
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
id="chkMessage"
|
||||
/>
|
||||
<label class="form-check-label" for="chkMessage">
|
||||
All Messages
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<div class="form-check">
|
||||
<input
|
||||
checked="false"
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
id="chkNewMessage"
|
||||
/>
|
||||
|
||||
<label class="form-check-label" for="chkNewMessage">
|
||||
Unread Messages
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<div class="form-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
id="chkPing"
|
||||
/>
|
||||
<label class="form-check-label" for="chkPing">
|
||||
Pings
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<div class="form-check">
|
||||
<input
|
||||
checked="true"
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
id="chkPingAck"
|
||||
/>
|
||||
<label class="form-check-label" for="chkPingAck">
|
||||
Ping-Acks
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<div class="form-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
id="chkBeacon"
|
||||
/>
|
||||
<label class="form-check-label" for="chkBeacon">
|
||||
Beacons
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<div class="form-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
id="chkRequest"
|
||||
/>
|
||||
<label class="form-check-label" for="chkRequest">
|
||||
Requests
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<div class="form-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
id="chkResponse"
|
||||
/>
|
||||
<label class="form-check-label" for="chkResponse">
|
||||
Responses
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary" id="btnFilter">
|
||||
Refresh
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<button
|
||||
id="chatSettingsDropDown"
|
||||
type="button"
|
||||
class="btn btn-outline-secondary dropdown-toggle"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
title="More options...."
|
||||
>
|
||||
<i class="bi bi-three-dots-vertical"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="chatSettingsDropDown">
|
||||
<li>
|
||||
<a
|
||||
class="dropdown-item bg-danger text-white"
|
||||
id="delete_selected_chat"
|
||||
href="#"
|
||||
><i class="bi bi-person-x" style="font-size: 1rem"></i>
|
||||
Delete chat</a
|
||||
>
|
||||
</li>
|
||||
<div class="dropdown-divider"></div>
|
||||
<li>
|
||||
<button
|
||||
class="dropdown-item"
|
||||
id="openHelpModalchat"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#chatHelpModal"
|
||||
>
|
||||
<i
|
||||
class="bi bi-question-circle"
|
||||
style="font-size: 1rem"
|
||||
></i>
|
||||
Help
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<span
|
||||
class="input-group-text ms-2"
|
||||
id="txtConnectedWithChat"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
title="Connected with"
|
||||
>------</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="m-0" />
|
||||
<! ------messages area ---------------------------------------------------------------------->
|
||||
<div
|
||||
class="container overflow-auto"
|
||||
id="message-container"
|
||||
style="height: calc(100% - 150px)"
|
||||
>
|
||||
<div class="tab-content" id="nav-tabContent"></div>
|
||||
<!--<div class="container position-absolute bottom-0">-->
|
||||
</div>
|
||||
<!-- </div>-->
|
||||
<div class="container-fluid mt-2 p-0">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="expand_textarea"
|
||||
class="btn-check"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<label
|
||||
class="btn d-flex justify-content-center"
|
||||
id="expand_textarea_label"
|
||||
for="expand_textarea"
|
||||
><i
|
||||
id="expand_textarea_button"
|
||||
class="bi bi-chevron-compact-up"
|
||||
></i
|
||||
></label>
|
||||
|
||||
<div class="input-group bottom-0 ms-2">
|
||||
<!--<input class="form-control" maxlength="8" style="max-width: 6rem; text-transform:uppercase; display:none" id="chatModuleDxCall" placeholder="DX CALL"></input>-->
|
||||
<!--<button class="btn btn-sm btn-primary me-2" id="emojipickerbutton" type="button">-->
|
||||
<div class="input-group-text">
|
||||
<i
|
||||
id="emojipickerbutton"
|
||||
class="bi bi-emoji-smile p-0"
|
||||
style="font-size: 1rem"
|
||||
></i>
|
||||
</div>
|
||||
|
||||
<textarea
|
||||
class="form-control"
|
||||
rows="1"
|
||||
id="chatModuleMessage"
|
||||
placeholder="Message - Send with [Enter]"
|
||||
></textarea>
|
||||
|
||||
<div class="input-group-text me-3">
|
||||
<i
|
||||
class="bi bi-paperclip"
|
||||
style="font-size: 1rem"
|
||||
id="selectFilesButton"
|
||||
></i>
|
||||
|
||||
<button
|
||||
class="btn btn-sm btn-secondary d-none invisible"
|
||||
id="sendMessage"
|
||||
type="button"
|
||||
>
|
||||
<i class="bi bi-send" style="font-size: 1.2rem"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- user modal -->
|
||||
|
||||
<div
|
||||
class="modal fade"
|
||||
id="userModal"
|
||||
tabindex="-1"
|
||||
aria-labelledby="userModalLabel"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="modal-dialog" style="max-width: 600px">
|
||||
<div class="modal-content">
|
||||
<div class="card mb-1 border-0">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-4">
|
||||
<div class="row position-relative p-0 m-0">
|
||||
<div class="col p-0 m-0">
|
||||
<img
|
||||
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgZmlsbD0iY3VycmVudENvbG9yIiBjbGFzcz0iYmkgYmktcGVyc29uLWJvdW5kaW5nLWJveCIgdmlld0JveD0iMCAwIDE2IDE2Ij4KICA8cGF0aCBkPSJNMS41IDFhLjUuNSAwIDAgMC0uNS41djNhLjUuNSAwIDAgMS0xIDB2LTNBMS41IDEuNSAwIDAgMSAxLjUgMGgzYS41LjUgMCAwIDEgMCAxaC0zek0xMSAuNWEuNS41IDAgMCAxIC41LS41aDNBMS41IDEuNSAwIDAgMSAxNiAxLjV2M2EuNS41IDAgMCAxLTEgMHYtM2EuNS41IDAgMCAwLS41LS41aC0zYS41LjUgMCAwIDEtLjUtLjV6TS41IDExYS41LjUgMCAwIDEgLjUuNXYzYS41LjUgMCAwIDAgLjUuNWgzYS41LjUgMCAwIDEgMCAxaC0zQTEuNSAxLjUgMCAwIDEgMCAxNC41di0zYS41LjUgMCAwIDEgLjUtLjV6bTE1IDBhLjUuNSAwIDAgMSAuNS41djNhMS41IDEuNSAwIDAgMS0xLjUgMS41aC0zYS41LjUgMCAwIDEgMC0xaDNhLjUuNSAwIDAgMCAuNS0uNXYtM2EuNS41IDAgMCAxIC41LS41eiIvPgogIDxwYXRoIGQ9Ik0zIDE0cy0xIDAtMS0xIDEtNCA2LTQgNiAzIDYgNC0xIDEtMSAxSDN6bTgtOWEzIDMgMCAxIDEtNiAwIDMgMyAwIDAgMSA2IDB6Ii8+Cjwvc3ZnPg=="
|
||||
class="img-fluid rounded-start w-100"
|
||||
alt="..."
|
||||
id="user_info_image"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="col position-absolute image-overlay text-white justify-content-center align-items-center d-flex align-middle h-100 opacity-0"
|
||||
id="userImageSelector"
|
||||
>
|
||||
<i class="bi bi-upload" style="font-size: 2.2rem"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="card-body">
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-pass"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Callsign"
|
||||
id="user_info_callsign"
|
||||
aria-label="Call"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-person-vcard"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="name"
|
||||
id="user_info_name"
|
||||
aria-label="Name"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-sunrise"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="age"
|
||||
id="user_info_age"
|
||||
aria-label="age"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-house"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Location"
|
||||
id="user_info_location"
|
||||
aria-label="Name"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-pin-map"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Grid"
|
||||
id="user_info_gridsquare"
|
||||
aria-label="Name"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-projector"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Radio"
|
||||
id="user_info_radio"
|
||||
aria-label="Name"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-broadcast-pin"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Antenna"
|
||||
id="user_info_antenna"
|
||||
aria-label="Name"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-envelope"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Email"
|
||||
id="user_info_email"
|
||||
aria-label="Name"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-globe"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Website"
|
||||
id="user_info_website"
|
||||
aria-label="Name"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-info-circle"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Comments"
|
||||
id="user_info_comments"
|
||||
aria-label="Comments"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
id="userInfoSave"
|
||||
>
|
||||
Save & Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- dx user modal -->
|
||||
<div
|
||||
class="modal fade"
|
||||
id="userModalDX"
|
||||
tabindex="-1"
|
||||
aria-labelledby="userModalDXLabel"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="modal-dialog" style="max-width: 600px">
|
||||
<div class="modal-content">
|
||||
<div class="card mb-1 border-0">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-4">
|
||||
<img
|
||||
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgZmlsbD0iY3VycmVudENvbG9yIiBjbGFzcz0iYmkgYmktcGVyc29uLWJvdW5kaW5nLWJveCIgdmlld0JveD0iMCAwIDE2IDE2Ij4KICA8cGF0aCBkPSJNMS41IDFhLjUuNSAwIDAgMC0uNS41djNhLjUuNSAwIDAgMS0xIDB2LTNBMS41IDEuNSAwIDAgMSAxLjUgMGgzYS41LjUgMCAwIDEgMCAxaC0zek0xMSAuNWEuNS41IDAgMCAxIC41LS41aDNBMS41IDEuNSAwIDAgMSAxNiAxLjV2M2EuNS41IDAgMCAxLTEgMHYtM2EuNS41IDAgMCAwLS41LS41aC0zYS41LjUgMCAwIDEtLjUtLjV6TS41IDExYS41LjUgMCAwIDEgLjUuNXYzYS41LjUgMCAwIDAgLjUuNWgzYS41LjUgMCAwIDEgMCAxaC0zQTEuNSAxLjUgMCAwIDEgMCAxNC41di0zYS41LjUgMCAwIDEgLjUtLjV6bTE1IDBhLjUuNSAwIDAgMSAuNS41djNhMS41IDEuNSAwIDAgMS0xLjUgMS41aC0zYS41LjUgMCAwIDEgMC0xaDNhLjUuNSAwIDAgMCAuNS0uNXYtM2EuNS41IDAgMCAxIC41LS41eiIvPgogIDxwYXRoIGQ9Ik0zIDE0cy0xIDAtMS0xIDEtNCA2LTQgNiAzIDYgNC0xIDEtMSAxSDN6bTgtOWEzIDMgMCAxIDEtNiAwIDMgMyAwIDAgMSA2IDB6Ii8+Cjwvc3ZnPg=="
|
||||
class="img-fluid rounded-start w-100"
|
||||
alt="..."
|
||||
id="dx_user_info_image"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="card-body">
|
||||
<h5>
|
||||
<span
|
||||
class="badge bg-secondary"
|
||||
id="dx_user_info_callsign"
|
||||
></span>
|
||||
-
|
||||
<span
|
||||
class="badge bg-secondary"
|
||||
id="dx_user_info_name"
|
||||
></span>
|
||||
<span
|
||||
class="badge bg-secondary"
|
||||
id="dx_user_info_age"
|
||||
></span>
|
||||
</h5>
|
||||
|
||||
<ul class="card-text list-unstyled">
|
||||
<li>
|
||||
<strong class="col"><i class="bi bi-house"></i> </strong
|
||||
><span id="dx_user_info_location"></span> (<span
|
||||
id="dx_user_info_gridsquare"
|
||||
></span
|
||||
>)
|
||||
</li>
|
||||
<li>
|
||||
<strong class="col"
|
||||
><i class="bi bi-envelope"></i> </strong
|
||||
><span id="dx_user_info_email"></span>
|
||||
</li>
|
||||
<li>
|
||||
<strong class="col"><i class="bi bi-globe"></i> </strong
|
||||
><span id="dx_user_info_website"></span>
|
||||
</li>
|
||||
<li>
|
||||
<strong class="col"
|
||||
><i class="bi bi-broadcast-pin"></i> </strong
|
||||
><span id="dx_user_info_antenna"></span>
|
||||
</li>
|
||||
<li>
|
||||
<strong class="col"
|
||||
><i class="bi bi-projector"></i> </strong
|
||||
><span id="dx_user_info_radio"></span>
|
||||
</li>
|
||||
<li>
|
||||
<strong class="col"
|
||||
><i class="bi bi-info-circle"></i> </strong
|
||||
><span id="dx_user_info_comments"></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm m-0 p-0">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-warning w-75"
|
||||
aria-label="Request"
|
||||
id="requestUserInfo"
|
||||
>
|
||||
Request user data (about 20kBytes!)
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary w-25"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- user shared folder -->
|
||||
<div
|
||||
class="modal fade"
|
||||
id="sharedFolderModal"
|
||||
tabindex="-1"
|
||||
aria-labelledby="sharedFolderModalLabel"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="modal-dialog modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="sharedFolderModalLabel">
|
||||
My Shared folder
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
id="openSharedFilesFolder"
|
||||
>
|
||||
<i class="bi bi-archive"></i>
|
||||
</button>
|
||||
</h1>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
></button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div class="container-fluid p-0">
|
||||
<div class="center mb-1">
|
||||
<div class="badge text-bg-info">
|
||||
<i class="bi bi-info"></i> Change folder in settings!
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<!-- START OF TABLE FOR SHARED FOLDER -->
|
||||
<table
|
||||
class="table table-sm table-hover table-bordered align-middle"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Type</th>
|
||||
<th scope="col">Size</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="sharedFolderTable"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- HELP MODAL -->
|
||||
<div
|
||||
class="modal fade"
|
||||
data-bs-backdrop="static"
|
||||
tabindex="-1"
|
||||
id="chatHelpModal"
|
||||
>
|
||||
<div class="modal-dialog modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Chat Help</h5>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<p class="card-text">
|
||||
Welcome to the chat window. Heard stations are listed in the
|
||||
list on the left. Clicking on a station will show messages
|
||||
sent and/or received from the selected station. Additional
|
||||
help is available on various extra features below.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<button type="button" class="btn btn-sm btn-primary ms-2">
|
||||
<i class="bi bi-person" style="font-size: 1.2rem"></i>
|
||||
</button>
|
||||
<p class="card-text">
|
||||
Set your station information and picture. This information can
|
||||
be requested by a remote station and can be enabled/disabled
|
||||
via settings.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline-secondary ms-2"
|
||||
>
|
||||
<i class="bi bi-person" style="font-size: 1.2rem"></i>
|
||||
</button>
|
||||
<p class="card-text">
|
||||
Request the selected station's information.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline-secondary ms-2"
|
||||
>
|
||||
<i class="bi bi-files" style="font-size: 1.2rem"></i>
|
||||
</button>
|
||||
<p class="card-text">
|
||||
Request the selected station's shared file(s) list. Clicking
|
||||
<button type="button" class="btn btn-sm btn-primary ms-2">
|
||||
<i class="bi bi-files" style="font-size: 1.2rem"></i>
|
||||
</button>
|
||||
will allow you to preview your shared files. Shared file can
|
||||
be enabled/disabled in settings.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-small btn-outline-primary dropdown-toggle me-2"
|
||||
>
|
||||
<i class="bi bi-funnel-fill"></i>
|
||||
</button>
|
||||
<p class="card-text">
|
||||
The filter button allows you to show or hide certain types of
|
||||
messages. A lot of data is logged and this allows you to
|
||||
modify what is shown. By default sent and received messages
|
||||
and ping acknowlegements are displayed.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- dx user shared folder -->
|
||||
<div
|
||||
class="modal fade"
|
||||
id="sharedFolderModalDX"
|
||||
tabindex="-1"
|
||||
aria-labelledby="sharedFolderModalDXLabel"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="modal-dialog modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="sharedFolderModalDXLabel">
|
||||
Shared folder
|
||||
</h1>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary m-2"
|
||||
aria-label="Request"
|
||||
id="requestSharedFolderList"
|
||||
>
|
||||
<i class="bi bi-arrow-repeat"></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="container-fluid">
|
||||
<div class="table-responsive">
|
||||
<!-- START OF TABLE FOR SHARED FOLDER DX -->
|
||||
<table
|
||||
class="table table-sm table-hover table-bordered align-middle"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Type</th>
|
||||
<th scope="col">Size</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="sharedFolderTableDX"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="input-group input-group-sm m-0 p-0"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
523
gui/src/components/chat.vue
Normal file
523
gui/src/components/chat.vue
Normal file
|
@ -0,0 +1,523 @@
|
|||
<script setup lang="ts">
|
||||
import chat_navbar from "./chat_navbar.vue";
|
||||
import chat_conversations from "./chat_conversations.vue";
|
||||
import chat_messages from "./chat_messages.vue";
|
||||
import chat_new_message from "./chat_new_message.vue";
|
||||
|
||||
//updateAllChat();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container-fluid m-0 p-0">
|
||||
<!------ chat navbar ---------------------------------------------------------------------->
|
||||
|
||||
<chat_navbar />
|
||||
|
||||
<div class="row h-100 ms-0 mt-0 me-1">
|
||||
<div class="col-3 m-0 p-0 h-100 bg-light">
|
||||
<!------Chats area ---------------------------------------------------------------------->
|
||||
<div class="container-fluid vh-100 overflow-scroll m-0 p-0">
|
||||
<chat_conversations />
|
||||
</div>
|
||||
<div class="h-100">
|
||||
<div
|
||||
class="list-group overflow-auto"
|
||||
id="list-tab-chat"
|
||||
role="tablist"
|
||||
style="height: calc(100vh - 70px)"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-9 border-start vh-100 p-0">
|
||||
<!------messages area ---------------------------------------------------------------------->
|
||||
<div
|
||||
class="container overflow-auto"
|
||||
id="message-container"
|
||||
style="height: calc(100% - 200px)"
|
||||
>
|
||||
<chat_messages />
|
||||
</div>
|
||||
|
||||
<!------ new message area ---------------------------------------------------------------------->
|
||||
|
||||
<chat_new_message />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- user modal -->
|
||||
|
||||
<div
|
||||
class="modal fade"
|
||||
id="userModal"
|
||||
tabindex="-1"
|
||||
aria-labelledby="userModalLabel"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="modal-dialog" style="max-width: 600px">
|
||||
<div class="modal-content">
|
||||
<div class="card mb-1 border-0">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-4">
|
||||
<div class="row position-relative p-0 m-0">
|
||||
<div class="col p-0 m-0">
|
||||
<img
|
||||
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgZmlsbD0iY3VycmVudENvbG9yIiBjbGFzcz0iYmkgYmktcGVyc29uLWJvdW5kaW5nLWJveCIgdmlld0JveD0iMCAwIDE2IDE2Ij4KICA8cGF0aCBkPSJNMS41IDFhLjUuNSAwIDAgMC0uNS41djNhLjUuNSAwIDAgMS0xIDB2LTNBMS41IDEuNSAwIDAgMSAxLjUgMGgzYS41LjUgMCAwIDEgMCAxaC0zek0xMSAuNWEuNS41IDAgMCAxIC41LS41aDNBMS41IDEuNSAwIDAgMSAxNiAxLjV2M2EuNS41IDAgMCAxLTEgMHYtM2EuNS41IDAgMCAwLS41LS41aC0zYS41LjUgMCAwIDEtLjUtLjV6TS41IDExYS41LjUgMCAwIDEgLjUuNXYzYS41LjUgMCAwIDAgLjUuNWgzYS41LjUgMCAwIDEgMCAxaC0zQTEuNSAxLjUgMCAwIDEgMCAxNC41di0zYS41LjUgMCAwIDEgLjUtLjV6bTE1IDBhLjUuNSAwIDAgMSAuNS41djNhMS41IDEuNSAwIDAgMS0xLjUgMS41aC0zYS41LjUgMCAwIDEgMC0xaDNhLjUuNSAwIDAgMCAuNS0uNXYtM2EuNS41IDAgMCAxIC41LS41eiIvPgogIDxwYXRoIGQ9Ik0zIDE0cy0xIDAtMS0xIDEtNCA2LTQgNiAzIDYgNC0xIDEtMSAxSDN6bTgtOWEzIDMgMCAxIDEtNiAwIDMgMyAwIDAgMSA2IDB6Ii8+Cjwvc3ZnPg=="
|
||||
class="img-fluid rounded-start w-100"
|
||||
alt="..."
|
||||
id="user_info_image"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="col position-absolute image-overlay text-white justify-content-center align-items-center d-flex align-middle h-100 opacity-0"
|
||||
id="userImageSelector"
|
||||
>
|
||||
<i class="bi bi-upload" style="font-size: 2.2rem"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="card-body">
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-pass"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Callsign"
|
||||
id="user_info_callsign"
|
||||
aria-label="Call"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-person-vcard"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="name"
|
||||
id="user_info_name"
|
||||
aria-label="Name"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-sunrise"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="age"
|
||||
id="user_info_age"
|
||||
aria-label="age"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-house"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Location"
|
||||
id="user_info_location"
|
||||
aria-label="Name"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-pin-map"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Grid"
|
||||
id="user_info_gridsquare"
|
||||
aria-label="Name"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-projector"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Radio"
|
||||
id="user_info_radio"
|
||||
aria-label="Name"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-broadcast-pin"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Antenna"
|
||||
id="user_info_antenna"
|
||||
aria-label="Name"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-envelope"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Email"
|
||||
id="user_info_email"
|
||||
aria-label="Name"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-globe"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Website"
|
||||
id="user_info_website"
|
||||
aria-label="Name"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-info-circle"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Comments"
|
||||
id="user_info_comments"
|
||||
aria-label="Comments"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
id="userInfoSave"
|
||||
>
|
||||
Save & Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- dx user modal -->
|
||||
<div
|
||||
class="modal fade"
|
||||
id="userModalDX"
|
||||
tabindex="-1"
|
||||
aria-labelledby="userModalDXLabel"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="modal-dialog" style="max-width: 600px">
|
||||
<div class="modal-content">
|
||||
<div class="card mb-1 border-0">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-4">
|
||||
<img
|
||||
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgZmlsbD0iY3VycmVudENvbG9yIiBjbGFzcz0iYmkgYmktcGVyc29uLWJvdW5kaW5nLWJveCIgdmlld0JveD0iMCAwIDE2IDE2Ij4KICA8cGF0aCBkPSJNMS41IDFhLjUuNSAwIDAgMC0uNS41djNhLjUuNSAwIDAgMS0xIDB2LTNBMS41IDEuNSAwIDAgMSAxLjUgMGgzYS41LjUgMCAwIDEgMCAxaC0zek0xMSAuNWEuNS41IDAgMCAxIC41LS41aDNBMS41IDEuNSAwIDAgMSAxNiAxLjV2M2EuNS41IDAgMCAxLTEgMHYtM2EuNS41IDAgMCAwLS41LS41aC0zYS41LjUgMCAwIDEtLjUtLjV6TS41IDExYS41LjUgMCAwIDEgLjUuNXYzYS41LjUgMCAwIDAgLjUuNWgzYS41LjUgMCAwIDEgMCAxaC0zQTEuNSAxLjUgMCAwIDEgMCAxNC41di0zYS41LjUgMCAwIDEgLjUtLjV6bTE1IDBhLjUuNSAwIDAgMSAuNS41djNhMS41IDEuNSAwIDAgMS0xLjUgMS41aC0zYS41LjUgMCAwIDEgMC0xaDNhLjUuNSAwIDAgMCAuNS0uNXYtM2EuNS41IDAgMCAxIC41LS41eiIvPgogIDxwYXRoIGQ9Ik0zIDE0cy0xIDAtMS0xIDEtNCA2LTQgNiAzIDYgNC0xIDEtMSAxSDN6bTgtOWEzIDMgMCAxIDEtNiAwIDMgMyAwIDAgMSA2IDB6Ii8+Cjwvc3ZnPg=="
|
||||
class="img-fluid rounded-start w-100"
|
||||
alt="..."
|
||||
id="dx_user_info_image"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="card-body">
|
||||
<h5>
|
||||
<span
|
||||
class="badge bg-secondary"
|
||||
id="dx_user_info_callsign"
|
||||
></span>
|
||||
-
|
||||
<span
|
||||
class="badge bg-secondary"
|
||||
id="dx_user_info_name"
|
||||
></span>
|
||||
<span class="badge bg-secondary" id="dx_user_info_age"></span>
|
||||
</h5>
|
||||
|
||||
<ul class="card-text list-unstyled">
|
||||
<li>
|
||||
<strong class="col"><i class="bi bi-house"></i> </strong
|
||||
><span id="dx_user_info_location"></span> (<span
|
||||
id="dx_user_info_gridsquare"
|
||||
></span
|
||||
>)
|
||||
</li>
|
||||
<li>
|
||||
<strong class="col"><i class="bi bi-envelope"></i> </strong
|
||||
><span id="dx_user_info_email"></span>
|
||||
</li>
|
||||
<li>
|
||||
<strong class="col"><i class="bi bi-globe"></i> </strong
|
||||
><span id="dx_user_info_website"></span>
|
||||
</li>
|
||||
<li>
|
||||
<strong class="col"
|
||||
><i class="bi bi-broadcast-pin"></i> </strong
|
||||
><span id="dx_user_info_antenna"></span>
|
||||
</li>
|
||||
<li>
|
||||
<strong class="col"><i class="bi bi-projector"></i> </strong
|
||||
><span id="dx_user_info_radio"></span>
|
||||
</li>
|
||||
<li>
|
||||
<strong class="col"
|
||||
><i class="bi bi-info-circle"></i> </strong
|
||||
><span id="dx_user_info_comments"></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm m-0 p-0">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-warning w-75"
|
||||
aria-label="Request"
|
||||
id="requestUserInfo"
|
||||
>
|
||||
Request user data (about 20kBytes!)
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary w-25"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- user shared folder -->
|
||||
<div
|
||||
class="modal fade"
|
||||
id="sharedFolderModal"
|
||||
tabindex="-1"
|
||||
aria-labelledby="sharedFolderModalLabel"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="modal-dialog modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="sharedFolderModalLabel">
|
||||
My Shared folder
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
id="openSharedFilesFolder"
|
||||
>
|
||||
<i class="bi bi-archive"></i>
|
||||
</button>
|
||||
</h1>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
></button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div class="container-fluid p-0">
|
||||
<div class="center mb-1">
|
||||
<div class="badge text-bg-info">
|
||||
<i class="bi bi-info"></i> Change folder in settings!
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<!-- START OF TABLE FOR SHARED FOLDER -->
|
||||
<table
|
||||
class="table table-sm table-hover table-bordered align-middle"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Type</th>
|
||||
<th scope="col">Size</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="sharedFolderTable"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- HELP MODAL -->
|
||||
<div
|
||||
class="modal fade"
|
||||
data-bs-backdrop="static"
|
||||
tabindex="-1"
|
||||
id="chatHelpModal"
|
||||
>
|
||||
<div class="modal-dialog modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Chat Help</h5>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<p class="card-text">
|
||||
Welcome to the chat window. Heard stations are listed in the
|
||||
list on the left. Clicking on a station will show messages sent
|
||||
and/or received from the selected station. Additional help is
|
||||
available on various extra features below.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<button type="button" class="btn btn-sm btn-primary ms-2">
|
||||
<i class="bi bi-person" style="font-size: 1.2rem"></i>
|
||||
</button>
|
||||
<p class="card-text">
|
||||
Set your station information and picture. This information can
|
||||
be requested by a remote station and can be enabled/disabled via
|
||||
settings.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline-secondary ms-2"
|
||||
>
|
||||
<i class="bi bi-person" style="font-size: 1.2rem"></i>
|
||||
</button>
|
||||
<p class="card-text">
|
||||
Request the selected station's information.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline-secondary ms-2"
|
||||
>
|
||||
<i class="bi bi-files" style="font-size: 1.2rem"></i>
|
||||
</button>
|
||||
<p class="card-text">
|
||||
Request the selected station's shared file(s) list. Clicking
|
||||
<button type="button" class="btn btn-sm btn-primary ms-2">
|
||||
<i class="bi bi-files" style="font-size: 1.2rem"></i>
|
||||
</button>
|
||||
will allow you to preview your shared files. Shared file can be
|
||||
enabled/disabled in settings.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-small btn-outline-primary dropdown-toggle me-2"
|
||||
>
|
||||
<i class="bi bi-funnel-fill"></i>
|
||||
</button>
|
||||
<p class="card-text">
|
||||
The filter button allows you to show or hide certain types of
|
||||
messages. A lot of data is logged and this allows you to modify
|
||||
what is shown. By default sent and received messages and ping
|
||||
acknowlegements are displayed.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- dx user shared folder -->
|
||||
<div
|
||||
class="modal fade"
|
||||
id="sharedFolderModalDX"
|
||||
tabindex="-1"
|
||||
aria-labelledby="sharedFolderModalDXLabel"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="modal-dialog modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="sharedFolderModalDXLabel">
|
||||
Shared folder
|
||||
</h1>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary m-2"
|
||||
aria-label="Request"
|
||||
id="requestSharedFolderList"
|
||||
>
|
||||
<i class="bi bi-arrow-repeat"></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="container-fluid">
|
||||
<div class="table-responsive">
|
||||
<!-- START OF TABLE FOR SHARED FOLDER DX -->
|
||||
<table
|
||||
class="table table-sm table-hover table-bordered align-middle"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Type</th>
|
||||
<th scope="col">Size</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="sharedFolderTableDX"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="input-group input-group-sm m-0 p-0"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
84
gui/src/components/chat_conversations.vue
Normal file
84
gui/src/components/chat_conversations.vue
Normal file
|
@ -0,0 +1,84 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useChatStore } from "../store/chatStore.js";
|
||||
const chat = useChatStore(pinia);
|
||||
|
||||
import {
|
||||
getNewMessagesByDXCallsign,
|
||||
resetIsNewMessage,
|
||||
} from "../js/chatHandler";
|
||||
|
||||
function chatSelected(callsign) {
|
||||
chat.selectedCallsign = callsign.toUpperCase();
|
||||
|
||||
// scroll message container to bottom
|
||||
var messageBody = document.getElementById("message-container");
|
||||
if (messageBody != null) {
|
||||
// needs sensible defaults
|
||||
messageBody.scrollTop = messageBody.scrollHeight - messageBody.clientHeight;
|
||||
}
|
||||
|
||||
if (getNewMessagesByDXCallsign(callsign)[1] > 0) {
|
||||
let messageArray = getNewMessagesByDXCallsign(callsign)[2];
|
||||
console.log(messageArray);
|
||||
|
||||
for (const key in messageArray) {
|
||||
resetIsNewMessage(messageArray[key].uuid, false);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
chat.beaconLabelArray = Object.values(
|
||||
chat.sorted_beacon_list[chat.selectedCallsign].timestamp,
|
||||
);
|
||||
chat.beaconDataArray = Object.values(
|
||||
chat.sorted_beacon_list[chat.selectedCallsign].snr,
|
||||
);
|
||||
} catch (e) {
|
||||
console.log("beacon data not fetched: " + e);
|
||||
chat.beaconLabelArray = [];
|
||||
chat.beaconDataArray = [];
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="list-group m-0 p-0" id="chat-list-tab" role="chat-tablist">
|
||||
<template v-for="(item, key) in chat.callsign_list" :key="item.dxcallsign">
|
||||
<a
|
||||
class="list-group-item list-group-item-action border-0 border-bottom rounded-0"
|
||||
:class="{ active: key == 0 }"
|
||||
:id="`list-chat-list-${item}`"
|
||||
data-bs-toggle="list"
|
||||
:href="`#list-${item}-messages`"
|
||||
role="tab"
|
||||
aria-controls="list-{{item}}-messages"
|
||||
@click="chatSelected(item)"
|
||||
>
|
||||
<div class="row">
|
||||
<div class="col-9">
|
||||
{{ item }}
|
||||
<span
|
||||
class="badge rounded-pill bg-danger"
|
||||
v-if="getNewMessagesByDXCallsign(item)[1] > 0"
|
||||
>
|
||||
{{ getNewMessagesByDXCallsign(item)[1] }} new messages
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<button
|
||||
class="btn btn-sm btn-outline-secondary ms-2 border-0"
|
||||
data-bs-target="#deleteChatModal"
|
||||
data-bs-toggle="modal"
|
||||
@click="chatSelected(item)"
|
||||
>
|
||||
<i class="bi bi-three-dots-vertical"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
102
gui/src/components/chat_messages.vue
Normal file
102
gui/src/components/chat_messages.vue
Normal file
|
@ -0,0 +1,102 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useChatStore } from "../store/chatStore.js";
|
||||
const chat = useChatStore(pinia);
|
||||
|
||||
import SentMessage from "./chat_messages_sent.vue"; // Import the chat_messages_sent component
|
||||
import ReceivedMessage from "./chat_messages_received.vue"; // Import the chat_messages_sent component
|
||||
import ReceivedBroadcastMessage from "./chat_messages_broadcast_received.vue"; // Import the chat_messages_sent component for broadcasts
|
||||
import SentBroadcastMessage from "./chat_messages_broadcast_sent.vue"; // Import the chat_messages_sent component for broadcasts
|
||||
|
||||
//helper function for saving the last messages day for disaplying the day based divider
|
||||
var prevChatMessageDay = "";
|
||||
|
||||
function getDateTime(timestampRaw) {
|
||||
var datetime = new Date(timestampRaw * 1000).toLocaleString(
|
||||
navigator.language,
|
||||
{
|
||||
hourCycle: "h23",
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
},
|
||||
);
|
||||
return datetime;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="tab-content" id="nav-tabContent-chat-messages">
|
||||
<template v-for="(callsign, key) in chat.callsign_list">
|
||||
<div
|
||||
class="tab-pane fade show"
|
||||
:class="{ active: key == 0 }"
|
||||
:id="`list-${callsign}-messages`"
|
||||
role="tabpanel"
|
||||
:aria-labelledby="`list-chat-list-${callsign}`"
|
||||
>
|
||||
<template
|
||||
v-for="item in chat.sorted_chat_list[callsign]"
|
||||
:key="item._id"
|
||||
>
|
||||
<div v-if="prevChatMessageDay !== getDateTime(item.timestamp)">
|
||||
<div class="separator my-2">
|
||||
{{ (prevChatMessageDay = getDateTime(item.timestamp)) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="item.type === 'beacon' && item.status === 'received'">
|
||||
<!-- {{ item }} -->
|
||||
</div>
|
||||
|
||||
<div v-if="item.type === 'ping'">{{ item.snr }} dB ping received</div>
|
||||
|
||||
<div v-if="item.type === 'ping-ack'">
|
||||
{{ item.snr }} dB ping-ack received
|
||||
</div>
|
||||
|
||||
<div v-if="item.type === 'transmit'">
|
||||
<sent-message :message="item" />
|
||||
</div>
|
||||
<div v-else-if="item.type === 'received'">
|
||||
<received-message :message="item" />
|
||||
</div>
|
||||
<div v-if="item.type === 'broadcast_transmit'">
|
||||
<sent-broadcast-message :message="item" />
|
||||
</div>
|
||||
<div v-else-if="item.type === 'broadcast_received'">
|
||||
<received-broadcast-message :message="item" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
/* https://stackoverflow.com/a/26634224 */
|
||||
.separator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.separator::before,
|
||||
.separator::after {
|
||||
content: "";
|
||||
flex: 1;
|
||||
border-bottom: 1px solid #adb5bd;
|
||||
}
|
||||
|
||||
.separator:not(:empty)::before {
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
|
||||
.separator:not(:empty)::after {
|
||||
margin-left: 0.25em;
|
||||
}
|
||||
</style>
|
48
gui/src/components/chat_messages_action_menu.vue
Normal file
48
gui/src/components/chat_messages_action_menu.vue
Normal file
|
@ -0,0 +1,48 @@
|
|||
<template>
|
||||
<div class="message-actions-menu">
|
||||
<!-- Add your action buttons here (e.g., Delete, Copy, Quote) -->
|
||||
<button @click="onDelete">Delete</button>
|
||||
<button @click="onCopy">Copy</button>
|
||||
<button @click="onQuote">Quote</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
onDelete() {
|
||||
// Implement delete action
|
||||
this.$emit("delete");
|
||||
},
|
||||
onCopy() {
|
||||
// Implement copy action
|
||||
this.$emit("copy");
|
||||
},
|
||||
onQuote() {
|
||||
// Implement quote action
|
||||
this.$emit("quote");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Style the message actions menu as needed */
|
||||
.message-actions-menu {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
display: none; /* Initially hidden */
|
||||
/* Add styling for buttons and menu */
|
||||
}
|
||||
|
||||
/* Style individual action buttons */
|
||||
.message-actions-menu button {
|
||||
/* Add button styles here */
|
||||
}
|
||||
|
||||
/* Style menu display on hover */
|
||||
.card:hover .message-actions-menu {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
96
gui/src/components/chat_messages_broadcast_received.vue
Normal file
96
gui/src/components/chat_messages_broadcast_received.vue
Normal file
|
@ -0,0 +1,96 @@
|
|||
<template>
|
||||
<div class="row justify-content-start mb-2">
|
||||
<div :class="messageWidthClass">
|
||||
<div class="card bg-light border-0 text-dark">
|
||||
<div class="card-header" v-if="getFileContent['filesize'] !== 0">
|
||||
<p class="card-text">
|
||||
{{ getFileContent["filename"] }} |
|
||||
{{ getFileContent["filesize"] }} Bytes |
|
||||
{{ getFileContent["filetype"] }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<p class="card-text">{{ message.msg }}</p>
|
||||
</div>
|
||||
|
||||
<div class="card-footer p-0 bg-light border-top-0">
|
||||
<p class="text-muted p-0 m-0 me-1 text-end">{{ getDateTime }}</p>
|
||||
<!-- Display formatted timestamp in card-footer -->
|
||||
</div>
|
||||
|
||||
<span
|
||||
class="position-absolute top-0 start-100 translate-middle badge rounded-1 bg-secondary border border-white"
|
||||
>
|
||||
{{ message.broadcast_sender }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete button outside of the card -->
|
||||
<div class="col-auto">
|
||||
<button class="btn btn-outline-secondary border-0" @click="deleteMessage">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { deleteMessageFromDB } from "../js/chatHandler";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
message: Object,
|
||||
},
|
||||
computed: {
|
||||
getFileContent() {
|
||||
try {
|
||||
var filename = Object.keys(this.message._attachments)[0];
|
||||
var filesize = this.message._attachments[filename]["length"];
|
||||
var filetype = filename.split(".")[1];
|
||||
|
||||
// ensure filesize is 0 for hiding message header if no data is available
|
||||
if (
|
||||
typeof filename === "undefined" ||
|
||||
filename === "" ||
|
||||
filename === "-" ||
|
||||
filename === "null"
|
||||
) {
|
||||
filesize = 0;
|
||||
}
|
||||
|
||||
return { filename: filename, filesize: filesize, filetype: filetype };
|
||||
} catch (e) {
|
||||
console.log("file not loaded from database - empty?");
|
||||
// we are only checking against filesize for displaying attachments
|
||||
return { filesize: 0 };
|
||||
}
|
||||
},
|
||||
messageWidthClass() {
|
||||
// Calculate a Bootstrap grid class based on message length
|
||||
// Adjust the logic as needed to fit your requirements
|
||||
if (this.message.msg.length <= 50) {
|
||||
return "col-4";
|
||||
} else if (this.message.msg.length <= 100) {
|
||||
return "col-6";
|
||||
} else {
|
||||
return "col-9";
|
||||
}
|
||||
},
|
||||
deleteMessage() {
|
||||
deleteMessageFromDB(this.message._id);
|
||||
},
|
||||
getDateTime() {
|
||||
var datetime = new Date(this.message.timestamp * 1000).toLocaleString(
|
||||
navigator.language,
|
||||
{
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
},
|
||||
);
|
||||
return datetime;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
113
gui/src/components/chat_messages_broadcast_sent.vue
Normal file
113
gui/src/components/chat_messages_broadcast_sent.vue
Normal file
|
@ -0,0 +1,113 @@
|
|||
<template>
|
||||
<div class="row justify-content-end mb-2">
|
||||
<!-- control area -->
|
||||
<div class="col-auto p-0 m-0">
|
||||
<button
|
||||
class="btn btn-outline-secondary border-0 me-1"
|
||||
@click="repeatMessage"
|
||||
>
|
||||
<i class="bi bi-arrow-repeat"></i>
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary border-0" @click="deleteMessage">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- message area -->
|
||||
<div :class="messageWidthClass">
|
||||
<div class="card bg-primary text-white">
|
||||
<div class="card-header" v-if="getFileContent['filesize'] !== 0">
|
||||
<p class="card-text">
|
||||
{{ getFileContent["filename"] }} |
|
||||
{{ getFileContent["filesize"] }} Bytes |
|
||||
{{ getFileContent["filetype"] }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<p class="card-text">{{ message.msg }}</p>
|
||||
</div>
|
||||
|
||||
<div class="card-footer p-0 bg-primary border-top-0">
|
||||
<p class="text p-0 m-0 me-1 text-end">{{ getDateTime }}</p>
|
||||
<!-- Display formatted timestamp in card-footer -->
|
||||
</div>
|
||||
|
||||
<div class="card-footer p-0 border-top-0" v-if="message.percent < 100">
|
||||
<div class="progress bg-secondary" :style="{ height: '10px' }">
|
||||
<div
|
||||
class="progress-bar progress-bar-striped overflow-visible"
|
||||
role="progressbar"
|
||||
:style="{ width: message.percent + '%', height: '10px' }"
|
||||
:aria-valuenow="message.percent"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
>
|
||||
{{ message.percent }} % with {{ message.bytesperminute }} bpm
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
repeatMessageTransmission,
|
||||
deleteMessageFromDB,
|
||||
} from "../js/chatHandler";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
message: Object,
|
||||
},
|
||||
computed: {
|
||||
getFileContent() {
|
||||
var filename = Object.keys(this.message._attachments)[0];
|
||||
var filesize = this.message._attachments[filename]["length"];
|
||||
var filetype = filename.split(".")[1];
|
||||
|
||||
// ensure filesize is 0 for hiding message header if no data is available
|
||||
if (
|
||||
typeof filename === "undefined" ||
|
||||
filename === "" ||
|
||||
filename === "-" ||
|
||||
filename === "null"
|
||||
) {
|
||||
filesize = 0;
|
||||
}
|
||||
|
||||
return { filename: filename, filesize: filesize, filetype: filetype };
|
||||
},
|
||||
messageWidthClass() {
|
||||
// Calculate a Bootstrap grid class based on message length
|
||||
// Adjust the logic as needed to fit your requirements
|
||||
if (this.message.msg.length <= 50) {
|
||||
return "col-4";
|
||||
} else if (this.message.msg.length <= 100) {
|
||||
return "col-6";
|
||||
} else {
|
||||
return "col-9";
|
||||
}
|
||||
},
|
||||
repeatMessage() {
|
||||
repeatMessageTransmission(this.message._id);
|
||||
},
|
||||
deleteMessage() {
|
||||
deleteMessageFromDB(this.message._id);
|
||||
},
|
||||
|
||||
getDateTime() {
|
||||
var datetime = new Date(this.message.timestamp * 1000).toLocaleString(
|
||||
navigator.language,
|
||||
{
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
},
|
||||
);
|
||||
return datetime;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
135
gui/src/components/chat_messages_received.vue
Normal file
135
gui/src/components/chat_messages_received.vue
Normal file
|
@ -0,0 +1,135 @@
|
|||
<template>
|
||||
<div class="row justify-content-start mb-2">
|
||||
<div :class="messageWidthClass">
|
||||
<div class="card bg-light border-0 text-dark">
|
||||
<div class="card-header" v-if="getFileContent['filesize'] !== 0">
|
||||
<p class="card-text">
|
||||
{{ getFileContent["filename"] }} |
|
||||
{{ getFileContent["filesize"] }} Bytes |
|
||||
{{ getFileContent["filetype"] }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<p class="card-text">{{ message.msg }}</p>
|
||||
</div>
|
||||
|
||||
<div class="card-footer p-0 bg-light border-top-0">
|
||||
<p class="text-muted p-0 m-0 me-1 text-end">{{ getDateTime }}</p>
|
||||
<!-- Display formatted timestamp in card-footer -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete button outside of the card -->
|
||||
<div class="col-auto">
|
||||
<button
|
||||
class="btn btn-outline-secondary border-0 me-1"
|
||||
@click="showMessageInfo"
|
||||
data-bs-target="#messageInfoModal"
|
||||
data-bs-toggle="modal"
|
||||
>
|
||||
<i class="bi bi-info-circle"></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-if="getFileContent['filesize'] !== 0"
|
||||
class="btn btn-outline-secondary border-0 me-1"
|
||||
@click="downloadAttachment"
|
||||
>
|
||||
<i class="bi bi-download"></i>
|
||||
</button>
|
||||
|
||||
<button class="btn btn-outline-secondary border-0" @click="deleteMessage">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
deleteMessageFromDB,
|
||||
requestMessageInfo,
|
||||
getMessageAttachment,
|
||||
} from "../js/chatHandler";
|
||||
import { atob_FD } from "../js/freedata";
|
||||
|
||||
// pinia store setup
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
import { saveAs } from "file-saver";
|
||||
|
||||
import { useChatStore } from "../store/chatStore.js";
|
||||
const chat = useChatStore(pinia);
|
||||
|
||||
export default {
|
||||
props: {
|
||||
message: Object,
|
||||
},
|
||||
methods: {
|
||||
showMessageInfo() {
|
||||
requestMessageInfo(this.message._id);
|
||||
//let infoModal = Modal.getOrCreateInstance(document.getElementById('messageInfoModal'))
|
||||
//console.log(this.infoModal)
|
||||
//this.infoModal.show()
|
||||
},
|
||||
deleteMessage() {
|
||||
deleteMessageFromDB(this.message._id);
|
||||
},
|
||||
async downloadAttachment() {
|
||||
try {
|
||||
// reset file store
|
||||
chat.downloadFileFromDB = [];
|
||||
|
||||
const attachment = await getMessageAttachment(this.message._id);
|
||||
const blob = new Blob([atob_FD(attachment[2])], {
|
||||
type: `${attachment[1]};charset=utf-8`,
|
||||
});
|
||||
window.focus();
|
||||
saveAs(blob, attachment[0]);
|
||||
} catch (error) {
|
||||
console.error("Failed to download attachment:", error);
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
getFileContent() {
|
||||
try {
|
||||
var filename = Object.keys(this.message._attachments)[0];
|
||||
var filesize = this.message._attachments[filename]["length"];
|
||||
var filetype = filename.split(".")[1];
|
||||
|
||||
return { filename: filename, filesize: filesize, filetype: filetype };
|
||||
} catch (e) {
|
||||
console.log("file not loaded from database - empty?");
|
||||
// we are only checking against filesize for displaying attachments
|
||||
return { filesize: 0 };
|
||||
}
|
||||
},
|
||||
messageWidthClass() {
|
||||
// Calculate a Bootstrap grid class based on message length
|
||||
// Adjust the logic as needed to fit your requirements
|
||||
if (this.message.msg.length <= 50) {
|
||||
return "col-4";
|
||||
} else if (this.message.msg.length <= 100) {
|
||||
return "col-6";
|
||||
} else {
|
||||
return "col-9";
|
||||
}
|
||||
},
|
||||
|
||||
getDateTime() {
|
||||
var datetime = new Date(this.message.timestamp * 1000).toLocaleString(
|
||||
navigator.language,
|
||||
{
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
},
|
||||
);
|
||||
return datetime;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
171
gui/src/components/chat_messages_sent.vue
Normal file
171
gui/src/components/chat_messages_sent.vue
Normal file
|
@ -0,0 +1,171 @@
|
|||
<template>
|
||||
<div class="row justify-content-end mb-2">
|
||||
<!-- control area -->
|
||||
<div class="col-auto p-0 m-0">
|
||||
<button
|
||||
v-if="getFileContent['filesize'] !== 0"
|
||||
class="btn btn-outline-secondary border-0 me-1"
|
||||
@click="downloadAttachment"
|
||||
>
|
||||
<i class="bi bi-download"></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-outline-secondary border-0 me-1"
|
||||
@click="repeatMessage"
|
||||
>
|
||||
<i class="bi bi-arrow-repeat"></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-outline-secondary border-0 me-1"
|
||||
@click="showMessageInfo"
|
||||
data-bs-target="#messageInfoModal"
|
||||
data-bs-toggle="modal"
|
||||
>
|
||||
<i class="bi bi-info-circle"></i>
|
||||
</button>
|
||||
|
||||
<button class="btn btn-outline-secondary border-0" @click="deleteMessage">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- message area -->
|
||||
<div :class="messageWidthClass">
|
||||
<div class="card bg-primary text-white">
|
||||
<div class="card-header" v-if="getFileContent['filesize'] !== 0">
|
||||
<p class="card-text">
|
||||
{{ getFileContent["filename"] }} |
|
||||
{{ getFileContent["filesize"] }} Bytes |
|
||||
{{ getFileContent["filetype"] }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<p class="card-text">{{ message.msg }}</p>
|
||||
</div>
|
||||
|
||||
<div class="card-footer p-0 bg-primary border-top-0">
|
||||
<p class="text p-0 m-0 me-1 text-end">{{ getDateTime }}</p>
|
||||
<!-- Display formatted timestamp in card-footer -->
|
||||
</div>
|
||||
|
||||
<div class="card-footer p-0 border-top-0" v-if="message.percent < 100">
|
||||
<div
|
||||
class="progress bg-secondary rounded-0 rounded-bottom"
|
||||
:style="{ height: '10px' }"
|
||||
>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped overflow-visible"
|
||||
role="progressbar"
|
||||
:style="{ width: message.percent + '%', height: '10px' }"
|
||||
:aria-valuenow="message.percent"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
>
|
||||
{{ message.percent }} % with {{ message.bytesperminute }} bpm
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { atob_FD } from "../js/freedata";
|
||||
|
||||
import {
|
||||
repeatMessageTransmission,
|
||||
deleteMessageFromDB,
|
||||
requestMessageInfo,
|
||||
getMessageAttachment,
|
||||
} from "../js/chatHandler";
|
||||
|
||||
// pinia store setup
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
import { saveAs } from "file-saver";
|
||||
|
||||
import { useChatStore } from "../store/chatStore.js";
|
||||
const chat = useChatStore(pinia);
|
||||
|
||||
export default {
|
||||
props: {
|
||||
message: Object,
|
||||
},
|
||||
|
||||
methods: {
|
||||
repeatMessage() {
|
||||
repeatMessageTransmission(this.message._id);
|
||||
},
|
||||
|
||||
deleteMessage() {
|
||||
deleteMessageFromDB(this.message._id);
|
||||
},
|
||||
showMessageInfo() {
|
||||
console.log("requesting message info.....");
|
||||
requestMessageInfo(this.message._id);
|
||||
//let infoModal = Modal.getOrCreateInstance(document.getElementById('messageInfoModal'))
|
||||
//console.log(this.infoModal)
|
||||
//this.infoModal.show()
|
||||
},
|
||||
async downloadAttachment() {
|
||||
try {
|
||||
// reset file store
|
||||
chat.downloadFileFromDB = [];
|
||||
|
||||
const attachment = await getMessageAttachment(this.message._id);
|
||||
const blob = new Blob([atob_FD(attachment[2])], {
|
||||
type: `${attachment[1]};charset=utf-8`,
|
||||
});
|
||||
saveAs(blob, attachment[0]);
|
||||
} catch (error) {
|
||||
console.error("Failed to download attachment:", error);
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
getFileContent() {
|
||||
var filename = Object.keys(this.message._attachments)[0];
|
||||
var filesize = this.message._attachments[filename]["length"];
|
||||
var filetype = filename.split(".")[1];
|
||||
|
||||
// ensure filesize is 0 for hiding message header if no data is available
|
||||
if (
|
||||
typeof filename === "undefined" ||
|
||||
filename === "" ||
|
||||
filename === "-"
|
||||
) {
|
||||
filesize = 0;
|
||||
}
|
||||
|
||||
return { filename: filename, filesize: filesize, filetype: filetype };
|
||||
},
|
||||
messageWidthClass() {
|
||||
// Calculate a Bootstrap grid class based on message length
|
||||
// Adjust the logic as needed to fit your requirements
|
||||
if (this.message.msg.length <= 50) {
|
||||
return "col-4";
|
||||
} else if (this.message.msg.length <= 100) {
|
||||
return "col-6";
|
||||
} else {
|
||||
return "col-9";
|
||||
}
|
||||
},
|
||||
|
||||
getDateTime() {
|
||||
var datetime = new Date(this.message.timestamp * 1000).toLocaleString(
|
||||
navigator.language,
|
||||
{
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
},
|
||||
);
|
||||
return datetime;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
195
gui/src/components/chat_navbar.vue
Normal file
195
gui/src/components/chat_navbar.vue
Normal file
|
@ -0,0 +1,195 @@
|
|||
<script setup lang="ts">
|
||||
// @ts-nocheck
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
import { useChatStore } from "../store/chatStore.js";
|
||||
const chat = useChatStore(pinia);
|
||||
|
||||
import { getRxBuffer } from "../js/sock.js";
|
||||
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
BarElement,
|
||||
} from "chart.js";
|
||||
|
||||
import { Bar } from "vue-chartjs";
|
||||
import { ref, computed } from "vue";
|
||||
import annotationPlugin from "chartjs-plugin-annotation";
|
||||
|
||||
const newChatCall = ref(null);
|
||||
|
||||
ChartJS.register(
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
BarElement,
|
||||
annotationPlugin,
|
||||
);
|
||||
|
||||
var beaconHistogramOptions = {
|
||||
type: "bar",
|
||||
bezierCurve: false, //remove curves from your plot
|
||||
scaleShowLabels: false, //remove labels
|
||||
tooltipEvents: [], //remove trigger from tooltips so they will'nt be show
|
||||
pointDot: false, //remove the points markers
|
||||
scaleShowGridLines: true, //set to false to remove the grids background
|
||||
maintainAspectRatio: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
annotation: {
|
||||
annotations: [
|
||||
{
|
||||
type: "line",
|
||||
mode: "horizontal",
|
||||
scaleID: "y",
|
||||
value: 0,
|
||||
borderColor: "darkgrey", // Set the color to dark grey for the zero line
|
||||
borderWidth: 0.5, // Set the line width
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
scales: {
|
||||
x: {
|
||||
position: "bottom",
|
||||
display: false,
|
||||
min: -10,
|
||||
max: 15,
|
||||
ticks: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
y: {
|
||||
display: false,
|
||||
min: -5,
|
||||
max: 10,
|
||||
ticks: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
//let dataArray = new Array(25).fill(0)
|
||||
//dataArray = dataArray.add([-3, 10, 8, 5, 3, 0, -5])
|
||||
//let dataArray1 = dataArray.shift(2)
|
||||
//console.log(dataArray1)
|
||||
//[-3, 10, 8, 5, 3, 0, -5]
|
||||
|
||||
try {
|
||||
chat.beaconLabelArray = Object.values(
|
||||
chat.sorted_beacon_list["DJ2LS-0"].timestamp,
|
||||
);
|
||||
chat.beaconDataArray = Object.values(chat.sorted_beacon_list["DJ2LS-0"].snr);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
const beaconHistogramData = computed(() => ({
|
||||
labels: chat.beaconLabelArray,
|
||||
datasets: [
|
||||
{
|
||||
data: chat.beaconDataArray,
|
||||
tension: 0.1,
|
||||
borderColor: "rgb(0, 255, 0)",
|
||||
|
||||
backgroundColor: function (context) {
|
||||
var value = context.dataset.data[context.dataIndex];
|
||||
return value >= 0 ? "green" : "red";
|
||||
},
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
function newChat() {
|
||||
let callsign = this.newChatCall.value;
|
||||
callsign = callsign.toUpperCase();
|
||||
chat.callsign_list.add(callsign);
|
||||
this.newChatCall.value = "";
|
||||
}
|
||||
|
||||
function syncWithModem() {
|
||||
getRxBuffer();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav class="navbar bg-body-tertiary border-bottom">
|
||||
<div class="container">
|
||||
<div class="row w-100">
|
||||
<div class="col-3 p-0 me-2">
|
||||
<div class="input-group bottom-0 m-0 ms-1">
|
||||
<input
|
||||
class="form-control"
|
||||
maxlength="9"
|
||||
style="text-transform: uppercase"
|
||||
placeholder="callsign"
|
||||
@keypress.enter="newChat()"
|
||||
ref="newChatCall"
|
||||
/>
|
||||
<button
|
||||
class="btn btn-sm btn-outline-success"
|
||||
id="createNewChatButton"
|
||||
type="button"
|
||||
title="Start a new chat (enter dx call sign first)"
|
||||
@click="newChat()"
|
||||
>
|
||||
new chat
|
||||
<i class="bi bi-pencil-square" style="font-size: 1.2rem"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-5 ms-2 p-0">
|
||||
<!-- right side of chat nav bar-->
|
||||
|
||||
<div class="input-group mb-0 p-0 w-50">
|
||||
<button type="button" class="btn btn-outline-secondary" disabled>
|
||||
Beacons
|
||||
</button>
|
||||
|
||||
<div
|
||||
class="form-floating border border-secondary-subtle border-1 rounded-end"
|
||||
>
|
||||
<Bar
|
||||
:data="beaconHistogramData"
|
||||
:options="beaconHistogramOptions"
|
||||
width="300"
|
||||
height="50"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-2 ms-2 p-0">
|
||||
<div class="input-group mb-0 p-0">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
@click="syncWithModem()"
|
||||
>
|
||||
Modem Sync {{ state.rx_buffer_length }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
306
gui/src/components/chat_new_message.vue
Normal file
306
gui/src/components/chat_new_message.vue
Normal file
|
@ -0,0 +1,306 @@
|
|||
<script setup lang="ts">
|
||||
// @ts-nocheck
|
||||
|
||||
|
||||
import {saveSettingsToFile} from '../js/settingsHandler';
|
||||
|
||||
import { setActivePinia } from 'pinia';
|
||||
import pinia from '../store/index';
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useSettingsStore } from '../store/settingsStore.js';
|
||||
const settings = useSettingsStore(pinia);
|
||||
|
||||
import { useStateStore } from '../store/stateStore.js';
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
import { useChatStore } from '../store/chatStore.js';
|
||||
const chat = useChatStore(pinia);
|
||||
|
||||
|
||||
import chat_navbar from './chat_navbar.vue'
|
||||
import chat_conversations from './chat_conversations.vue'
|
||||
import chat_messages from './chat_messages.vue'
|
||||
|
||||
import {updateAllChat, newMessage, newBroadcast} from '../js/chatHandler'
|
||||
|
||||
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend
|
||||
} from 'chart.js'
|
||||
import { Line } from 'vue-chartjs'
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
|
||||
import { VuemojiPicker, EmojiClickEventDetail } from 'vuemoji-picker'
|
||||
|
||||
const handleEmojiClick = (detail: EmojiClickEventDetail) => {
|
||||
chat.inputText += detail.unicode
|
||||
|
||||
}
|
||||
|
||||
|
||||
const chatModuleMessage=ref(null);
|
||||
|
||||
|
||||
|
||||
|
||||
function transmitNewMessage(){
|
||||
|
||||
chat.inputText = chat.inputText.trim();
|
||||
if (chat.inputText.length==0)
|
||||
return;
|
||||
if (chat.selectedCallsign.startsWith("BC-")) {
|
||||
|
||||
newBroadcast(chat.selectedCallsign, chat.inputText)
|
||||
|
||||
} else {
|
||||
newMessage(chat.selectedCallsign, chat.inputText, chat.inputFile, chat.inputFileName, chat.inputFileSize, chat.inputFileType)
|
||||
}
|
||||
// finally do a cleanup
|
||||
//chatModuleMessage.reset();
|
||||
chat.inputText = '';
|
||||
chatModuleMessage.value="";
|
||||
// @ts-expect-error
|
||||
resetFile()
|
||||
}
|
||||
|
||||
function resetFile(event){
|
||||
chat.inputFileName = '-'
|
||||
chat.inputFileSize = '-'
|
||||
chat.inputFileType = '-'
|
||||
|
||||
}
|
||||
|
||||
|
||||
function readFile(event) {
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = () => {
|
||||
console.log(reader.result);
|
||||
chat.inputFileName = event.target.files[0].name
|
||||
chat.inputFileSize = event.target.files[0].size
|
||||
chat.inputFileType = event.target.files[0].type
|
||||
|
||||
chat.inputFile = reader.result
|
||||
calculateTimeNeeded()
|
||||
|
||||
// String.fromCharCode.apply(null, Array.from(chatFile))
|
||||
|
||||
|
||||
};
|
||||
|
||||
reader.readAsArrayBuffer(event.target.files[0]);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
ChartJS.register(
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
// calculate time needed for transmitting a file
|
||||
function calculateTimeNeeded(){
|
||||
|
||||
var calculatedSpeedPerMinutePER0 = []
|
||||
var calculatedSpeedPerMinutePER25 = []
|
||||
var calculatedSpeedPerMinutePER75 = []
|
||||
|
||||
// bpm vs snr with PER == 0
|
||||
var snrList = [
|
||||
{snr: -10, bpm: 100},
|
||||
{snr: -5, bpm: 300},
|
||||
{snr: 0, bpm: 800},
|
||||
{snr: 5, bpm: 2500},
|
||||
{snr: 10, bpm: 5300}
|
||||
];
|
||||
|
||||
for (let i = 0; i < snrList.length; i++) {
|
||||
|
||||
var result = snrList.find(obj => {
|
||||
return obj.snr === snrList[i].snr
|
||||
})
|
||||
|
||||
calculatedSpeedPerMinutePER0.push(chat.inputFileSize / result.bpm)
|
||||
calculatedSpeedPerMinutePER25.push(chat.inputFileSize / (result.bpm * 0.75))
|
||||
calculatedSpeedPerMinutePER75.push(chat.inputFileSize / (result.bpm * 0.25))
|
||||
|
||||
}
|
||||
|
||||
chat.chartSpeedPER0 = calculatedSpeedPerMinutePER0
|
||||
chat.chartSpeedPER25 = calculatedSpeedPerMinutePER25
|
||||
chat.chartSpeedPER75 = calculatedSpeedPerMinutePER75
|
||||
|
||||
}
|
||||
|
||||
|
||||
const speedChartData = computed(() => ({
|
||||
labels: ['-10', '-5', '0', '5', '10'],
|
||||
datasets: [
|
||||
{ data: chat.chartSpeedPER0, label: 'PER 0%' ,tension: 0.1, borderColor: 'rgb(0, 255, 0)' },
|
||||
{ data: chat.chartSpeedPER25, label: 'PER 25%' ,tension: 0.1, borderColor: 'rgb(255, 255, 0)'},
|
||||
{ data: chat.chartSpeedPER75, label: 'PER 75%' ,tension: 0.1, borderColor: 'rgb(255, 0, 0)' }
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
));
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
|
||||
<div class="container-fluid mt-2 p-0">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="expand_textarea"
|
||||
class="btn-check"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<label
|
||||
class="btn d-flex justify-content-center"
|
||||
id="expand_textarea_label"
|
||||
for="expand_textarea"
|
||||
><i
|
||||
id="expand_textarea_button"
|
||||
class="bi bi-chevron-compact-up"
|
||||
></i
|
||||
></label>
|
||||
|
||||
<div class="input-group bottom-0 ms-2">
|
||||
|
||||
<button type="button" class="btn btn-outline-secondary border-0 rounded-pill me-1"
|
||||
data-bs-toggle="modal" data-bs-target="#emojiPickerModal"
|
||||
data-bs-backdrop="false"
|
||||
>
|
||||
<i
|
||||
id="emojipickerbutton"
|
||||
class="bi bi-emoji-smile p-0"
|
||||
style="font-size: 1rem"
|
||||
></i>
|
||||
</button>
|
||||
|
||||
|
||||
|
||||
<!-- trigger file selection modal -->
|
||||
<button type="button" class="btn btn-outline-secondary border-0 rounded-pill me-1" data-bs-toggle="modal" data-bs-target="#fileSelectionModal">
|
||||
<i class="bi bi-paperclip" style="font-size: 1.2rem"></i>
|
||||
</button>
|
||||
|
||||
|
||||
<textarea
|
||||
class="form-control border rounded-pill"
|
||||
rows="1"
|
||||
ref="chatModuleMessage"
|
||||
placeholder="Message - Send with [Enter]"
|
||||
v-model="chat.inputText"
|
||||
@keyup.enter.exact="transmitNewMessage()"
|
||||
></textarea>
|
||||
|
||||
|
||||
|
||||
|
||||
<button
|
||||
class="btn btn-sm btn-secondary ms-1 me-2 rounded-pill"
|
||||
@click="transmitNewMessage()"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="bi bi-send ms-4 me-4"
|
||||
style="font-size: 1.2rem"
|
||||
></i>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- select file modal -->
|
||||
|
||||
<div
|
||||
class="modal fade"
|
||||
id="fileSelectionModal"
|
||||
tabindex="-1"
|
||||
aria-labelledby="fileSelectionModalLabel"
|
||||
aria-hidden="true"
|
||||
>
|
||||
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="staticBackdropLabel">File Attachment</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" @click="resetFile"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
|
||||
<div class="alert alert-warning d-flex align-items-center" role="alert">
|
||||
<i class="bi bi-exclamation-triangle-fill ms-2 me-2"></i>
|
||||
<div>
|
||||
Transmission speed over HF channels is very limited!
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="input-group-text mb-3">
|
||||
<input class="" type="file" ref="doc" @change="readFile" />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="btn-group me-2" role="group" aria-label="Basic outlined example">
|
||||
<button type="button" class="btn btn-secondary">Type</button>
|
||||
<button type="button" class="btn btn-secondary disabled">{{chat.inputFileType}}</button>
|
||||
</div>
|
||||
|
||||
<div class="btn-group me-2" role="group" aria-label="Basic outlined example">
|
||||
<button type="button" class="btn btn-secondary">Size</button>
|
||||
<button type="button" class="btn btn-secondary disabled">{{chat.inputFileSize}}</button>
|
||||
</div>
|
||||
|
||||
<Line :data="speedChartData" />
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @click="resetFile">Reset</button>
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">Append</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Emoji Picker Modal -->
|
||||
<div class="modal fade" id="emojiPickerModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-sm">
|
||||
<div class="modal-content">
|
||||
<div class="modal-body p-0">
|
||||
<VuemojiPicker @emojiClick="handleEmojiClick" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</template>
|
||||
|
570
gui/src/components/main.vue
Normal file
570
gui/src/components/main.vue
Normal file
|
@ -0,0 +1,570 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
import main_modals from "./main_modals.vue";
|
||||
import main_top_navbar from "./main_top_navbar.vue";
|
||||
import main_audio from "./main_audio.vue";
|
||||
import main_rig_control from "./main_rig_control.vue";
|
||||
import main_my_station from "./main_my_station.vue";
|
||||
import main_updater from "./main_updater.vue";
|
||||
import settings_view from "./settings.vue";
|
||||
import main_active_rig_control from "./main_active_rig_control.vue";
|
||||
import main_footer_navbar from "./main_footer_navbar.vue";
|
||||
|
||||
import main_active_stats from "./main_active_stats.vue";
|
||||
import main_active_broadcasts from "./main_active_broadcasts.vue";
|
||||
import main_active_heard_stations from "./main_active_heard_stations.vue";
|
||||
import main_active_audio_level from "./main_active_audio_level.vue";
|
||||
|
||||
import chat from "./chat.vue";
|
||||
|
||||
import { stopTransmission } from "../js/sock.js";
|
||||
|
||||
const version = import.meta.env.PACKAGE_VERSION;
|
||||
|
||||
function stopAllTransmissions() {
|
||||
console.log("stopping transmissions");
|
||||
stopTransmission();
|
||||
}
|
||||
function openWebExternal(url) {
|
||||
open(url);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-------------------------------- INFO TOASTS ---------------->
|
||||
<div
|
||||
aria-live="polite"
|
||||
aria-atomic="true"
|
||||
class="position-relative"
|
||||
style="z-index: 500"
|
||||
>
|
||||
<div
|
||||
class="toast-container position-absolute top-0 end-0 p-3"
|
||||
id="mainToastContainer"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-sm-auto bg-body-secondary border-end">
|
||||
<div
|
||||
class="d-flex flex-sm-column flex-row flex-nowrap align-items-center sticky-top"
|
||||
>
|
||||
<div
|
||||
class="list-group bg-body-secondary"
|
||||
id="main-list-tab"
|
||||
role="tablist"
|
||||
style="margin-top: 100px"
|
||||
>
|
||||
<a
|
||||
class="list-group-item list-group-item-action active"
|
||||
id="list-modem-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-modem"
|
||||
role="tab"
|
||||
aria-controls="list-modem"
|
||||
title="Home"
|
||||
><i class="bi bi-house-door-fill h3"></i
|
||||
></a>
|
||||
<a
|
||||
class="list-group-item list-group-item-action"
|
||||
id="list-chat-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-chat"
|
||||
role="tab"
|
||||
aria-controls="list-chat"
|
||||
title="Chat"
|
||||
><i class="bi bi-chat-text h3"></i
|
||||
></a>
|
||||
|
||||
<a
|
||||
class="list-group-item list-group-item-action d-none"
|
||||
id="list-mesh-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-mesh"
|
||||
role="tab"
|
||||
aria-controls="list-mesh"
|
||||
><i class="bi bi-rocket h3"></i
|
||||
></a>
|
||||
|
||||
<a
|
||||
class="list-group-item list-group-item-action mt-2 border"
|
||||
id="list-info-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-info"
|
||||
role="tab"
|
||||
aria-controls="list-info"
|
||||
title="About"
|
||||
><i class="bi bi-info h3"></i
|
||||
></a>
|
||||
|
||||
<a
|
||||
class="list-group-item list-group-item-action d-none"
|
||||
id="list-logger-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-logger"
|
||||
role="tab"
|
||||
aria-controls="list-logger"
|
||||
><i class="bi bi-activity h3"></i
|
||||
></a>
|
||||
|
||||
<a
|
||||
class="list-group-item list-group-item-action rounded-bottom"
|
||||
id="list-settings-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-settings"
|
||||
role="tab"
|
||||
aria-controls="list-settings"
|
||||
title="Settings"
|
||||
><i class="bi bi-gear-wide-connected h3"></i
|
||||
></a>
|
||||
|
||||
<a
|
||||
class="btn border btn-outline-danger list-group-item mt-5"
|
||||
id="stop_transmission_connection"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="false"
|
||||
@click="stopAllTransmissions()"
|
||||
title="Abort session and stop transmissions"
|
||||
><i class="bi bi-sign-stop-fill h3"></i
|
||||
></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm min-vh-100 m-0 p-0">
|
||||
<!-- content -->
|
||||
|
||||
<div class="tab-content" id="nav-tabContent-settings">
|
||||
<div
|
||||
class="tab-pane fade show active"
|
||||
id="list-modem"
|
||||
role="tabpanel"
|
||||
aria-labelledby="list-modem-list"
|
||||
>
|
||||
<!-- TOP NAVBAR -->
|
||||
<main_top_navbar />
|
||||
|
||||
<div
|
||||
id="blurdiv"
|
||||
style="
|
||||
-webkit-filter: blur(0px);
|
||||
filter: blur(0px);
|
||||
height: 100vh;
|
||||
"
|
||||
>
|
||||
<!--beginn of blur div -->
|
||||
<!-------------------------------- MAIN AREA ---------------->
|
||||
|
||||
<!------------------------------------------------------------------------------------------>
|
||||
<div class="container p-3">
|
||||
<div
|
||||
class="row collapse multi-collapse show mt-4"
|
||||
id="collapseFirstRow"
|
||||
>
|
||||
<div class="col">
|
||||
<main_audio />
|
||||
</div>
|
||||
<div class="col">
|
||||
<main_rig_control />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="row collapse multi-collapse show mt-4"
|
||||
id="collapseSecondRow"
|
||||
>
|
||||
<div class="col">
|
||||
<main_my_station />
|
||||
</div>
|
||||
<div class="col">
|
||||
<main_updater />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="row collapse multi-collapse" id="collapseThirdRow">
|
||||
<main_active_rig_control />
|
||||
|
||||
<div class="col-5">
|
||||
<main_active_audio_level />
|
||||
</div>
|
||||
<div class="col">
|
||||
<main_active_broadcasts />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="row collapse multi-collapse mt-3"
|
||||
id="collapseFourthRow"
|
||||
>
|
||||
<div class="col-5">
|
||||
<main_active_stats />
|
||||
</div>
|
||||
<div class="col">
|
||||
<main_active_heard_stations />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="tab-pane fade d-none"
|
||||
id="list-mesh"
|
||||
role="tabpanel"
|
||||
aria-labelledby="list-mesh-list"
|
||||
>
|
||||
<div class="container">
|
||||
<nav>
|
||||
<div class="nav nav-tabs" id="nav-tab-mesh" role="tablist-mesh">
|
||||
<button
|
||||
class="nav-link active"
|
||||
id="nav-route-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#nav-route"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="nav-route"
|
||||
aria-selected="true"
|
||||
>
|
||||
Routes
|
||||
</button>
|
||||
<button
|
||||
class="nav-link"
|
||||
id="nav-signaling-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#nav-signaling"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="nav-signaling"
|
||||
aria-selected="false"
|
||||
>
|
||||
Signaling
|
||||
</button>
|
||||
<button
|
||||
class="nav-link"
|
||||
id="nav-actions-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#nav-actions"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="nav-actions"
|
||||
aria-selected="false"
|
||||
>
|
||||
Actions
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="tab-content d-none" id="nav-tabContent-Mesh">
|
||||
<div
|
||||
class="tab-pane fade show active vw-100 vh-90 overflow-auto"
|
||||
id="nav-route"
|
||||
role="tabpanel"
|
||||
aria-labelledby="nav-route-tab"
|
||||
>
|
||||
<div class="container-fluid">
|
||||
<div
|
||||
class="table-responsive overflow-auto"
|
||||
style="max-width: 99vw; max-height: 99vh"
|
||||
>
|
||||
<table class="table table-hover table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Timestamp</th>
|
||||
<th scope="col">DXCall</th>
|
||||
<th scope="col">Router</th>
|
||||
<th scope="col">Hops</th>
|
||||
<th scope="col">Score</th>
|
||||
<th scope="col">SNR</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="mesh-table"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
id="nav-signaling"
|
||||
role="tabpanel"
|
||||
aria-labelledby="nav-signaling-tab"
|
||||
>
|
||||
<div class="container-fluid">
|
||||
<div
|
||||
class="table-responsive overflow-auto"
|
||||
style="max-width: 99vw; max-height: 99vh"
|
||||
>
|
||||
<table class="table table-hover table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Timestamp</th>
|
||||
<th scope="col">Destination</th>
|
||||
<th scope="col">Origin</th>
|
||||
<th scope="col">Frametype</th>
|
||||
<th scope="col">Payload</th>
|
||||
<th scope="col">Attempt</th>
|
||||
<th scope="col">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="mesh-signalling-table"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
id="nav-actions"
|
||||
role="tabpanel-mesh"
|
||||
aria-labelledby="nav-actions-tab"
|
||||
>
|
||||
<div class="input-group mt-1">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
style="max-width: 6rem; text-transform: uppercase"
|
||||
placeholder="DXcall"
|
||||
pattern="[A-Z]*"
|
||||
id="dxCallMesh"
|
||||
maxlength="11"
|
||||
aria-label="Input group"
|
||||
aria-describedby="btnGroupAddon"
|
||||
/>
|
||||
<button
|
||||
id="transmit_mesh_ping"
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
>
|
||||
mesh ping
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
id="list-info"
|
||||
role="tabpanel"
|
||||
aria-labelledby="list-info-list"
|
||||
>
|
||||
<h1 class="modal-title fs-5" id="aboutModalLabel">
|
||||
FreeDATA - {{ version }}
|
||||
</h1>
|
||||
|
||||
<h4 class="fs-5">modem version - {{ state.modem_version }}</h4>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row mt-2">
|
||||
<div
|
||||
class="btn-toolbar mx-auto"
|
||||
role="toolbar"
|
||||
aria-label="Toolbar with button groups"
|
||||
>
|
||||
<div class="btn-group">
|
||||
<button
|
||||
class="btn btn-sm bi bi-geo-alt btn-secondary me-2"
|
||||
id="openExplorer"
|
||||
type="button"
|
||||
data-bs-placement="bottom"
|
||||
@click="openWebExternal('https://explorer.freedata.app')"
|
||||
>
|
||||
Explorer map
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-2 bi bi-graph-up"
|
||||
id="btnStats"
|
||||
type="button"
|
||||
data-bs-placement="bottom"
|
||||
@click="
|
||||
openWebExternal('https://statistics.freedata.app/')
|
||||
"
|
||||
>
|
||||
Statistics
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button
|
||||
class="btn btn-secondary bi bi-bookmarks me-2"
|
||||
id="fdWww"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
title="FreeDATA website"
|
||||
role="button"
|
||||
@click="openWebExternal('https://freedata.app')"
|
||||
>
|
||||
Website
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button
|
||||
class="btn btn-secondary bi bi-github me-2"
|
||||
id="ghUrl"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
title="Github"
|
||||
role="button"
|
||||
@click="
|
||||
openWebExternal('https://github.com/dj2ls/freedata')
|
||||
"
|
||||
>
|
||||
Github
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button
|
||||
class="btn btn-secondary bi bi-wikipedia me-2"
|
||||
id="wikiUrl"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
title="Wiki"
|
||||
role="button"
|
||||
@click="openWebExternal('https://wiki.freedata.app')"
|
||||
>
|
||||
Wiki
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button
|
||||
class="btn btn-secondary bi bi-discord"
|
||||
id="discordUrl"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
title="Discord"
|
||||
role="button"
|
||||
@click="openWebExternal('https://discord.freedata.app')"
|
||||
>
|
||||
Discord
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-5">
|
||||
<h6>Special thanks to</h6>
|
||||
<hr />
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-4" id="divContrib"></div>
|
||||
<div class="col-4" id="divContrib2"></div>
|
||||
<div class="col-4" id="divContrib3"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
id="list-chat"
|
||||
role="tabpanel"
|
||||
aria-labelledby="list-chat-list"
|
||||
>
|
||||
<chat />
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
id="list-logger"
|
||||
role="tabpanel"
|
||||
aria-labelledby="list-logger-list"
|
||||
>
|
||||
<div class="container">
|
||||
<nav
|
||||
class="navbar fixed-top bg-body-tertiary border-bottom"
|
||||
style="margin-left: 87px"
|
||||
>
|
||||
<div class="container-fluid">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="btn-check"
|
||||
id="enable_filter_info"
|
||||
autocomplete="off"
|
||||
checked
|
||||
/>
|
||||
<label class="btn btn-outline-info" for="enable_filter_info"
|
||||
>info</label
|
||||
>
|
||||
|
||||
<input
|
||||
type="checkbox"
|
||||
class="btn-check"
|
||||
id="enable_filter_debug"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<label
|
||||
class="btn btn-outline-primary"
|
||||
for="enable_filter_debug"
|
||||
>debug</label
|
||||
>
|
||||
|
||||
<input
|
||||
type="checkbox"
|
||||
class="btn-check"
|
||||
id="enable_filter_warning"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<label
|
||||
class="btn btn-outline-warning"
|
||||
for="enable_filter_warning"
|
||||
>warning</label
|
||||
>
|
||||
|
||||
<input
|
||||
type="checkbox"
|
||||
class="btn-check"
|
||||
id="enable_filter_error"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<label
|
||||
class="btn btn-outline-danger"
|
||||
for="enable_filter_error"
|
||||
>error</label
|
||||
>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container-fluid mt-5">
|
||||
<div class="tableFixHead">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Timestamp</th>
|
||||
<th scope="col">Type</th>
|
||||
<th scope="col">Area</th>
|
||||
<th scope="col">Log entry</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="log">
|
||||
<!--
|
||||
<tr>
|
||||
<th scope="row">1</th>
|
||||
<td>Mark</td>
|
||||
<td>Otto</td>
|
||||
<td>@mdo</td>
|
||||
</tr>
|
||||
-->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<settings_view />
|
||||
|
||||
<!---------------------------------------------------------------------- FOOTER AREA ------------------------------------------------------------>
|
||||
|
||||
<div class="container">
|
||||
<main_footer_navbar />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<main_modals />
|
||||
</template>
|
188
gui/src/components/main_active_audio_level.vue
Normal file
188
gui/src/components/main_active_audio_level.vue
Normal file
|
@ -0,0 +1,188 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
import { record_audio } from "../js/sock.js";
|
||||
|
||||
function startStopRecordAudio() {
|
||||
record_audio();
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="card mb-1">
|
||||
<div class="card-header p-1">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
<i class="bi bi-volume-up" style="font-size: 1.2rem"></i>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<strong class="fs-5">Audio</strong>
|
||||
</div>
|
||||
<div class="col-7">
|
||||
<button
|
||||
type="button"
|
||||
id="audioModalButton"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#audioModal"
|
||||
class="btn btn-sm btn-outline-secondary me-1"
|
||||
>
|
||||
Tune
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
id="startStopRecording"
|
||||
class="btn btn-sm"
|
||||
@click="startStopRecordAudio()"
|
||||
v-bind:class="{
|
||||
'btn-outline-secondary': state.audio_recording === 'False',
|
||||
'btn-secondary': state.audio_recording === 'True',
|
||||
}"
|
||||
>
|
||||
Record
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-1 text-end">
|
||||
<button
|
||||
type="button"
|
||||
id="openHelpModalAudioLevel"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#audioLevelHelpModal"
|
||||
class="btn m-0 p-0 border-0"
|
||||
>
|
||||
<i class="bi bi-question-circle" style="font-size: 1rem"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-2">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
<div
|
||||
class="progress mb-0 rounded-0 rounded-top"
|
||||
style="height: 22px"
|
||||
>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-primary force-gpu"
|
||||
id="noise_level"
|
||||
role="progressbar"
|
||||
:style="{ width: state.s_meter_strength_percent + '%' }"
|
||||
aria-valuenow="{{state.s_meter_strength_percent}}"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<p
|
||||
class="justify-content-center d-flex position-absolute w-100"
|
||||
id="noise_level_value"
|
||||
>
|
||||
S-Meter(dB): {{ state.s_meter_strength_raw }}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="progress mb-0 rounded-0 rounded-bottom"
|
||||
style="height: 8px"
|
||||
>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-warning"
|
||||
role="progressbar"
|
||||
style="width: 1%"
|
||||
aria-valuenow="1"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<div
|
||||
class="progress-bar bg-success"
|
||||
role="progressbar"
|
||||
style="width: 89%"
|
||||
aria-valuenow="50"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-warning"
|
||||
role="progressbar"
|
||||
style="width: 20%"
|
||||
aria-valuenow="20"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-danger"
|
||||
role="progressbar"
|
||||
style="width: 29%"
|
||||
aria-valuenow="29"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
<div
|
||||
class="progress mb-0 rounded-0 rounded-top"
|
||||
style="height: 22px"
|
||||
>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-primary force-gpu"
|
||||
id="dbfs_level"
|
||||
role="progressbar"
|
||||
:style="{ width: state.dbfs_level_percent + '%' }"
|
||||
aria-valuenow="0"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<p
|
||||
class="justify-content-center d-flex position-absolute w-100"
|
||||
id="dbfs_level_value"
|
||||
>
|
||||
{{ state.dbfs_level }} dBFS
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="progress mb-0 rounded-0 rounded-bottom"
|
||||
style="height: 8px"
|
||||
>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-warning"
|
||||
role="progressbar"
|
||||
style="width: 1%"
|
||||
aria-valuenow="1"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<div
|
||||
class="progress-bar bg-success"
|
||||
role="progressbar"
|
||||
style="width: 89%"
|
||||
aria-valuenow="50"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-warning"
|
||||
role="progressbar"
|
||||
style="width: 20%"
|
||||
aria-valuenow="20"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-danger"
|
||||
role="progressbar"
|
||||
style="width: 29%"
|
||||
aria-valuenow="29"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
121
gui/src/components/main_active_broadcasts.vue
Normal file
121
gui/src/components/main_active_broadcasts.vue
Normal file
|
@ -0,0 +1,121 @@
|
|||
<script setup lang="ts">
|
||||
import { saveSettingsToFile } from "../js/settingsHandler";
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useSettingsStore } from "../store/settingsStore.js";
|
||||
const settings = useSettingsStore(pinia);
|
||||
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
import { sendCQ, sendPing, startBeacon, stopBeacon } from "../js/sock.js";
|
||||
|
||||
function transmitCQ() {
|
||||
sendCQ();
|
||||
}
|
||||
|
||||
function transmitPing() {
|
||||
sendPing((<HTMLInputElement>document.getElementById("dxCall")).value);
|
||||
}
|
||||
|
||||
function startStopBeacon() {
|
||||
switch (state.beacon_state) {
|
||||
case "False":
|
||||
startBeacon(settings.beacon_interval);
|
||||
|
||||
break;
|
||||
case "True":
|
||||
stopBeacon();
|
||||
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="card mb-1">
|
||||
<div class="card-header p-1">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
<i class="bi bi-broadcast" style="font-size: 1.2rem"></i>
|
||||
</div>
|
||||
<div class="col-10">
|
||||
<strong class="fs-5">Broadcasts</strong>
|
||||
</div>
|
||||
<div class="col-1 text-end">
|
||||
<button
|
||||
type="button"
|
||||
id="openHelpModalBroadcasts"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#broadcastsHelpModal"
|
||||
class="btn m-0 p-0 border-0"
|
||||
>
|
||||
<i class="bi bi-question-circle" style="font-size: 1rem"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-2">
|
||||
<div class="row">
|
||||
<div class="col-md-auto">
|
||||
<div class="input-group input-group-sm mb-0">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
style="max-width: 6rem; text-transform: uppercase"
|
||||
placeholder="DXcall"
|
||||
pattern="[A-Z]*"
|
||||
id="dxCall"
|
||||
maxlength="11"
|
||||
aria-label="Input group"
|
||||
aria-describedby="btnGroupAddon"
|
||||
/>
|
||||
<button
|
||||
class="btn btn-sm btn-outline-secondary ms-1"
|
||||
id="sendPing"
|
||||
type="button"
|
||||
data-bs-placement="bottom"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="false"
|
||||
title="Send a ping request to a remote station"
|
||||
@click="transmitPing()"
|
||||
>
|
||||
Ping
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm btn-outline-secondary ms-1"
|
||||
id="sendCQ"
|
||||
type="button"
|
||||
title="Send a CQ to the world"
|
||||
@click="transmitCQ()"
|
||||
>
|
||||
Call CQ
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
id="startBeacon"
|
||||
class="btn btn-sm ms-1"
|
||||
@click="startStopBeacon()"
|
||||
v-bind:class="{
|
||||
'btn-success': state.beacon_state === 'True',
|
||||
'btn-outline-secondary': state.beacon_state === 'False',
|
||||
}"
|
||||
title="Toggle beacon mode. The interval can be set in settings. While sending a beacon, you can receive ping requests and open a datachannel. If a datachannel is opened, the beacon pauses."
|
||||
>
|
||||
<i class="bi bi-soundwave"></i> Toggle beacon
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end of row-->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
105
gui/src/components/main_active_heard_stations.vue
Normal file
105
gui/src/components/main_active_heard_stations.vue
Normal file
|
@ -0,0 +1,105 @@
|
|||
<script setup lang="ts">
|
||||
// @ts-nocheck
|
||||
const { distance } = require("qth-locator");
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useSettingsStore } from "../store/settingsStore.js";
|
||||
const settings = useSettingsStore(pinia);
|
||||
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
function getDateTime(timestampRaw) {
|
||||
var datetime = new Date(timestampRaw * 1000).toLocaleString(
|
||||
navigator.language,
|
||||
{
|
||||
hourCycle: "h23",
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
},
|
||||
);
|
||||
return datetime;
|
||||
}
|
||||
|
||||
function getMaidenheadDistance(dxGrid) {
|
||||
try {
|
||||
return parseInt(distance(settings.mygrid, dxGrid));
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="card mb-1 h-100">
|
||||
<!--325px-->
|
||||
<div class="card-header p-1">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-auto">
|
||||
<i class="bi bi-list-columns-reverse" style="font-size: 1.2rem"></i>
|
||||
</div>
|
||||
<div class="col-10">
|
||||
<strong class="fs-5">Heard stations</strong>
|
||||
</div>
|
||||
<div class="col-1 text-end">
|
||||
<button
|
||||
type="button"
|
||||
id="openHelpModalHeardStations"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#heardStationsHelpModal"
|
||||
class="btn m-0 p-0 border-0"
|
||||
>
|
||||
<i class="bi bi-question-circle" style="font-size: 1rem"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-0" style="overflow-y: overlay">
|
||||
<div class="table-responsive">
|
||||
<!-- START OF TABLE FOR HEARD STATIONS -->
|
||||
<table class="table table-sm" id="tblHeardStationList">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" id="thTime">
|
||||
<i id="hslSort" class="bi bi-sort-up"></i>Time
|
||||
</th>
|
||||
<th scope="col" id="thFreq">Frequency</th>
|
||||
<th> </th>
|
||||
<th scope="col" id="thDxcall">DXCall</th>
|
||||
<th scope="col" id="thDxgrid">DXGrid</th>
|
||||
<th scope="col" id="thDist">Distance</th>
|
||||
<th scope="col" id="thType">Type</th>
|
||||
<th scope="col" id="thSnr">SNR (rx/dx)</th>
|
||||
<!--<th scope="col">Off</th>-->
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="heardstations">
|
||||
<!--https://vuejs.org/guide/essentials/list.html-->
|
||||
<tr v-for="item in state.heard_stations" :key="item.timestamp">
|
||||
<td>{{ getDateTime(item.timestamp) }}</td>
|
||||
<td>{{ item.frequency }}</td>
|
||||
<td> </td>
|
||||
<td>
|
||||
<span class="badge bg-secondary">{{ item.dxcallsign }}</span>
|
||||
</td>
|
||||
<td>{{ item.dxgrid }}</td>
|
||||
<td>{{ getMaidenheadDistance(item.dxgrid) }} km</td>
|
||||
<td>{{ item.datatype }}</td>
|
||||
<td>{{ item.snr }}</td>
|
||||
<!--<td>{{ item.offset }}</td>-->
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- END OF HEARD STATIONS TABLE -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
153
gui/src/components/main_active_rig_control.vue
Normal file
153
gui/src/components/main_active_rig_control.vue
Normal file
|
@ -0,0 +1,153 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
import { set_frequency, set_mode, set_rf_level } from "../js/sock.js";
|
||||
|
||||
function set_hamlib_frequency() {
|
||||
set_frequency(state.new_frequency);
|
||||
}
|
||||
|
||||
function set_hamlib_mode() {
|
||||
set_mode(state.mode);
|
||||
}
|
||||
|
||||
function set_hamlib_rf_level() {
|
||||
set_rf_level(state.rf_level);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mb-3">
|
||||
<div class="card mb-1">
|
||||
<div class="card-header p-1">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
<i class="bi bi-house-door" style="font-size: 1.2rem"></i>
|
||||
</div>
|
||||
<div class="col-10">
|
||||
<strong class="fs-5 me-2">Radio control</strong>
|
||||
<span
|
||||
class="badge"
|
||||
v-bind:class="{
|
||||
'text-bg-success': state.hamlib_status === 'connected',
|
||||
'text-bg-danger disabled':
|
||||
state.hamlib_status === 'disconnected',
|
||||
}"
|
||||
>{{ state.hamlib_status }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="col-1 text-end">
|
||||
<button
|
||||
type="button"
|
||||
id="openHelpModalStation"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#stationHelpModal"
|
||||
class="btn m-0 p-0 border-0"
|
||||
disabled
|
||||
>
|
||||
<i class="bi bi-question-circle" style="font-size: 1rem"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-2">
|
||||
<div class="input-group bottom-0 m-0">
|
||||
<div class="me-2">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">QRG</span>
|
||||
<span class="input-group-text">{{ state.frequency }} Hz</span>
|
||||
<span class="input-group-text">QSY</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
v-model="state.new_frequency"
|
||||
style="max-width: 8rem"
|
||||
pattern="[0-9]*"
|
||||
list="frequencyDataList"
|
||||
v-bind:class="{
|
||||
disabled: state.hamlib_status === 'disconnected',
|
||||
}"
|
||||
/>
|
||||
|
||||
<datalist id="frequencyDataList">
|
||||
<option selected value="7053000">40m | USB | EU, US</option>
|
||||
<option value="14093000">20m | USB | EU, US</option>
|
||||
<option value="21093000">15m | USB | EU, US</option>
|
||||
<option value="24908000">12m | USB | EU, US</option>
|
||||
<option value="28093000">10m | USB | EU, US</option>
|
||||
<option value="50308000">6m | USB | US</option>
|
||||
<option value="50616000">6m | USB | EU, US</option>
|
||||
</datalist>
|
||||
<button
|
||||
class="btn btn-sm btn-outline-success"
|
||||
type="button"
|
||||
@click="set_hamlib_frequency"
|
||||
v-bind:class="{
|
||||
disabled: state.hamlib_status === 'disconnected',
|
||||
}"
|
||||
>
|
||||
Apply
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="me-2">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">Mode</span>
|
||||
<select
|
||||
class="form-control"
|
||||
v-model="state.mode"
|
||||
@click="set_hamlib_mode()"
|
||||
v-bind:class="{
|
||||
disabled: state.hamlib_status === 'disconnected',
|
||||
}"
|
||||
>
|
||||
<option value="USB">USB</option>
|
||||
<option value="LSB">LSB</option>
|
||||
<option value="PKTUSB">PKT-U</option>
|
||||
<option value="PKTLSB">PKT-L</option>
|
||||
<option value="AM">AM</option>
|
||||
<option value="FM">FM</option>
|
||||
<option value="PKTFM">PKTFM</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="me-2">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">Power</span>
|
||||
<select
|
||||
class="form-control"
|
||||
v-model="state.rf_level"
|
||||
@click="set_hamlib_rf_level()"
|
||||
v-bind:class="{
|
||||
disabled: state.hamlib_status === 'disconnected',
|
||||
}"
|
||||
>
|
||||
<option value="0">-</option>
|
||||
<option value="10">10</option>
|
||||
<option value="20">20</option>
|
||||
<option value="30">30</option>
|
||||
<option value="40">40</option>
|
||||
<option value="50">50</option>
|
||||
<option value="60">60</option>
|
||||
<option value="70">70</option>
|
||||
<option value="80">80</option>
|
||||
<option value="90">90</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
<span class="input-group-text">%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
396
gui/src/components/main_active_stats.vue
Normal file
396
gui/src/components/main_active_stats.vue
Normal file
|
@ -0,0 +1,396 @@
|
|||
<script setup lang="ts">
|
||||
// @ts-nocheck
|
||||
// reason for no check is, that we have some mixing of typescript and chart js which seems to be not to be fixed that easy
|
||||
|
||||
import { saveSettingsToFile } from "../js/settingsHandler";
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useSettingsStore } from "../store/settingsStore.js";
|
||||
const settings = useSettingsStore(pinia);
|
||||
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
} from "chart.js";
|
||||
import { Line, Scatter } from "vue-chartjs";
|
||||
import { computed } from "vue";
|
||||
|
||||
function selectStatsControl(obj) {
|
||||
switch (obj.delegateTarget.id) {
|
||||
case "list-waterfall-list":
|
||||
settings.spectrum = "waterfall";
|
||||
break;
|
||||
case "list-scatter-list":
|
||||
settings.spectrum = "scatter";
|
||||
break;
|
||||
case "list-chart-list":
|
||||
settings.spectrum = "chart";
|
||||
break;
|
||||
default:
|
||||
settings.spectrum = "waterfall";
|
||||
}
|
||||
saveSettingsToFile();
|
||||
}
|
||||
|
||||
ChartJS.register(
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
);
|
||||
|
||||
// https://www.chartjs.org/docs/latest/samples/line/segments.html
|
||||
const skipped = (speedCtx, value) =>
|
||||
speedCtx.p0.skip || speedCtx.p1.skip ? value : undefined;
|
||||
const down = (speedCtx, value) =>
|
||||
speedCtx.p0.parsed.y > speedCtx.p1.parsed.y ? value : undefined;
|
||||
|
||||
var transmissionSpeedChartOptions = {
|
||||
//type: "line",
|
||||
responsive: true,
|
||||
animations: true,
|
||||
maintainAspectRatio: false,
|
||||
cubicInterpolationMode: "monotone",
|
||||
tension: 0.4,
|
||||
scales: {
|
||||
SNR: {
|
||||
type: "linear",
|
||||
ticks: { beginAtZero: false, color: "rgb(255, 99, 132)" },
|
||||
position: "right",
|
||||
},
|
||||
SPEED: {
|
||||
type: "linear",
|
||||
ticks: { beginAtZero: false, color: "rgb(120, 100, 120)" },
|
||||
position: "left",
|
||||
grid: {
|
||||
drawOnChartArea: false, // only want the grid lines for one axis to show up
|
||||
},
|
||||
},
|
||||
x: { ticks: { beginAtZero: true } },
|
||||
},
|
||||
};
|
||||
|
||||
const transmissionSpeedChartData = computed(() => ({
|
||||
labels: state.arq_speed_list_timestamp,
|
||||
datasets: [
|
||||
{
|
||||
type: "line",
|
||||
label: "SNR[dB]",
|
||||
data: state.arq_speed_list_snr,
|
||||
borderColor: "rgb(75, 192, 192, 1.0)",
|
||||
pointRadius: 1,
|
||||
segment: {
|
||||
borderColor: (speedCtx) =>
|
||||
skipped(speedCtx, "rgb(0,0,0,0.4)") ||
|
||||
down(speedCtx, "rgb(192,75,75)"),
|
||||
borderDash: (speedCtx) => skipped(speedCtx, [3, 3]),
|
||||
},
|
||||
spanGaps: true,
|
||||
backgroundColor: "rgba(75, 192, 192, 0.2)",
|
||||
order: 1,
|
||||
yAxisID: "SNR",
|
||||
},
|
||||
{
|
||||
type: "bar",
|
||||
label: "Speed[bpm]",
|
||||
data: state.arq_speed_list_bpm,
|
||||
borderColor: "rgb(120, 100, 120, 1.0)",
|
||||
backgroundColor: "rgba(120, 100, 120, 0.2)",
|
||||
order: 0,
|
||||
yAxisID: "SPEED",
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
const scatterChartOptions = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
x: {
|
||||
type: "linear",
|
||||
position: "bottom",
|
||||
grid: {
|
||||
display: true,
|
||||
lineWidth: 1, // Set the line width for x-axis grid lines
|
||||
},
|
||||
ticks: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
y: {
|
||||
type: "linear",
|
||||
position: "left",
|
||||
grid: {
|
||||
display: true,
|
||||
lineWidth: 1, // Set the line width for y-axis grid lines
|
||||
},
|
||||
ticks: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// dummy data
|
||||
//state.scatter = [{"x":"166","y":"46"},{"x":"-193","y":"-139"},{"x":"-165","y":"-291"},{"x":"311","y":"-367"},{"x":"389","y":"199"},{"x":"78","y":"372"},{"x":"242","y":"-431"},{"x":"-271","y":"-248"},{"x":"28","y":"-130"},{"x":"-20","y":"187"},{"x":"74","y":"362"},{"x":"-316","y":"-229"},{"x":"-180","y":"261"},{"x":"321","y":"360"},{"x":"438","y":"-288"},{"x":"378","y":"-94"},{"x":"462","y":"-163"},{"x":"-265","y":"248"},{"x":"210","y":"314"},{"x":"230","y":"-320"},{"x":"261","y":"-244"},{"x":"-283","y":"-373"}]
|
||||
|
||||
const scatterChartData = computed(() => ({
|
||||
datasets: [
|
||||
{
|
||||
type: "scatter",
|
||||
fill: true,
|
||||
data: state.scatter,
|
||||
label: "Scatter",
|
||||
tension: 0.1,
|
||||
borderColor: "rgb(0, 255, 0)",
|
||||
},
|
||||
],
|
||||
}));
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { initWaterfall } from "../js/waterfallHandler.js";
|
||||
|
||||
export default {
|
||||
mounted() {
|
||||
// This code will be executed after the component is mounted to the DOM
|
||||
// You can access DOM elements or perform other initialization here
|
||||
//const myElement = this.$refs.waterfall; // Access the DOM element with ref
|
||||
|
||||
// init waterfall
|
||||
initWaterfall();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card mb-1" style="height: calc(var(--variable-height) - 20px)">
|
||||
<div class="card-header p-1">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-11">
|
||||
<div class="btn-group" role="group">
|
||||
<div
|
||||
class="list-group list-group-horizontal"
|
||||
id="list-tab"
|
||||
role="tablist"
|
||||
>
|
||||
<a
|
||||
class="py-1 list-group-item list-group-item-action"
|
||||
id="list-waterfall-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-waterfall"
|
||||
role="tab"
|
||||
aria-controls="list-waterfall"
|
||||
v-bind:class="{ active: settings.spectrum === 'waterfall' }"
|
||||
@click="selectStatsControl($event)"
|
||||
><strong><i class="bi bi-water"></i></strong
|
||||
></a>
|
||||
<a
|
||||
class="py-1 list-group-item list-group-item-action"
|
||||
id="list-scatter-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-scatter"
|
||||
role="tab"
|
||||
aria-controls="list-scatter"
|
||||
v-bind:class="{ active: settings.spectrum === 'scatter' }"
|
||||
@click="selectStatsControl($event)"
|
||||
><strong><i class="bi bi-border-outer"></i></strong
|
||||
></a>
|
||||
<a
|
||||
class="py-1 list-group-item list-group-item-action"
|
||||
id="list-chart-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-chart"
|
||||
role="tab"
|
||||
aria-controls="list-chart"
|
||||
v-bind:class="{ active: settings.spectrum === 'chart' }"
|
||||
@click="selectStatsControl($event)"
|
||||
><strong><i class="bi bi-graph-up-arrow"></i></strong
|
||||
></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-group" role="group" aria-label="Busy indicators">
|
||||
<button
|
||||
class="btn btn-sm ms-1 p-1 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
v-bind:class="{
|
||||
'btn-warning': state.getChannelBusySlotState(0) === true,
|
||||
'btn-outline-secondary':
|
||||
state.getChannelBusySlotState(0) === false,
|
||||
}"
|
||||
title="Channel busy state: <strong class='text-success'>not busy</strong> / <strong class='text-danger'>busy </strong>"
|
||||
>
|
||||
S1
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm p-1 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
v-bind:class="{
|
||||
'btn-warning': state.getChannelBusySlotState(1) === true,
|
||||
'btn-outline-secondary':
|
||||
state.getChannelBusySlotState(1) === false,
|
||||
}"
|
||||
title="Channel busy state: <strong class='text-success'>not busy</strong> / <strong class='text-danger'>busy </strong>"
|
||||
>
|
||||
S2
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm p-1 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
v-bind:class="{
|
||||
'btn-warning': state.getChannelBusySlotState(2) === true,
|
||||
'btn-outline-secondary':
|
||||
state.getChannelBusySlotState(2) === false,
|
||||
}"
|
||||
title="Channel busy state: <strong class='text-success'>not busy</strong> / <strong class='text-danger'>busy </strong>"
|
||||
>
|
||||
S3
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm p-1 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
v-bind:class="{
|
||||
'btn-warning': state.getChannelBusySlotState(3) === true,
|
||||
'btn-outline-secondary':
|
||||
state.getChannelBusySlotState(3) === false,
|
||||
}"
|
||||
title="Channel busy state: <strong class='text-success'>not busy</strong> / <strong class='text-danger'>busy </strong>"
|
||||
>
|
||||
S4
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm p-1 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
v-bind:class="{
|
||||
'btn-warning': state.getChannelBusySlotState(4) === true,
|
||||
'btn-outline-secondary':
|
||||
state.getChannelBusySlotState(4) === false,
|
||||
}"
|
||||
title="Channel busy state: <strong class='text-success'>not busy</strong> / <strong class='text-danger'>busy </strong>"
|
||||
>
|
||||
S5
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm p-1 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
title="Recieving data: illuminates <strong class='text-success'>green</strong> if receiving codec2 data"
|
||||
v-bind:class="{
|
||||
'btn-success': state.is_codec2_traffic === 'True',
|
||||
'btn-outline-secondary': state.is_codec2_traffic === 'False',
|
||||
}"
|
||||
>
|
||||
data
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-1 text-end">
|
||||
<button
|
||||
type="button"
|
||||
id="openHelpModalWaterfall"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#waterfallHelpModal"
|
||||
class="btn m-0 p-0 border-0"
|
||||
>
|
||||
<i class="bi bi-question-circle" style="font-size: 1rem"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-1">
|
||||
<div class="tab-content" id="nav-stats-tabContent">
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
v-bind:class="{ 'show active': settings.spectrum === 'waterfall' }"
|
||||
id="list-waterfall"
|
||||
role="stats_tabpanel"
|
||||
aria-labelledby="list-waterfall-list"
|
||||
>
|
||||
<canvas
|
||||
ref="waterfall"
|
||||
id="waterfall"
|
||||
style="position: relative; z-index: 2"
|
||||
class="force-gpu h-100 w-100"
|
||||
></canvas>
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
v-bind:class="{ 'show active': settings.spectrum === 'scatter' }"
|
||||
id="list-scatter"
|
||||
role="tabpanel"
|
||||
aria-labelledby="list-scatter-list"
|
||||
>
|
||||
<Scatter :data="scatterChartData" :options="scatterChartOptions" />
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
v-bind:class="{ 'show active': settings.spectrum === 'chart' }"
|
||||
id="list-chart"
|
||||
role="tabpanel"
|
||||
aria-labelledby="list-chart-list"
|
||||
>
|
||||
<Line
|
||||
:data="transmissionSpeedChartData"
|
||||
:options="transmissionSpeedChartOptions"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--278px-->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
61
gui/src/components/main_audio.vue
Normal file
61
gui/src/components/main_audio.vue
Normal file
|
@ -0,0 +1,61 @@
|
|||
<script setup>
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useAudioStore } from "../store/audioStore.js";
|
||||
const audio = useAudioStore(pinia);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card mb-0">
|
||||
<div class="card-header p-1">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
<i class="bi bi-volume-up" style="font-size: 1.2rem"></i>
|
||||
</div>
|
||||
<div class="col-10">
|
||||
<strong class="fs-5">Audio devices</strong>
|
||||
</div>
|
||||
<div class="col-1 text-end">
|
||||
<button
|
||||
type="button"
|
||||
id="openHelpModalAudio"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#audioHelpModal"
|
||||
class="btn m-0 p-0 border-0"
|
||||
>
|
||||
<i class="bi bi-question-circle" style="font-size: 1rem"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body p-2" style="height: 100px">
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text">
|
||||
<i class="bi bi-mic-fill" style="font-size: 1rem"></i>
|
||||
</span>
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
id="audio_input_selectbox"
|
||||
aria-label=".form-select-sm"
|
||||
v-html="audio.getInputDevices()"
|
||||
></select>
|
||||
</div>
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-text">
|
||||
<i class="bi bi-volume-up" style="font-size: 1rem"></i>
|
||||
</span>
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
id="audio_output_selectbox"
|
||||
aria-label=".form-select-sm"
|
||||
v-html="audio.getOutputDevices()"
|
||||
></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
251
gui/src/components/main_footer_navbar.vue
Normal file
251
gui/src/components/main_footer_navbar.vue
Normal file
|
@ -0,0 +1,251 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav
|
||||
class="navbar fixed-bottom navbar-expand-xl bg-body-tertiary border-top p-2"
|
||||
style="margin-left: 87px"
|
||||
>
|
||||
<div class="col">
|
||||
<div class="btn-toolbar" role="toolbar" style="margin-left: 2px">
|
||||
<div class="btn-group btn-group-sm me-1" role="group">
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-1"
|
||||
v-bind:class="{
|
||||
'bg-danger': state.ptt_state === 'True',
|
||||
'bg-secondary': state.ptt_state === 'False',
|
||||
}"
|
||||
id="ptt_state"
|
||||
type="button"
|
||||
title="Rig PTT state"
|
||||
style="pointer-events: auto"
|
||||
disabled
|
||||
>
|
||||
<i class="bi bi-broadcast-pin" style="font-size: 0.8rem"></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-1"
|
||||
id="busy_state"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
v-bind:class="{
|
||||
'bg-danger': state.busy_state === 'BUSY',
|
||||
'bg-secondary': state.busy_state === 'IDLE',
|
||||
}"
|
||||
title="Modem state"
|
||||
disabled
|
||||
style="pointer-events: auto"
|
||||
>
|
||||
<i class="bi bi-cpu" style="font-size: 0.8rem"></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-1"
|
||||
id="arq_session"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
v-bind:class="{
|
||||
'bg-secondary': state.arq_session_state === 'disconnected',
|
||||
'bg-warning': state.arq_session_state === 'connected',
|
||||
}"
|
||||
disabled
|
||||
style="pointer-events: auto"
|
||||
title="Session state"
|
||||
>
|
||||
<i class="bi bi-arrow-left-right" style="font-size: 0.8rem"></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-1"
|
||||
id="arq_state"
|
||||
type="button"
|
||||
title="Data channel state"
|
||||
v-bind:class="{
|
||||
'bg-secondary': state.arq_state === 'False',
|
||||
'bg-warning': state.arq_state === 'True',
|
||||
}"
|
||||
disabled
|
||||
style="pointer-events: auto"
|
||||
>
|
||||
<i class="bi bi-file-earmark-binary" style="font-size: 0.8rem"></i>
|
||||
</button>
|
||||
<!--
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-1"
|
||||
id="rigctld_state"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
title="rigctld state: <strong class='text-success'>CONNECTED</strong> / <strong class='text-secondary'>UNKNOWN</strong>"
|
||||
>
|
||||
<i class="bi bi-usb-symbol" style="font-size: 0.8rem"></i>
|
||||
</button>
|
||||
-->
|
||||
|
||||
<button
|
||||
class="btn btn-sm disabled me-3"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
v-bind:class="{
|
||||
'btn-warning': state.channel_busy === 'True',
|
||||
'btn-secondary': state.channel_busy === 'False',
|
||||
}"
|
||||
style="pointer-events: auto"
|
||||
title="Channel busy"
|
||||
>
|
||||
<i class="bi bi-hourglass"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="btn-group btn-group-sm me-1" role="group">
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-4 disabled"
|
||||
type="button"
|
||||
title="What's the frequency, Kenneth?"
|
||||
style="pointer-events: auto"
|
||||
>
|
||||
{{ parseInt(state.frequency) / 1000 }} KHz
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="btn-group btn-group-sm me-1" role="group">
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-0"
|
||||
type="button"
|
||||
title="Speed level"
|
||||
>
|
||||
<i class="bi bi-speedometer2" style="font-size: 1rem"></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-4 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
>
|
||||
<i
|
||||
class="bi"
|
||||
style="font-size: 1rem"
|
||||
v-bind:class="{
|
||||
'bi-reception-0': state.speed_level == 0,
|
||||
'bi-reception-1': state.speed_level == 1,
|
||||
'bi-reception-2': state.speed_level == 2,
|
||||
'bi-reception-3': state.speed_level == 3,
|
||||
'bi-reception-4': state.speed_level == 4,
|
||||
}"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group btn-group-sm me-1" role="group">
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-0"
|
||||
type="button"
|
||||
title="Bytes transfered"
|
||||
>
|
||||
<i class="bi bi-file-earmark-binary" style="font-size: 1rem"></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-4 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
>
|
||||
{{ state.arq_total_bytes }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group btn-group-sm me-1" role="group">
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-0"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
title="Current or last connected with station"
|
||||
>
|
||||
<i class="bi bi-file-earmark-binary" style="font-size: 1rem"></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm btn-secondary disabled me-1"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
>
|
||||
{{ state.dxcallsign }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<div style="margin-right: 2px">
|
||||
<div
|
||||
class="progress w-100 rounded-0 rounded-top"
|
||||
style="height: 20px; min-width: 200px"
|
||||
>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-primary force-gpu"
|
||||
id="transmission_progress"
|
||||
role="progressbar"
|
||||
:style="{ width: state.arq_transmission_percent + '%' }"
|
||||
aria-valuenow="0"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<p
|
||||
class="justify-content-center m-0 d-flex position-absolute w-100 text-dark"
|
||||
>
|
||||
{{ state.arq_seconds_until_finish }}s left
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="progress mb-0 rounded-0 rounded-bottom"
|
||||
style="height: 10px"
|
||||
>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-warning"
|
||||
id="transmission_timeleft"
|
||||
role="progressbar"
|
||||
:style="{ width: state.arq_seconds_until_timeout_percent + '%' }"
|
||||
aria-valuenow="0"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
>
|
||||
<p
|
||||
class="justify-content-center m-0 d-flex position-absolute w-100 text-dark"
|
||||
>
|
||||
timeout in {{ state.arq_seconds_until_timeout }}s
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
ww
|
1214
gui/src/components/main_modals.vue
Normal file
1214
gui/src/components/main_modals.vue
Normal file
File diff suppressed because it is too large
Load diff
124
gui/src/components/main_my_station.vue
Normal file
124
gui/src/components/main_my_station.vue
Normal file
|
@ -0,0 +1,124 @@
|
|||
<script setup lang="ts">
|
||||
import { saveSettingsToFile } from "../js/settingsHandler";
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useSettingsStore } from "../store/settingsStore.js";
|
||||
const settings = useSettingsStore(pinia);
|
||||
|
||||
function saveSettings() {
|
||||
saveSettingsToFile();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card mb-1">
|
||||
<div class="card-header p-1">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
<i class="bi bi-house-door" style="font-size: 1.2rem"></i>
|
||||
</div>
|
||||
<div class="col-10">
|
||||
<strong class="fs-5">My station</strong>
|
||||
</div>
|
||||
<div class="col-1 text-end">
|
||||
<button
|
||||
type="button"
|
||||
id="openHelpModalStation"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#stationHelpModal"
|
||||
class="btn m-0 p-0 border-0"
|
||||
>
|
||||
<i class="bi bi-question-circle" style="font-size: 1rem"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-2">
|
||||
<div class="row">
|
||||
<div class="col-md-auto">
|
||||
<div
|
||||
class="input-group input-group-sm mb-0"
|
||||
data-bs-placement="bottom"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="false"
|
||||
title="Enter your callsign and save it"
|
||||
>
|
||||
<span class="input-group-text">
|
||||
<i class="bi bi-person-bounding-box" style="font-size: 1rem"></i>
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
style="width: 5rem; text-transform: uppercase"
|
||||
placeholder="callsign"
|
||||
pattern="[A-Z]*"
|
||||
id="myCall"
|
||||
maxlength="8"
|
||||
aria-label="Input group"
|
||||
aria-describedby="btnGroupAddon"
|
||||
v-model="settings.mycall"
|
||||
@input="saveSettings"
|
||||
/>
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
id="myCallSSID"
|
||||
v-model="settings.myssid"
|
||||
@change="saveSettings"
|
||||
>
|
||||
<option selected value="0">0</option>
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="3">3</option>
|
||||
<option value="4">4</option>
|
||||
<option value="5">5</option>
|
||||
<option value="6">6</option>
|
||||
<option value="7">7</option>
|
||||
<option value="8">8</option>
|
||||
<option value="9">9</option>
|
||||
<option value="10">10</option>
|
||||
<option value="11">11</option>
|
||||
<option value="12">12</option>
|
||||
<option value="13">13</option>
|
||||
<option value="14">14</option>
|
||||
<option value="15">15</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-auto">
|
||||
<div
|
||||
class="input-group input-group-sm mb-0"
|
||||
data-bs-placement="bottom"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="false"
|
||||
title="Enter your gridsquare and save it"
|
||||
>
|
||||
<span class="input-group-text">
|
||||
<i class="bi bi-house-fill" style="font-size: 1rem"></i>
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control mr-1"
|
||||
style="max-width: 6rem"
|
||||
placeholder="locator"
|
||||
id="myGrid"
|
||||
maxlength="6"
|
||||
aria-label="Input group"
|
||||
aria-describedby="btnGroupAddon"
|
||||
v-model="settings.mygrid"
|
||||
@input="saveSettings"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end of row-->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
276
gui/src/components/main_rig_control.vue
Normal file
276
gui/src/components/main_rig_control.vue
Normal file
|
@ -0,0 +1,276 @@
|
|||
<script setup lang="ts">
|
||||
import { saveSettingsToFile } from "../js/settingsHandler";
|
||||
|
||||
import { startRigctld, stopRigctld } from "../js/daemon";
|
||||
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useSettingsStore } from "../store/settingsStore.js";
|
||||
const settings = useSettingsStore(pinia);
|
||||
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
function startStopRigctld() {
|
||||
switch (state.rigctld_started) {
|
||||
case "stopped":
|
||||
|
||||
settings.hamlib_deviceport = (<HTMLInputElement>document.getElementById("hamlib_deviceport")).value;
|
||||
|
||||
startRigctld();
|
||||
|
||||
break;
|
||||
case "running":
|
||||
stopRigctld();
|
||||
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
function selectRadioControl() {
|
||||
// @ts-expect-error
|
||||
switch (event.target.id) {
|
||||
case "list-rig-control-none-list":
|
||||
settings.radiocontrol = "disabled";
|
||||
break;
|
||||
case "list-rig-control-rigctld-list":
|
||||
settings.radiocontrol = "rigctld";
|
||||
break;
|
||||
case "list-rig-control-tci-list":
|
||||
settings.radiocontrol = "tci";
|
||||
break;
|
||||
default:
|
||||
console.log("default=!==");
|
||||
settings.radiocontrol = "disabled";
|
||||
}
|
||||
saveSettingsToFile();
|
||||
}
|
||||
|
||||
|
||||
function testHamlib(){
|
||||
|
||||
console.log("not yet implemented")
|
||||
alert("not yet implemented")
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card mb-0">
|
||||
<div class="card-header p-1">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
<i class="bi bi-projector" style="font-size: 1.2rem"></i>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<strong class="fs-5">Rig control</strong>
|
||||
</div>
|
||||
|
||||
<div class="col-6">
|
||||
<div
|
||||
class="list-group list-group-horizontal"
|
||||
id="rig-control-list-tab"
|
||||
role="rig-control-tablist"
|
||||
>
|
||||
<a
|
||||
class="py-1 ps-1 pe-1 list-group-item list-group-item-action"
|
||||
id="list-rig-control-none-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-rig-control-none"
|
||||
role="tab"
|
||||
aria-controls="list-rig-control-none"
|
||||
v-bind:class="{ active: settings.radiocontrol === 'disabled' }"
|
||||
@click="selectRadioControl()"
|
||||
>None/Vox</a
|
||||
>
|
||||
<a
|
||||
class="py-1 ps-1 pe-1 list-group-item list-group-item-action"
|
||||
id="list-rig-control-rigctld-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-rig-control-rigctld"
|
||||
role="tab"
|
||||
aria-controls="list-rig-control-rigctld"
|
||||
v-bind:class="{ active: settings.radiocontrol === 'rigctld' }"
|
||||
@click="selectRadioControl()"
|
||||
>Rigctld</a
|
||||
>
|
||||
<a
|
||||
class="py-1 ps-1 pe-1 list-group-item list-group-item-action"
|
||||
id="list-rig-control-tci-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-rig-control-tci"
|
||||
role="tab"
|
||||
aria-controls="list-rig-control-tci"
|
||||
v-bind:class="{ active: settings.radiocontrol === 'tci' }"
|
||||
@click="selectRadioControl()"
|
||||
>TCI</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-1 text-end">
|
||||
<button
|
||||
type="button"
|
||||
id="openHelpModalRigControl"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#rigcontrolHelpModal"
|
||||
class="btn m-0 p-0 border-0"
|
||||
>
|
||||
<i class="bi bi-question-circle" style="font-size: 1rem"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-2" style="height: 100px">
|
||||
<div class="tab-content" id="rig-control-nav-tabContent">
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
v-bind:class="{ 'show active': settings.radiocontrol === 'disabled' }"
|
||||
id="list-rig-control-none"
|
||||
role="tabpanel"
|
||||
aria-labelledby="list-rig-control-none-list"
|
||||
>
|
||||
<p class="small">
|
||||
Modem will not utilize rig control and features will be limited. While
|
||||
functional; it is recommended to configure hamlib.
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
id="list-rig-control-rigctld"
|
||||
v-bind:class="{ 'show active': settings.radiocontrol === 'rigctld' }"
|
||||
role="tabpanel"
|
||||
aria-labelledby="list-rig-control-rigctld-list"
|
||||
>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
|
||||
<span class="input-group-text"> Radio port </span>
|
||||
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
id="hamlib_deviceport"
|
||||
style="width: 7rem"
|
||||
v-html="settings.getSerialDevices()"
|
||||
>
|
||||
|
||||
</select>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text">Rigctld service</span>
|
||||
<button
|
||||
class="btn btn-outline-success"
|
||||
type="button"
|
||||
id="hamlib_rigctld_start"
|
||||
@click="startStopRigctld"
|
||||
>
|
||||
Start
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-outline-danger"
|
||||
type="button"
|
||||
id="hamlib_rigctld_stop"
|
||||
@click="startStopRigctld"
|
||||
|
||||
>
|
||||
Stop
|
||||
</button>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Status"
|
||||
id="hamlib_rigctld_status"
|
||||
aria-label="State"
|
||||
aria-describedby="basic-addon1"
|
||||
v-model="state.rigctld_started"
|
||||
|
||||
/>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
id="testHamlib"
|
||||
class="btn btn-sm btn-outline-secondary ms-1"
|
||||
data-bs-placement="bottom"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
@click="testHamlib"
|
||||
title="Test your hamlib settings and toggle PTT once. Button will become <strong class='text-success'>green</strong> on success and <strong class='text-danger'>red</strong> if fails."
|
||||
>
|
||||
PTT Test
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
id="list-rig-control-tci"
|
||||
v-bind:class="{ 'show active': settings.radiocontrol === 'tci' }"
|
||||
role="tabpanel"
|
||||
aria-labelledby="list-rig-control-tci-list"
|
||||
>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text">TCI</span>
|
||||
<span class="input-group-text">Address</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="tci IP"
|
||||
id="tci_ip"
|
||||
aria-label="Device IP"
|
||||
v-model="settings.tci_ip"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text">Port</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="tci port"
|
||||
id="tci_port"
|
||||
aria-label="Device Port"
|
||||
v-model="settings.tci_port"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- RADIO CONTROL DISABLED -->
|
||||
<div id="radio-control-disabled"></div>
|
||||
|
||||
<!-- RADIO CONTROL RIGCTLD -->
|
||||
<div id="radio-control-rigctld"></div>
|
||||
<!-- RADIO CONTROL TCI-->
|
||||
<div id="radio-control-tci"></div>
|
||||
<!-- RADIO CONTROL HELP -->
|
||||
<div id="radio-control-help">
|
||||
<!--
|
||||
<strong>VOX:</strong> Use rig control mode 'none'
|
||||
<br />
|
||||
<strong>HAMLIB locally:</strong> configure in settings, then
|
||||
start/stop service.
|
||||
<br />
|
||||
<strong>HAMLIB remotely:</strong> Enter IP/Port, connection
|
||||
happens automatically.
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
<!--<div class="card-footer text-muted small" id="hamlib_info_field">
|
||||
Define Modem rig control mode (none/hamlib)
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
</template>
|
128
gui/src/components/main_top_navbar.vue
Normal file
128
gui/src/components/main_top_navbar.vue
Normal file
|
@ -0,0 +1,128 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
import { useSettingsStore } from "../store/settingsStore.js";
|
||||
const settings = useSettingsStore(pinia);
|
||||
|
||||
import { useAudioStore } from "../store/audioStore.js";
|
||||
const audioStore = useAudioStore(pinia);
|
||||
|
||||
import { startModem, stopModem } from "../js/daemon";
|
||||
import { saveSettingsToFile } from "../js/settingsHandler";
|
||||
|
||||
function startStopModem() {
|
||||
switch (state.modem_running_state) {
|
||||
case "stopped":
|
||||
|
||||
let startupInputDeviceValue = parseInt((<HTMLSelectElement>document.getElementById("audio_input_selectbox")).value);
|
||||
let startupOutputDeviceValue = parseInt((<HTMLSelectElement>document.getElementById("audio_output_selectbox")).value);
|
||||
|
||||
let startupInputDeviceIndex = (<HTMLSelectElement>document.getElementById("audio_input_selectbox")).selectedIndex;
|
||||
let startupOutputDeviceIndex = (<HTMLSelectElement>document.getElementById("audio_output_selectbox")).selectedIndex;
|
||||
|
||||
|
||||
audioStore.startupInputDevice = startupInputDeviceValue
|
||||
audioStore.startupOutputDevice = startupOutputDeviceValue
|
||||
|
||||
// get full name of audio device
|
||||
settings.rx_audio = (<HTMLSelectElement>document.getElementById("audio_input_selectbox")).options[startupInputDeviceIndex].text;
|
||||
settings.tx_audio = (<HTMLSelectElement>document.getElementById("audio_output_selectbox")).options[startupOutputDeviceIndex].text;
|
||||
|
||||
|
||||
saveSettingsToFile();
|
||||
|
||||
startModem();
|
||||
|
||||
break;
|
||||
case "running":
|
||||
stopModem();
|
||||
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav class="navbar bg-body-tertiary border-bottom">
|
||||
<div class="mx-auto">
|
||||
<span class="badge bg-secondary me-4"
|
||||
>Modem location | {{ settings.modem_host }}</span
|
||||
>
|
||||
|
||||
<span class="badge bg-secondary me-4"
|
||||
>Service | {{ state.modem_running_state }}</span
|
||||
>
|
||||
|
||||
<div class="btn-group" role="group"></div>
|
||||
<div class="btn-group me-4" role="group">
|
||||
<button
|
||||
type="button"
|
||||
id="startModem"
|
||||
class="btn btn-sm btn-outline-success"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="false"
|
||||
title="Start the Modem. Please set your audio and radio settings first!"
|
||||
@click="startStopModem()"
|
||||
v-bind:class="{ disabled: state.modem_running_state === 'running' }"
|
||||
>
|
||||
<i class="bi bi-play-fill"></i>
|
||||
<span class="ms-2">start modem</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
id="stopModem"
|
||||
class="btn btn-sm btn-outline-danger"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="false"
|
||||
title="Stop the Modem."
|
||||
@click="startStopModem()"
|
||||
v-bind:class="{ disabled: state.modem_running_state === 'stopped' }"
|
||||
>
|
||||
<i class="bi bi-stop-fill"></i>
|
||||
<span class="ms-2">stop modem</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
id="openHelpModalStartStopModem"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#startStopModemHelpModal"
|
||||
class="btn me-4 p-0 border-0"
|
||||
>
|
||||
<i class="bi bi-question-circle" style="font-size: 1rem"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
<div class="btn-toolbar" role="toolbar">
|
||||
|
||||
<span data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-trigger="hover" data-bs-html="false"
|
||||
title="View the received files. This is currently under development!">
|
||||
|
||||
|
||||
<button class="btn btn-sm btn-primary me-2" data-bs-toggle="offcanvas" data-bs-target="#receivedFilesSidebar" id="openReceivedFiles" type="button" > <strong>Files </strong>
|
||||
<i class="bi bi-file-earmark-arrow-up-fill" style="font-size: 1rem; color: white;"></i>
|
||||
<i class="bi bi-file-earmark-arrow-down-fill" style="font-size: 1rem; color: white;"></i>
|
||||
</button>
|
||||
</span> <span data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-trigger="hover" data-bs-html="false" title="Send files through HF. This is currently under development!">
|
||||
<button class="btn btn-sm btn-primary me-2" id="openDataModule" data-bs-toggle="offcanvas" data-bs-target="#transmitFileSidebar" type="button" style="display: None;"> <strong>TX File </strong>
|
||||
<i class="bi bi-file-earmark-arrow-up-fill" style="font-size: 1rem; color: white;"></i>
|
||||
</button>
|
||||
|
||||
</span> <span data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-trigger="hover" data-bs-html="true"
|
||||
title="Settings and Info">
|
||||
|
||||
</span>
|
||||
</div>
|
||||
--></nav>
|
||||
</template>
|
87
gui/src/components/main_updater.vue
Normal file
87
gui/src/components/main_updater.vue
Normal file
|
@ -0,0 +1,87 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useSettingsStore } from "../store/settingsStore.js";
|
||||
const settings = useSettingsStore(pinia);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card mb-0">
|
||||
<div class="card-header p-1 d-flex">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
<i class="bi bi-cloud-download" style="font-size: 1.2rem"></i>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<strong class="fs-5">Updater</strong>
|
||||
</div>
|
||||
<div class="col-7">
|
||||
<div class="progress w-100 ms-1 m-1">
|
||||
<div
|
||||
class="progress-bar"
|
||||
style="width: 0%"
|
||||
role="progressbar"
|
||||
id="UpdateProgressBar"
|
||||
aria-valuenow="0"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
>
|
||||
<span id="UpdateProgressInfo"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-1 text-end">
|
||||
<button
|
||||
type="button"
|
||||
id="openHelpModalUpdater"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#updaterHelpModal"
|
||||
class="btn m-0 p-0 border-0"
|
||||
>
|
||||
<i class="bi bi-question-circle" style="font-size: 1rem"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-2 mb-1">
|
||||
<button
|
||||
class="btn btn-secondary btn-sm ms-1 me-1"
|
||||
id="updater_channel"
|
||||
type="button"
|
||||
disabled
|
||||
>
|
||||
{{ settings.update_channel }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-secondary btn-sm ms-1"
|
||||
id="updater_status"
|
||||
type="button"
|
||||
disabled
|
||||
>
|
||||
...
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-secondary btn-sm ms-1"
|
||||
id="updater_changelog"
|
||||
type="button"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#updaterReleaseNotes"
|
||||
>
|
||||
Changelog
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-primary btn-sm ms-1"
|
||||
id="update_and_install"
|
||||
type="button"
|
||||
style="display: none"
|
||||
>
|
||||
Install & Restart
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
171
gui/src/components/settings.vue
Normal file
171
gui/src/components/settings.vue
Normal file
|
@ -0,0 +1,171 @@
|
|||
<script setup lang="ts">
|
||||
import settings_gui from "./settings_gui.vue";
|
||||
import settings_chat from "./settings_chat.vue";
|
||||
import settings_hamlib from "./settings_hamlib.vue";
|
||||
import settings_modem from "./settings_modem.vue";
|
||||
import settings_web from "./settings_web.vue";
|
||||
import settings_exp from "./settings_exp.vue";
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
id="list-settings"
|
||||
role="tabpanel"
|
||||
aria-labelledby="list-settings-list"
|
||||
>
|
||||
<div class="container">
|
||||
<div class="badge text-bg-warning ms-3">
|
||||
<i class="bi bi-exclamation-triangle"></i> Please restart the modem
|
||||
after changing settings!
|
||||
</div>
|
||||
<!-- SETTINGS Nav tabs -->
|
||||
<ul class="nav nav-tabs" id="myTab" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
class="nav-link active"
|
||||
id="gui-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#gui"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="home"
|
||||
aria-selected="true"
|
||||
>
|
||||
GUI
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
class="nav-link"
|
||||
id="chat-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#chat"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="home"
|
||||
aria-selected="true"
|
||||
>
|
||||
Chat
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
class="nav-link"
|
||||
id="hamlib-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#hamlib"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="profile"
|
||||
aria-selected="false"
|
||||
>
|
||||
Hamlib
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
class="nav-link"
|
||||
id="modem-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#modem"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="profile"
|
||||
aria-selected="false"
|
||||
>
|
||||
Modem
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
class="nav-link"
|
||||
id="web-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#web"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="messages"
|
||||
aria-selected="false"
|
||||
>
|
||||
Web
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
class="nav-link"
|
||||
id="experiments-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#experiments"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="settings"
|
||||
aria-selected="false"
|
||||
>
|
||||
Exp
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- SETTINGS Nav Tab panes -->
|
||||
<div class="tab-content mt-1">
|
||||
<!-- GUI tab contents-->
|
||||
<div
|
||||
class="tab-pane active"
|
||||
id="gui"
|
||||
role="tabpanel"
|
||||
aria-labelledby="gui-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_gui />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="tab-pane"
|
||||
id="chat"
|
||||
role="tabpanel"
|
||||
aria-labelledby="chat-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_chat />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="tab-pane"
|
||||
id="hamlib"
|
||||
role="tabpanel"
|
||||
aria-labelledby="hamlib-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_hamlib />
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane"
|
||||
id="modem"
|
||||
role="tabpanel"
|
||||
aria-labelledby="modem-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_modem />
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane"
|
||||
id="web"
|
||||
role="tabpanel"
|
||||
aria-labelledby="web-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_web />
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane"
|
||||
id="experiments"
|
||||
role="tabpanel"
|
||||
aria-labelledby="experiments-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_exp />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
128
gui/src/components/settings_chat.vue
Normal file
128
gui/src/components/settings_chat.vue
Normal file
|
@ -0,0 +1,128 @@
|
|||
<script setup lang="ts">
|
||||
import { saveSettingsToFile } from "../js/settingsHandler";
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useSettingsStore } from "../store/settingsStore.js";
|
||||
const settings = useSettingsStore(pinia);
|
||||
|
||||
function saveSettings() {
|
||||
saveSettingsToFile();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Enable "is typing"</label>
|
||||
<label class="input-group-text w-50">
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="enable_is_writing"
|
||||
@change="saveSettings"
|
||||
v-model="settings.enable_is_writing"
|
||||
true-value="True"
|
||||
false-value="False"
|
||||
disabled
|
||||
/>
|
||||
<label class="form-check-label" for="GraphicsSwitch"
|
||||
>Additional broadcast burst</label
|
||||
>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Allow requesting "user profile"</label>
|
||||
<label class="input-group-text w-50">
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="enable_request_profile"
|
||||
@change="saveSettings"
|
||||
v-model="settings.enable_request_profile"
|
||||
true-value="True"
|
||||
false-value="False"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50"
|
||||
>Allow requesting "shared folder"</label
|
||||
>
|
||||
<label class="input-group-text w-50">
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="enable_request_shared_folder"
|
||||
@change="saveSettings"
|
||||
v-model="settings.enable_request_shared_folder"
|
||||
true-value="True"
|
||||
false-value="False"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Shared folder path</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control w-50"
|
||||
id="shared_folder_path"
|
||||
@change="saveSettings"
|
||||
v-model="settings.shared_folder_path"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50"
|
||||
>Enable auto retry on Beacon or Ping
|
||||
</label>
|
||||
|
||||
<label class="input-group-text w-50">
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="enable_auto_retry"
|
||||
@change="saveSettings"
|
||||
v-model="settings.enable_auto_retry"
|
||||
true-value="True"
|
||||
false-value="False"
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text w-50">message retry attempts</span>
|
||||
<select
|
||||
class="form-select form-select-sm w-50"
|
||||
id="max_retry_attempts"
|
||||
@change="saveSettings"
|
||||
v-model="settings.max_retry_attempts"
|
||||
disabled
|
||||
>
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="3">3</option>
|
||||
<option value="4">4</option>
|
||||
<option value="5">5</option>
|
||||
<option value="6">6</option>
|
||||
<option value="7">7</option>
|
||||
<option value="8">8</option>
|
||||
<option value="9">9</option>
|
||||
</select>
|
||||
</div>
|
||||
</template>
|
99
gui/src/components/settings_exp.vue
Normal file
99
gui/src/components/settings_exp.vue
Normal file
|
@ -0,0 +1,99 @@
|
|||
<script setup lang="ts">
|
||||
import { saveSettingsToFile } from "../js/settingsHandler";
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useSettingsStore } from "../store/settingsStore.js";
|
||||
const settings = useSettingsStore(pinia);
|
||||
|
||||
function saveSettings() {
|
||||
saveSettingsToFile();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Enable autotune</label>
|
||||
<label class="input-group-text w-50">
|
||||
<div class="form-check form-switch form-check-inline ms-2">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="autoTuneSwitch"
|
||||
@change="saveSettings"
|
||||
v-model="settings.auto_tune"
|
||||
true-value="True"
|
||||
false-value="False"
|
||||
/>
|
||||
<label class="form-check-label" for="autoTuneSwitch"
|
||||
>adjust ALC on TX</label
|
||||
>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Enable FSK mode</label>
|
||||
<label class="input-group-text w-50">
|
||||
<div class="form-check form-switch form-check-inline ms-2">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="fskModeSwitch"
|
||||
@change="saveSettings"
|
||||
v-model="settings.enable_fsk"
|
||||
true-value="True"
|
||||
false-value="False"
|
||||
disabled
|
||||
/>
|
||||
<label class="form-check-label" for="fskModeSwitch"
|
||||
>not available, yet</label
|
||||
>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Enable MESH protocol</label>
|
||||
<label class="input-group-text w-50">
|
||||
<div class="form-check form-switch form-check-inline ms-2">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="enableMeshSwitch"
|
||||
@change="saveSettings"
|
||||
v-model="settings.enable_mesh_features"
|
||||
true-value="True"
|
||||
false-value="False"
|
||||
/>
|
||||
<label class="form-check-label" for="enableMeshSwitch"
|
||||
>experimental! REALLY!</label
|
||||
>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Database maintenance</label>
|
||||
<label class="input-group-text w-50">
|
||||
<button
|
||||
class="btn btn-outline-secondary btn-sm w-50"
|
||||
id="btnCleanDB"
|
||||
type="button"
|
||||
disabled
|
||||
>
|
||||
Clean</button
|
||||
>
|
||||
<div
|
||||
class="spinner-border text-warning invisible"
|
||||
role="status"
|
||||
id="divCleanDBSpinner"
|
||||
></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="center">
|
||||
<div class="badge text-bg-danger">
|
||||
<i class="bi bi-shield-exclamation"></i> These options may not work and
|
||||
are for experienced users only!
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
158
gui/src/components/settings_gui.vue
Normal file
158
gui/src/components/settings_gui.vue
Normal file
|
@ -0,0 +1,158 @@
|
|||
<script setup lang="ts">
|
||||
import { saveSettingsToFile } from "../js/settingsHandler";
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useSettingsStore } from "../store/settingsStore.js";
|
||||
const settings = useSettingsStore(pinia);
|
||||
|
||||
function saveSettings() {
|
||||
saveSettingsToFile();
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text w-50">GUI theme</span>
|
||||
<select
|
||||
class="form-select form-select-sm w-50"
|
||||
id="theme_selector"
|
||||
@change="saveSettings"
|
||||
v-model="settings.theme"
|
||||
disabled
|
||||
>
|
||||
<option value="default_light">Default (light)</option>
|
||||
<option value="default_dark">Default (dark)</option>
|
||||
<option value="default_auto">Default (auto)</option>
|
||||
<option value="cerulean">Cerulean</option>
|
||||
<option value="cosmo">Cosmo</option>
|
||||
<option value="cyborg">Cyborg</option>
|
||||
<option value="darkly">Darkly</option>
|
||||
<option value="flatly">Flatly</option>
|
||||
<option value="journal">Journal</option>
|
||||
<option value="litera">Litera</option>
|
||||
<option value="lumen">Lumen</option>
|
||||
<option value="lux">Lux</option>
|
||||
<option value="materia">Materia</option>
|
||||
<option value="minty">Minty</option>
|
||||
<option value="morph">Morhp</option>
|
||||
<option value="pulse">Pulse</option>
|
||||
<option value="quartz">Quartz</option>
|
||||
<option value="sandstone">Sandstone</option>
|
||||
<option value="simplex">Simplex</option>
|
||||
<option value="sketchy">Sketchy</option>
|
||||
<option value="slate">Slate</option>
|
||||
<option value="solar">Solar</option>
|
||||
<option value="spacelab">Spacelab</option>
|
||||
<option value="superhero">Superhero</option>
|
||||
<option value="united">United</option>
|
||||
<option value="vapor">Vapor</option>
|
||||
<option value="yeti">Yeti</option>
|
||||
<option value="zephyr">Zephyr</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text w-50">Waterfall theme</span>
|
||||
<select
|
||||
class="form-select form-select-sm w-50"
|
||||
id="wftheme_selector"
|
||||
@change="saveSettings"
|
||||
v-model="settings.wftheme"
|
||||
disabled
|
||||
>
|
||||
<option value="2">Default</option>
|
||||
<option value="0">Turbo</option>
|
||||
<option value="1">Fosphor</option>
|
||||
<option value="3">Inferno</option>
|
||||
<option value="4">Magma</option>
|
||||
<option value="5">Jet</option>
|
||||
<option value="6">Binary</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Enable fancy GUI</label>
|
||||
<label class="input-group-text w-50">
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="GraphicsSwitch"
|
||||
@change="saveSettings"
|
||||
v-model="settings.high_graphics"
|
||||
true-value="True"
|
||||
false-value="False"
|
||||
disabled
|
||||
/>
|
||||
<label class="form-check-label" for="GraphicsSwitch"
|
||||
>Higher CPU Usage</label
|
||||
>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50" for="inputGroupFile02"
|
||||
>Received files folder</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control w-50"
|
||||
id="received_files_folder"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text w-50">Update channel</span>
|
||||
<select
|
||||
class="form-select form-select-sm w-50"
|
||||
id="update_channel_selector"
|
||||
@change="saveSettings"
|
||||
v-model="settings.update_channel"
|
||||
disabled
|
||||
>
|
||||
<option value="latest">stable</option>
|
||||
<option value="beta">beta</option>
|
||||
<option value="alpha">alpha</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Enable notifications</label>
|
||||
<label class="input-group-text w-50">
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="NotificationSwitch"
|
||||
@change="saveSettings"
|
||||
v-model="settings.enable_sys_notification"
|
||||
true-value="True"
|
||||
false-value="False"
|
||||
disabled
|
||||
/>
|
||||
<label class="form-check-label" for="NotificationSwitch"
|
||||
>Show system pop-ups</label
|
||||
>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Auto-start Modem/rigctld</label>
|
||||
<label class="input-group-text w-50">
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="AutoStartSwitch"
|
||||
@change="saveSettings"
|
||||
v-model="settings.auto_start"
|
||||
true-value="True"
|
||||
false-value="False"
|
||||
disabled
|
||||
/>
|
||||
<label class="form-check-label" for="AutoStartSwitch"
|
||||
>Start on app launch</label
|
||||
>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
522
gui/src/components/settings_hamlib.vue
Normal file
522
gui/src/components/settings_hamlib.vue
Normal file
|
@ -0,0 +1,522 @@
|
|||
<script setup lang="ts">
|
||||
import { saveSettingsToFile } from "../js/settingsHandler";
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useSettingsStore } from "../store/settingsStore.js";
|
||||
const settings = useSettingsStore(pinia);
|
||||
function saveSettings() {
|
||||
saveSettingsToFile();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<hr class="m-2" />
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">Rigctld path</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="rigctld Path"
|
||||
id="hamlib_rigctld_path"
|
||||
aria-label="Device IP"
|
||||
aria-describedby="basic-addon1"
|
||||
@change="saveSettings"
|
||||
v-model="settings.hamlib_rigctld_path"
|
||||
/>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px"
|
||||
>Rigctld server port</span
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="rigctld port"
|
||||
id="hamlib_rigctld_server_port"
|
||||
aria-label="Device Port"
|
||||
aria-describedby="basic-addon1"
|
||||
@change="saveSettings"
|
||||
v-model="settings.hamlib_rigctld_server_port"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">Rigctld remote ip</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="rigctld IP"
|
||||
id="hamlib_rigctld_ip"
|
||||
aria-label="Device IP"
|
||||
v-model="settings.hamlib_rigctld_ip"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px"
|
||||
>Rigctld remote port</span
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="rigctld port"
|
||||
id="hamlib_rigctld_port"
|
||||
aria-label="Device Port"
|
||||
v-model="settings.hamlib_rigctld_port"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<hr class="m-2" />
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px"> Radio model </span>
|
||||
<input
|
||||
class="form-control"
|
||||
list="datalistOptions"
|
||||
id="hamlib_deviceid"
|
||||
placeholder="Search radio..."
|
||||
style="width: 7rem"
|
||||
@change="saveSettings"
|
||||
v-model="settings.hamlib_deviceid"
|
||||
/>
|
||||
<datalist id="datalistOptions">
|
||||
<option selected value="-- ignore --">-- ignore --</option>
|
||||
<option value="2028">Kenwood TS480</option>
|
||||
<option value="1">Hamlib Dummy</option>
|
||||
<option value="2">Hamlib NET rigctl</option>
|
||||
<option value="4">FLRig FLRig</option>
|
||||
<option value="5">TRXManager TRXManager 5.7.630+</option>
|
||||
<option value="6">Hamlib Dummy No VFO</option>
|
||||
<option value="1001">Yaesu FT-847</option>
|
||||
<option value="1003">Yaesu FT-1000D</option>
|
||||
<option value="1004">Yaesu MARK-V FT-1000MP</option>
|
||||
<option value="1005">Yaesu FT-747GX</option>
|
||||
<option value="1006">Yaesu FT-757GX</option>
|
||||
<option value="1007">Yaesu FT-757GXII</option>
|
||||
<option value="1009">Yaesu FT-767GX</option>
|
||||
<option value="1010">Yaesu FT-736R</option>
|
||||
<option value="1011">Yaesu FT-840</option>
|
||||
<option value="1013">Yaesu FT-900</option>
|
||||
<option value="1014">Yaesu FT-920</option>
|
||||
<option value="1015">Yaesu FT-890</option>
|
||||
<option value="1016">Yaesu FT-990</option>
|
||||
<option value="1017">Yaesu FRG-100</option>
|
||||
<option value="1018">Yaesu FRG-9600</option>
|
||||
<option value="1019">Yaesu FRG-8800</option>
|
||||
<option value="1020">Yaesu FT-817</option>
|
||||
<option value="1021">Yaesu FT-100</option>
|
||||
<option value="1022">Yaesu FT-857</option>
|
||||
<option value="1023">Yaesu FT-897</option>
|
||||
<option value="1024">Yaesu FT-1000MP</option>
|
||||
<option value="1025">Yaesu MARK-V Field FT-1000MP</option>
|
||||
<option value="1026">Yaesu VR-5000</option>
|
||||
<option value="1027">Yaesu FT-450</option>
|
||||
<option value="1028">Yaesu FT-950</option>
|
||||
<option value="1029">Yaesu FT-2000</option>
|
||||
<option value="1030">Yaesu FTDX-9000</option>
|
||||
<option value="1031">Yaesu FT-980</option>
|
||||
<option value="1032">Yaesu FTDX-5000</option>
|
||||
<option value="1033">Vertex Standard VX-1700</option>
|
||||
<option value="1034">Yaesu FTDX-1200</option>
|
||||
<option value="1035">Yaesu FT-991</option>
|
||||
<option value="1036">Yaesu FT-891</option>
|
||||
<option value="1037">Yaesu FTDX-3000</option>
|
||||
<option value="1038">Yaesu FT-847UNI</option>
|
||||
<option value="1039">Yaesu FT-600</option>
|
||||
<option value="1040">Yaesu FTDX-101D</option>
|
||||
<option value="1041">Yaesu FT-818</option>
|
||||
<option value="1042">Yaesu FTDX-10</option>
|
||||
<option value="1043">Yaesu FT-897D</option>
|
||||
<option value="1044">Yaesu FTDX-101MP</option>
|
||||
<option value="2001">Kenwood TS-50S</option>
|
||||
<option value="2002">Kenwood TS-440S</option>
|
||||
<option value="2003">Kenwood TS-450S</option>
|
||||
<option value="2004">Kenwood TS-570D</option>
|
||||
<option value="2005">Kenwood TS-690S</option>
|
||||
<option value="2006">Kenwood TS-711</option>
|
||||
<option value="2007">Kenwood TS-790</option>
|
||||
<option value="2008">Kenwood TS-811</option>
|
||||
<option value="2009">Kenwood TS-850</option>
|
||||
<option value="2010">Kenwood TS-870S</option>
|
||||
<option value="2011">Kenwood TS-940S</option>
|
||||
<option value="2012">Kenwood TS-950S</option>
|
||||
<option value="2013">Kenwood TS-950SDX</option>
|
||||
<option value="2014">Kenwood TS-2000</option>
|
||||
<option value="2015">Kenwood R-5000</option>
|
||||
<option value="2016">Kenwood TS-570S</option>
|
||||
<option value="2017">Kenwood TH-D7A</option>
|
||||
<option value="2019">Kenwood TH-F6A</option>
|
||||
<option value="2020">Kenwood TH-F7E</option>
|
||||
<option value="2021">Elecraft K2</option>
|
||||
<option value="2022">Kenwood TS-930</option>
|
||||
<option value="2023">Kenwood TH-G71</option>
|
||||
<option value="2024">Kenwood TS-680S</option>
|
||||
<option value="2025">Kenwood TS-140S</option>
|
||||
<option value="2026">Kenwood TM-D700</option>
|
||||
<option value="2027">Kenwood TM-V7</option>
|
||||
<option value="2028">Kenwood TS-480</option>
|
||||
<option value="2029">Elecraft K3</option>
|
||||
<option value="2030">Kenwood TRC-80</option>
|
||||
<option value="2031">Kenwood TS-590S</option>
|
||||
<option value="2032">SigFox Transfox</option>
|
||||
<option value="2033">Kenwood TH-D72A</option>
|
||||
<option value="2034">Kenwood TM-D710(G)</option>
|
||||
<option value="2036">FlexRadio 6xxx</option>
|
||||
<option value="2037">Kenwood TS-590SG</option>
|
||||
<option value="2038">Elecraft XG3</option>
|
||||
<option value="2039">Kenwood TS-990s</option>
|
||||
<option value="2040">OpenHPSDR PiHPSDR</option>
|
||||
<option value="2041">Kenwood TS-890S</option>
|
||||
<option value="2042">Kenwood TH-D74</option>
|
||||
<option value="2043">Elecraft K3S</option>
|
||||
<option value="2044">Elecraft KX2</option>
|
||||
<option value="2045">Elecraft KX3</option>
|
||||
<option value="2046">Hilberling PT-8000A</option>
|
||||
<option value="2047">Elecraft K4</option>
|
||||
<option value="2048">FlexRadio/ANAN PowerSDR/Thetis</option>
|
||||
<option value="2049">Malachite DSP</option>
|
||||
<option value="3002">Icom IC-1275</option>
|
||||
<option value="3003">Icom IC-271</option>
|
||||
<option value="3004">Icom IC-275</option>
|
||||
<option value="3006">Icom IC-471</option>
|
||||
<option value="3007">Icom IC-475</option>
|
||||
<option value="3009">Icom IC-706</option>
|
||||
<option value="3010">Icom IC-706MkII</option>
|
||||
<option value="3011">Icom IC-706MkIIG</option>
|
||||
<option value="3012">Icom IC-707</option>
|
||||
<option value="3013">Icom IC-718</option>
|
||||
<option value="3014">Icom IC-725</option>
|
||||
<option value="3015">Icom IC-726</option>
|
||||
<option value="3016">Icom IC-728</option>
|
||||
<option value="3017">Icom IC-729</option>
|
||||
<option value="3019">Icom IC-735</option>
|
||||
<option value="3020">Icom IC-736</option>
|
||||
<option value="3021">Icom IC-737</option>
|
||||
<option value="3022">Icom IC-738</option>
|
||||
<option value="3023">Icom IC-746</option>
|
||||
<option value="3024">Icom IC-751</option>
|
||||
<option value="3026">Icom IC-756</option>
|
||||
<option value="3027">Icom IC-756PRO</option>
|
||||
<option value="3028">Icom IC-761</option>
|
||||
<option value="3029">Icom IC-765</option>
|
||||
<option value="3030">Icom IC-775</option>
|
||||
<option value="3031">Icom IC-781</option>
|
||||
<option value="3032">Icom IC-820H</option>
|
||||
<option value="3034">Icom IC-821H</option>
|
||||
<option value="3035">Icom IC-970</option>
|
||||
<option value="3036">Icom IC-R10</option>
|
||||
<option value="3037">Icom IC-R71</option>
|
||||
<option value="3038">Icom IC-R72</option>
|
||||
<option value="3039">Icom IC-R75</option>
|
||||
<option value="3040">Icom IC-R7000</option>
|
||||
<option value="3041">Icom IC-R7100</option>
|
||||
<option value="3042">Icom ICR-8500</option>
|
||||
<option value="3043">Icom IC-R9000</option>
|
||||
<option value="3044">Icom IC-910</option>
|
||||
<option value="3045">Icom IC-78</option>
|
||||
<option value="3046">Icom IC-746PRO</option>
|
||||
<option value="3047">Icom IC-756PROII</option>
|
||||
<option value="3051">Ten-Tec Omni VI Plus</option>
|
||||
<option value="3052">Optoelectronics OptoScan535</option>
|
||||
<option value="3053">Optoelectronics OptoScan456</option>
|
||||
<option value="3054">Icom IC ID-1</option>
|
||||
<option value="3055">Icom IC-703</option>
|
||||
<option value="3056">Icom IC-7800</option>
|
||||
<option value="3057">Icom IC-756PROIII</option>
|
||||
<option value="3058">Icom IC-R20</option>
|
||||
<option value="3060">Icom IC-7000</option>
|
||||
<option value="3061">Icom IC-7200</option>
|
||||
<option value="3062">Icom IC-7700</option>
|
||||
<option value="3063">Icom IC-7600</option>
|
||||
<option value="3064">Ten-Tec Delta II</option>
|
||||
<option value="3065">Icom IC-92D</option>
|
||||
<option value="3066">Icom IC-R9500</option>
|
||||
<option value="3067">Icom IC-7410</option>
|
||||
<option value="3068">Icom IC-9100</option>
|
||||
<option value="3069">Icom IC-RX7</option>
|
||||
<option value="3070">Icom IC-7100</option>
|
||||
<option value="3071">Icom ID-5100</option>
|
||||
<option value="3072">Icom IC-2730</option>
|
||||
<option value="3073">Icom IC-7300</option>
|
||||
<option value="3074">Microtelecom Perseus</option>
|
||||
<option value="3075">Icom IC-785x</option>
|
||||
<option value="3076">Xeigu X108G</option>
|
||||
<option value="3077">Icom IC-R6</option>
|
||||
<option value="3078">Icom IC-7610</option>
|
||||
<option value="3079">Icom IC-R8600</option>
|
||||
<option value="3080">Icom IC-R30</option>
|
||||
<option value="3081">Icom IC-9700</option>
|
||||
<option value="3082">Icom ID-4100</option>
|
||||
<option value="3083">Icom ID-31</option>
|
||||
<option value="3084">Icom ID-51</option>
|
||||
<option value="3085">Icom IC-705</option>
|
||||
<option value="4001">Icom IC-PCR1000</option>
|
||||
<option value="4002">Icom IC-PCR100</option>
|
||||
<option value="4003">Icom IC-PCR1500</option>
|
||||
<option value="4004">Icom IC-PCR2500</option>
|
||||
<option value="5001">AOR AR8200</option>
|
||||
<option value="5002">AOR AR8000</option>
|
||||
<option value="5003">AOR AR7030</option>
|
||||
<option value="5004">AOR AR5000</option>
|
||||
<option value="5005">AOR AR3030</option>
|
||||
<option value="5006">AOR AR3000A</option>
|
||||
<option value="5008">AOR AR2700</option>
|
||||
<option value="5013">AOR AR8600</option>
|
||||
<option value="5014">AOR AR5000A</option>
|
||||
<option value="5015">AOR AR7030 Plus</option>
|
||||
<option value="5016">AOR SR2200</option>
|
||||
<option value="6005">JRC NRD-525</option>
|
||||
<option value="6006">JRC NRD-535D</option>
|
||||
<option value="6007">JRC NRD-545 DSP</option>
|
||||
<option value="8001">Uniden BC780xlt</option>
|
||||
<option value="8002">Uniden BC245xlt</option>
|
||||
<option value="8003">Uniden BC895xlt</option>
|
||||
<option value="8004">Radio Shack PRO-2052</option>
|
||||
<option value="8006">Uniden BC250D</option>
|
||||
<option value="8010">Uniden BCD-396T</option>
|
||||
<option value="8011">Uniden BCD-996T</option>
|
||||
<option value="8012">Uniden BC898T</option>
|
||||
<option value="9002">Drake R-8A</option>
|
||||
<option value="9003">Drake R-8B</option>
|
||||
<option value="10004">Lowe HF-235</option>
|
||||
<option value="11003">Racal RA6790/GM</option>
|
||||
<option value="11005">Racal RA3702</option>
|
||||
<option value="12004">Watkins-Johnson WJ-8888</option>
|
||||
<option value="14002">Skanti TRP8000</option>
|
||||
<option value="14004">Skanti TRP 8255 S R</option>
|
||||
<option value="15001">Winradio WR-1000</option>
|
||||
<option value="15002">Winradio WR-1500</option>
|
||||
<option value="15003">Winradio WR-1550</option>
|
||||
<option value="15004">Winradio WR-3100</option>
|
||||
<option value="15005">Winradio WR-3150</option>
|
||||
<option value="15006">Winradio WR-3500</option>
|
||||
<option value="15007">Winradio WR-3700</option>
|
||||
<option value="15009">Winradio WR-G313</option>
|
||||
<option value="16001">Ten-Tec TT-550</option>
|
||||
<option value="16002">Ten-Tec TT-538 Jupiter</option>
|
||||
<option value="16003">Ten-Tec RX-320</option>
|
||||
<option value="16004">Ten-Tec RX-340</option>
|
||||
<option value="16005">Ten-Tec RX-350</option>
|
||||
<option value="16007">Ten-Tec TT-516 Argonaut V</option>
|
||||
<option value="16008">Ten-Tec TT-565 Orion</option>
|
||||
<option value="16009">Ten-Tec TT-585 Paragon</option>
|
||||
<option value="16011">Ten-Tec TT-588 Omni VII</option>
|
||||
<option value="16012">Ten-Tec RX-331</option>
|
||||
<option value="16013">Ten-Tec TT-599 Eagle</option>
|
||||
<option value="17001">Alinco DX-77</option>
|
||||
<option value="17002">Alinco DX-SR8</option>
|
||||
<option value="18001">Kachina 505DSP</option>
|
||||
<option value="22001">TAPR DSP-10</option>
|
||||
<option value="23001">Flex-radio SDR-1000</option>
|
||||
<option value="23003">DTTS Microwave Society DttSP IPC</option>
|
||||
<option value="23004">DTTS Microwave Society DttSP UDP</option>
|
||||
<option value="24001">RFT EKD-500</option>
|
||||
<option value="25001">Elektor Elektor 3/04</option>
|
||||
<option value="25002">SAT-Schneider DRT1</option>
|
||||
<option value="25003">Coding Technologies Digital World Traveller</option>
|
||||
<option value="25006">AmQRP DDS-60</option>
|
||||
<option value="25007">Elektor Elektor SDR-USB</option>
|
||||
<option value="25008">mRS miniVNA</option>
|
||||
<option value="25009">SoftRock Si570 AVR-USB</option>
|
||||
<option value="25011">KTH-SDR kit Si570 PIC-USB</option>
|
||||
<option value="25012">FiFi FiFi-SDR</option>
|
||||
<option value="25013">AMSAT-UK FUNcube Dongle</option>
|
||||
<option value="25014">N2ADR HiQSDR</option>
|
||||
<option value="25015">Funkamateur FA-SDR</option>
|
||||
<option value="25016">AE9RB Si570 Peaberry V1</option>
|
||||
<option value="25017">AE9RB Si570 Peaberry V2</option>
|
||||
<option value="25018">AMSAT-UK FUNcube Dongle Pro+</option>
|
||||
<option value="25019">HobbyPCB RS-HFIQ</option>
|
||||
<option value="26001">Video4Linux SW/FM radio</option>
|
||||
<option value="26002">Video4Linux2 SW/FM radio</option>
|
||||
<option value="27001">Rohde&Schwarz ESMC</option>
|
||||
<option value="27002">Rohde&Schwarz EB200</option>
|
||||
<option value="27003">Rohde&Schwarz XK2100</option>
|
||||
<option value="28001">Philips/Simoco PRM8060</option>
|
||||
<option value="29001">ADAT www.adat.ch ADT-200A</option>
|
||||
<option value="30001">Icom IC-M700PRO</option>
|
||||
<option value="30002">Icom IC-M802</option>
|
||||
<option value="30003">Icom IC-M710</option>
|
||||
<option value="30004">Icom IC-M803</option>
|
||||
<option value="31001">Dorji DRA818V</option>
|
||||
<option value="31002">Dorji DRA818U</option>
|
||||
<option value="32001">Barrett 2050</option>
|
||||
<option value="32002">Barrett 950</option>
|
||||
<option value="33001">ELAD FDM-DUO</option>
|
||||
</datalist>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">Serial speed</span>
|
||||
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
id="hamlib_serialspeed"
|
||||
@change="saveSettings"
|
||||
v-model="settings.hamlib_serialspeed"
|
||||
>
|
||||
<option selected value="ignore">-- ignore --</option>
|
||||
<option value="1200">1200</option>
|
||||
<option value="2400">2400</option>
|
||||
<option value="4800">4800</option>
|
||||
<option value="9600">9600</option>
|
||||
<option value="14400">14400</option>
|
||||
<option value="19200">19200</option>
|
||||
<option value="28800">28800</option>
|
||||
<option value="38400">38400</option>
|
||||
<option value="57600">57600</option>
|
||||
<option value="115200">115200</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">Data bits</span>
|
||||
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
id="hamlib_data_bits"
|
||||
@change="saveSettings"
|
||||
v-model="settings.hamlib_data_bits"
|
||||
>
|
||||
<option selected value="ignore">-- ignore --</option>
|
||||
<option value="7">7</option>
|
||||
<option value="8">8</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">Stop bits</span>
|
||||
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
id="hamlib_stop_bits"
|
||||
@change="saveSettings"
|
||||
v-model="settings.hamlib_stop_bits"
|
||||
>
|
||||
<option selected value="ignore">-- ignore --</option>
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">Serial handshake</span>
|
||||
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
id="hamlib_handshake"
|
||||
@change="saveSettings"
|
||||
v-model="settings.hamlib_handshake"
|
||||
>
|
||||
<option selected value="ignore">-- ignore --</option>
|
||||
<option value="None">None (Default)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">PTT device port</span>
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
id="hamlib_ptt_port"
|
||||
@change="saveSettings"
|
||||
v-model="settings.hamlib_ptt_port"
|
||||
>
|
||||
<option selected value="ignore">-- ignore --</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">PTT type</span>
|
||||
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
id="hamlib_pttprotocol"
|
||||
style="width: 0.5rem"
|
||||
@change="saveSettings"
|
||||
v-model="settings.hamlib_pttprotocol"
|
||||
>
|
||||
<option selected value="ignore">-- ignore --</option>
|
||||
<option value="NONE">NONE</option>
|
||||
<option value="RIG">RIG</option>
|
||||
<option value="USB">USB</option>
|
||||
<option value="RTS">Serial RTS</option>
|
||||
<option value="PARALLEL">Rig PARALLEL</option>
|
||||
<option value="MICDATA">Rig MICDATA</option>
|
||||
<option value="CM108">Rig CM108</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">DCD</span>
|
||||
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
id="hamlib_dcd"
|
||||
style="width: 0.5rem"
|
||||
@change="saveSettings"
|
||||
v-model="settings.hamlib_dcd"
|
||||
>
|
||||
<option selected value="ignore">-- ignore --</option>
|
||||
<option value="NONE">NONE</option>
|
||||
<option value="RIG">RIG/CAT</option>
|
||||
<option value="DSR">DSR</option>
|
||||
<option value="CTS">CTS</option>
|
||||
<option value="CD">CD</option>
|
||||
<option value="PARALLEL">PARALLEL</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">DTR</span>
|
||||
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
id="hamlib_dtrstate"
|
||||
style="width: 0.5rem"
|
||||
@change="saveSettings"
|
||||
v-model="settings.hamlib_dtrstate"
|
||||
>
|
||||
<option selected value="ignore">-- ignore --</option>
|
||||
<option value="OFF">OFF</option>
|
||||
<option value="ON">ON</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">Rigctld command</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="hamlib_rigctld_command"
|
||||
aria-label="Device Port"
|
||||
aria-describedby="basic-addon1"
|
||||
disabled
|
||||
placeholder="auto populated from above settings"
|
||||
/>
|
||||
<button
|
||||
class="btn btn-outline-secondary"
|
||||
type="button"
|
||||
id="btnHamlibCopyCommand"
|
||||
>
|
||||
<i id="btnHamlibCopyCommandBi" class="bi bi-clipboard"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px"
|
||||
>Rigctld custom arguments</span
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="not typically needed"
|
||||
id="hamlib_rigctld_custom_args"
|
||||
aria-label="Custom arguments"
|
||||
aria-describedby="basic-addon1"
|
||||
@change="saveSettings"
|
||||
v-model="settings.hamlib_rigctld_custom_args"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
216
gui/src/components/settings_modem.vue
Normal file
216
gui/src/components/settings_modem.vue
Normal file
|
@ -0,0 +1,216 @@
|
|||
<script setup lang="ts">
|
||||
import { saveSettingsToFile } from "../js/settingsHandler";
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useSettingsStore } from "../store/settingsStore.js";
|
||||
const settings = useSettingsStore(pinia);
|
||||
|
||||
function saveSettings() {
|
||||
saveSettingsToFile();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">modem port</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="modem port"
|
||||
id="modem_port"
|
||||
maxlength="5"
|
||||
max="65534"
|
||||
min="1025"
|
||||
@change="saveSettings"
|
||||
v-model="settings.modem_port"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">modem host</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="modem host"
|
||||
id="modem_port"
|
||||
@change="saveSettings"
|
||||
v-model="settings.modem_host"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">TX delay in ms</label>
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
id="tx_delay"
|
||||
@change="saveSettings"
|
||||
v-model="settings.tx_delay"
|
||||
>
|
||||
<option value="0">0</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
<option value="150">150</option>
|
||||
<option value="200">200</option>
|
||||
<option value="250">250</option>
|
||||
<option value="300">300</option>
|
||||
<option value="350">350</option>
|
||||
<option value="400">400</option>
|
||||
<option value="450">450</option>
|
||||
<option value="500">500</option>
|
||||
<option value="550">550</option>
|
||||
<option value="600">600</option>
|
||||
<option value="650">650</option>
|
||||
<option value="700">700</option>
|
||||
<option value="750">750</option>
|
||||
<option value="800">800</option>
|
||||
<option value="850">850</option>
|
||||
<option value="900">900</option>
|
||||
<option value="950">950</option>
|
||||
<option value="1000">1000</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-25">Tuning range</label>
|
||||
<label class="input-group-text">fmin</label>
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
id="tuning_range_fmin"
|
||||
@change="saveSettings"
|
||||
v-model="settings.tuning_range_fmin"
|
||||
>
|
||||
<option value="-50.0">-50.0</option>
|
||||
<option value="-100.0">-100.0</option>
|
||||
<option value="-150.0">-150.0</option>
|
||||
<option value="-200.0">-200.0</option>
|
||||
<option value="-250.0">-250.0</option>
|
||||
</select>
|
||||
<label class="input-group-text">fmax</label>
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
id="tuning_range_fmax"
|
||||
@change="saveSettings"
|
||||
v-model="settings.tuning_range_fmax"
|
||||
>
|
||||
<option value="50.0">50.0</option>
|
||||
<option value="100.0">100.0</option>
|
||||
<option value="150.0">150.0</option>
|
||||
<option value="200.0">200.0</option>
|
||||
<option value="250.0">250.0</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text w-50">Beacon interval</span>
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
id="beaconInterval"
|
||||
style="width: 6rem"
|
||||
@change="saveSettings"
|
||||
v-model="settings.beacon_interval"
|
||||
>
|
||||
<option value="60">60 secs</option>
|
||||
<option value="90">90 secs</option>
|
||||
<option value="120">2 mins</option>
|
||||
<option selected value="300">5 mins</option>
|
||||
<option value="600">10 mins</option>
|
||||
<option value="900">15 mins</option>
|
||||
<option value="1800">30 mins</option>
|
||||
<option value="3600">60 mins</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Enable waterfall data</label>
|
||||
<label class="input-group-text w-50">
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="fftSwitch"
|
||||
@change="saveSettings"
|
||||
v-model="settings.enable_fft"
|
||||
true-value="True"
|
||||
false-value="False"
|
||||
/>
|
||||
<label class="form-check-label" for="fftSwitch">Waterfall</label>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Enable scatter diagram data</label>
|
||||
<label class="input-group-text w-50">
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="scatterSwitch"
|
||||
@change="saveSettings"
|
||||
v-model="settings.enable_scatter"
|
||||
true-value="True"
|
||||
false-value="False"
|
||||
/>
|
||||
<label class="form-check-label" for="scatterSwitch">Scatter</label>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Enable 250Hz bandwidth mode</label>
|
||||
<label class="input-group-text w-50">
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="250HzModeSwitch"
|
||||
v-model="settings.low_bandwidth_mode"
|
||||
true-value="True"
|
||||
false-value="False"
|
||||
@change="saveSettings"
|
||||
/>
|
||||
<label class="form-check-label" for="250HzModeSwitch">250Hz</label>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Respond to CQ</label>
|
||||
<label class="input-group-text w-50">
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="respondCQSwitch"
|
||||
v-model="settings.respond_to_cq"
|
||||
true-value="True"
|
||||
false-value="False"
|
||||
@change="saveSettings"
|
||||
/>
|
||||
<label class="form-check-label" for="respondCQSwitch">QRV</label>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">RX buffer size</label>
|
||||
<label class="input-group-text w-50">
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
id="rx_buffer_size"
|
||||
@change="saveSettings"
|
||||
v-model="settings.rx_buffer_size"
|
||||
>
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="4">4</option>
|
||||
<option value="8">8</option>
|
||||
<option value="16">16</option>
|
||||
<option value="32">32</option>
|
||||
<option value="64">64</option>
|
||||
<option value="128">128</option>
|
||||
<option value="256">256</option>
|
||||
<option value="512">512</option>
|
||||
<option value="1024">1024</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
53
gui/src/components/settings_web.vue
Normal file
53
gui/src/components/settings_web.vue
Normal file
|
@ -0,0 +1,53 @@
|
|||
<script setup lang="ts">
|
||||
import { saveSettingsToFile } from "../js/settingsHandler";
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useSettingsStore } from "../store/settingsStore.js";
|
||||
const settings = useSettingsStore(pinia);
|
||||
|
||||
function saveSettings() {
|
||||
saveSettingsToFile();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Explorer publishing</label>
|
||||
<label class="input-group-text w-50">
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="ExplorerSwitch"
|
||||
@change="saveSettings"
|
||||
v-model="settings.enable_explorer"
|
||||
true-value="True"
|
||||
false-value="False"
|
||||
/>
|
||||
<label class="form-check-label" for="ExplorerSwitch">Publish</label>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Explorer stats publishing</label>
|
||||
<label class="input-group-text w-50">
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="ExplorerStatsSwitch"
|
||||
@change="saveSettings"
|
||||
v-model="settings.explorer_stats"
|
||||
true-value="True"
|
||||
false-value="False"
|
||||
/>
|
||||
<label class="form-check-label" for="ExplorerStatsSwitch"
|
||||
>Publish stats</label
|
||||
>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
|
@ -1,285 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<!--<meta http-equiv="Content-Security-Policy" content="script-src 'self';">-->
|
||||
<!-- Bootstrap CSS -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="../node_modules/bootstrap/dist/css/bootstrap.min.css"
|
||||
/>
|
||||
<title>Send & Receive Data</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container-fluid">
|
||||
<div class="container mt-1">
|
||||
<div class="row mb-1">
|
||||
<div class="col">
|
||||
<div class="card text-dark bg-light mb-0">
|
||||
<div class="card-header">Select data</div>
|
||||
<div class="card-body">
|
||||
<div class="input-group input-group-sm mb-0">
|
||||
<input
|
||||
type="file"
|
||||
class="form-control"
|
||||
id="inputGroupFile02"
|
||||
/>
|
||||
<label class="input-group-text" for="inputGroupFile02"
|
||||
>kB</label
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--col-->
|
||||
</div>
|
||||
<!--row-->
|
||||
<div class="row mb-2">
|
||||
<div class="col">
|
||||
<div class="card text-dark bg-light mb-0">
|
||||
<div class="card-header">Transmission</div>
|
||||
<div class="card-body">
|
||||
<div class="row mb-2">
|
||||
<div class="col-auto">
|
||||
<div class="input-group input-group-sm">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
style="max-width: 6rem"
|
||||
placeholder="DX Call"
|
||||
id="dxCall"
|
||||
maxlength="6"
|
||||
aria-label="Input group example"
|
||||
aria-describedby="btnGroupAddon"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
id="sendPing"
|
||||
class="btn btn-primary"
|
||||
>
|
||||
Ping
|
||||
</button>
|
||||
<span class="input-group-text" id="tnc_running_state"
|
||||
>ACK</span
|
||||
>
|
||||
<span class="input-group-text" id="tnc_running_state"
|
||||
>0000 km</span
|
||||
>
|
||||
<span class="input-group-text" id="tnc_running_state"
|
||||
>0 dB</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-auto">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-text" id="basic-addon1"
|
||||
>Mode</span
|
||||
>
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm example"
|
||||
id="hamlib_deviceport"
|
||||
>
|
||||
<option selected value="DATAC1">DATAC1</option>
|
||||
<option value="DATAC3">DATAC3</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-text" id="basic-addon1"
|
||||
>Frames</span
|
||||
>
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm example"
|
||||
id="hamlib_deviceport"
|
||||
>
|
||||
<option selected value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="input-group input-group-sm">
|
||||
<button
|
||||
type="button"
|
||||
id="startTransmission"
|
||||
class="btn btn-success"
|
||||
>
|
||||
Send
|
||||
</button>
|
||||
<!--<button type="button" id="stopTNC"class="btn btn-danger">STOP</button>-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--col-->
|
||||
</div>
|
||||
<!--row-->
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="card text-dark bg-light mb-0">
|
||||
<div class="card-header">Info</div>
|
||||
<div class="card-body">123</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<hr />
|
||||
</div>
|
||||
</div>
|
||||
<!--row-->
|
||||
</div>
|
||||
<!--container-->
|
||||
</div>
|
||||
<!---------------------------------------------------------------------- FOOTER AREA ------------------------------------------------------------>
|
||||
<nav class="navbar fixed-bottom navbar-light bg-light">
|
||||
<div class="container-fluid">
|
||||
<div
|
||||
class="btn-toolbar"
|
||||
role="toolbar"
|
||||
aria-label="Toolbar with button groups"
|
||||
>
|
||||
<div
|
||||
class="btn-group btn-group-sm me-2"
|
||||
role="group"
|
||||
aria-label="First group"
|
||||
>
|
||||
<button class="btn btn-secondary" id="ptt_state" type="button">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
fill="currentColor"
|
||||
class="bi bi-broadcast-pin"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path
|
||||
d="M3.05 3.05a7 7 0 0 0 0 9.9.5.5 0 0 1-.707.707 8 8 0 0 1 0-11.314.5.5 0 0 1 .707.707zm2.122 2.122a4 4 0 0 0 0 5.656.5.5 0 1 1-.708.708 5 5 0 0 1 0-7.072.5.5 0 0 1 .708.708zm5.656-.708a.5.5 0 0 1 .708 0 5 5 0 0 1 0 7.072.5.5 0 1 1-.708-.708 4 4 0 0 0 0-5.656.5.5 0 0 1 0-.708zm2.122-2.12a.5.5 0 0 1 .707 0 8 8 0 0 1 0 11.313.5.5 0 0 1-.707-.707 7 7 0 0 0 0-9.9.5.5 0 0 1 0-.707zM6 8a2 2 0 1 1 2.5 1.937V15.5a.5.5 0 0 1-1 0V9.937A2 2 0 0 1 6 8z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="btn-group btn-group-sm me-2"
|
||||
role="group"
|
||||
aria-label="Second group"
|
||||
>
|
||||
<button class="btn btn-secondary" id="busy_state" type="button">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
fill="currentColor"
|
||||
class="bi bi-cpu"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path
|
||||
d="M5 0a.5.5 0 0 1 .5.5V2h1V.5a.5.5 0 0 1 1 0V2h1V.5a.5.5 0 0 1 1 0V2h1V.5a.5.5 0 0 1 1 0V2A2.5 2.5 0 0 1 14 4.5h1.5a.5.5 0 0 1 0 1H14v1h1.5a.5.5 0 0 1 0 1H14v1h1.5a.5.5 0 0 1 0 1H14v1h1.5a.5.5 0 0 1 0 1H14a2.5 2.5 0 0 1-2.5 2.5v1.5a.5.5 0 0 1-1 0V14h-1v1.5a.5.5 0 0 1-1 0V14h-1v1.5a.5.5 0 0 1-1 0V14h-1v1.5a.5.5 0 0 1-1 0V14A2.5 2.5 0 0 1 2 11.5H.5a.5.5 0 0 1 0-1H2v-1H.5a.5.5 0 0 1 0-1H2v-1H.5a.5.5 0 0 1 0-1H2v-1H.5a.5.5 0 0 1 0-1H2A2.5 2.5 0 0 1 4.5 2V.5A.5.5 0 0 1 5 0zm-.5 3A1.5 1.5 0 0 0 3 4.5v7A1.5 1.5 0 0 0 4.5 13h7a1.5 1.5 0 0 0 1.5-1.5v-7A1.5 1.5 0 0 0 11.5 3h-7zM5 6.5A1.5 1.5 0 0 1 6.5 5h3A1.5 1.5 0 0 1 11 6.5v3A1.5 1.5 0 0 1 9.5 11h-3A1.5 1.5 0 0 1 5 9.5v-3zM6.5 6a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="btn-group btn-group-sm me-2"
|
||||
role="group"
|
||||
aria-label="Second group"
|
||||
>
|
||||
<button class="btn btn-secondary" id="arq_state" type="button">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
fill="currentColor"
|
||||
class="bi bi-arrow-left-right"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M1 11.5a.5.5 0 0 0 .5.5h11.793l-3.147 3.146a.5.5 0 0 0 .708.708l4-4a.5.5 0 0 0 0-.708l-4-4a.5.5 0 0 0-.708.708L13.293 11H1.5a.5.5 0 0 0-.5.5zm14-7a.5.5 0 0 1-.5.5H2.707l3.147 3.146a.5.5 0 1 1-.708.708l-4-4a.5.5 0 0 1 0-.708l4-4a.5.5 0 1 1 .708.708L2.707 4H14.5a.5.5 0 0 1 .5.5z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="btn-group btn-group-sm me-2"
|
||||
role="group"
|
||||
aria-label="Third group"
|
||||
>
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
id="signalling_state"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
fill="currentColor"
|
||||
class="bi bi-journal-code"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M8.646 5.646a.5.5 0 0 1 .708 0l2 2a.5.5 0 0 1 0 .708l-2 2a.5.5 0 0 1-.708-.708L10.293 8 8.646 6.354a.5.5 0 0 1 0-.708zm-1.292 0a.5.5 0 0 0-.708 0l-2 2a.5.5 0 0 0 0 .708l2 2a.5.5 0 0 0 .708-.708L5.707 8l1.647-1.646a.5.5 0 0 0 0-.708z"
|
||||
/>
|
||||
<path
|
||||
d="M3 0h10a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2v-1h1v1a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v1H1V2a2 2 0 0 1 2-2z"
|
||||
/>
|
||||
<path
|
||||
d="M1 5v-.5a.5.5 0 0 1 1 0V5h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H1zm0 3v-.5a.5.5 0 0 1 1 0V8h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H1zm0 3v-.5a.5.5 0 0 1 1 0v.5h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H1z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="btn btn-secondary" id="data_state" type="button">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
fill="currentColor"
|
||||
class="bi bi-journal-richtext"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path
|
||||
d="M7.5 3.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0zm-.861 1.542 1.33.886 1.854-1.855a.25.25 0 0 1 .289-.047L11 4.75V7a.5.5 0 0 1-.5.5h-5A.5.5 0 0 1 5 7v-.5s1.54-1.274 1.639-1.208zM5 9.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5z"
|
||||
/>
|
||||
<path
|
||||
d="M3 0h10a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2v-1h1v1a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v1H1V2a2 2 0 0 1 2-2z"
|
||||
/>
|
||||
<path
|
||||
d="M1 5v-.5a.5.5 0 0 1 1 0V5h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H1zm0 3v-.5a.5.5 0 0 1 1 0V8h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H1zm0 3v-.5a.5.5 0 0 1 1 0v.5h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H1z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="input-group input-group-sm me-2">
|
||||
<span class="input-group-text" id="basic-addon1">Bytes/s</span>
|
||||
<span class="input-group-text" id="basic-addon1">----</span>
|
||||
</div>
|
||||
<div class="progress" style="height: 100%; width: 200px">
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-primary"
|
||||
id="arq-progress"
|
||||
role="progressbar"
|
||||
style="width: 25%"
|
||||
aria-valuenow="25"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
>
|
||||
25%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<script src="../node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,261 +0,0 @@
|
|||
<option value="1">Hamlib Dummy</option>
|
||||
<option value="2">Hamlib NET rigctl</option>
|
||||
<option value="4">FLRig FLRig</option>
|
||||
<option value="5">TRXManager TRXManager 5.7.630+</option>
|
||||
<option value="6">Hamlib Dummy No VFO</option>
|
||||
<option value="1001">Yaesu FT-847</option>
|
||||
<option value="1003">Yaesu FT-1000D</option>
|
||||
<option value="1004">Yaesu MARK-V FT-1000MP</option>
|
||||
<option value="1005">Yaesu FT-747GX</option>
|
||||
<option value="1006">Yaesu FT-757GX</option>
|
||||
<option value="1007">Yaesu FT-757GXII</option>
|
||||
<option value="1009">Yaesu FT-767GX</option>
|
||||
<option value="1010">Yaesu FT-736R</option>
|
||||
<option value="1011">Yaesu FT-840</option>
|
||||
<option value="1013">Yaesu FT-900</option>
|
||||
<option value="1014">Yaesu FT-920</option>
|
||||
<option value="1015">Yaesu FT-890</option>
|
||||
<option value="1016">Yaesu FT-990</option>
|
||||
<option value="1017">Yaesu FRG-100</option>
|
||||
<option value="1018">Yaesu FRG-9600</option>
|
||||
<option value="1019">Yaesu FRG-8800</option>
|
||||
<option value="1020">Yaesu FT-817</option>
|
||||
<option value="1021">Yaesu FT-100</option>
|
||||
<option value="1022">Yaesu FT-857</option>
|
||||
<option value="1023">Yaesu FT-897</option>
|
||||
<option value="1024">Yaesu FT-1000MP</option>
|
||||
<option value="1025">Yaesu MARK-V Field FT-1000MP</option>
|
||||
<option value="1026">Yaesu VR-5000</option>
|
||||
<option value="1027">Yaesu FT-450</option>
|
||||
<option value="1028">Yaesu FT-950</option>
|
||||
<option value="1029">Yaesu FT-2000</option>
|
||||
<option value="1030">Yaesu FTDX-9000</option>
|
||||
<option value="1031">Yaesu FT-980</option>
|
||||
<option value="1032">Yaesu FTDX-5000</option>
|
||||
<option value="1033">Vertex Standard VX-1700</option>
|
||||
<option value="1034">Yaesu FTDX-1200</option>
|
||||
<option value="1035">Yaesu FT-991</option>
|
||||
<option value="1036">Yaesu FT-891</option>
|
||||
<option value="1037">Yaesu FTDX-3000</option>
|
||||
<option value="1038">Yaesu FT-847UNI</option>
|
||||
<option value="1039">Yaesu FT-600</option>
|
||||
<option value="1040">Yaesu FTDX-101D</option>
|
||||
<option value="1041">Yaesu FT-818</option>
|
||||
<option value="1042">Yaesu FTDX-10</option>
|
||||
<option value="1043">Yaesu FT-897D</option>
|
||||
<option value="1044">Yaesu FTDX-101MP</option>
|
||||
<option value="2001">Kenwood TS-50S</option>
|
||||
<option value="2002">Kenwood TS-440S</option>
|
||||
<option value="2003">Kenwood TS-450S</option>
|
||||
<option value="2004">Kenwood TS-570D</option>
|
||||
<option value="2005">Kenwood TS-690S</option>
|
||||
<option value="2006">Kenwood TS-711</option>
|
||||
<option value="2007">Kenwood TS-790</option>
|
||||
<option value="2008">Kenwood TS-811</option>
|
||||
<option value="2009">Kenwood TS-850</option>
|
||||
<option value="2010">Kenwood TS-870S</option>
|
||||
<option value="2011">Kenwood TS-940S</option>
|
||||
<option value="2012">Kenwood TS-950S</option>
|
||||
<option value="2013">Kenwood TS-950SDX</option>
|
||||
<option value="2014">Kenwood TS-2000</option>
|
||||
<option value="2015">Kenwood R-5000</option>
|
||||
<option value="2016">Kenwood TS-570S</option>
|
||||
<option value="2017">Kenwood TH-D7A</option>
|
||||
<option value="2019">Kenwood TH-F6A</option>
|
||||
<option value="2020">Kenwood TH-F7E</option>
|
||||
<option value="2021">Elecraft K2</option>
|
||||
<option value="2022">Kenwood TS-930</option>
|
||||
<option value="2023">Kenwood TH-G71</option>
|
||||
<option value="2024">Kenwood TS-680S</option>
|
||||
<option value="2025">Kenwood TS-140S</option>
|
||||
<option value="2026">Kenwood TM-D700</option>
|
||||
<option value="2027">Kenwood TM-V7</option>
|
||||
<option value="2028">Kenwood TS-480</option>
|
||||
<option value="2029">Elecraft K3</option>
|
||||
<option value="2030">Kenwood TRC-80</option>
|
||||
<option value="2031">Kenwood TS-590S</option>
|
||||
<option value="2032">SigFox Transfox</option>
|
||||
<option value="2033">Kenwood TH-D72A</option>
|
||||
<option value="2034">Kenwood TM-D710(G)</option>
|
||||
<option value="2036">FlexRadio 6xxx</option>
|
||||
<option value="2037">Kenwood TS-590SG</option>
|
||||
<option value="2038">Elecraft XG3</option>
|
||||
<option value="2039">Kenwood TS-990s</option>
|
||||
<option value="2040">OpenHPSDR PiHPSDR</option>
|
||||
<option value="2041">Kenwood TS-890S</option>
|
||||
<option value="2042">Kenwood TH-D74</option>
|
||||
<option value="2043">Elecraft K3S</option>
|
||||
<option value="2044">Elecraft KX2</option>
|
||||
<option value="2045">Elecraft KX3</option>
|
||||
<option value="2046">Hilberling PT-8000A</option>
|
||||
<option value="2047">Elecraft K4</option>
|
||||
<option value="2048">FlexRadio/ANAN PowerSDR/Thetis</option>
|
||||
<option value="2049">Malachite DSP</option>
|
||||
<option value="3002">Icom IC-1275</option>
|
||||
<option value="3003">Icom IC-271</option>
|
||||
<option value="3004">Icom IC-275</option>
|
||||
<option value="3006">Icom IC-471</option>
|
||||
<option value="3007">Icom IC-475</option>
|
||||
<option value="3009">Icom IC-706</option>
|
||||
<option value="3010">Icom IC-706MkII</option>
|
||||
<option value="3011">Icom IC-706MkIIG</option>
|
||||
<option value="3012">Icom IC-707</option>
|
||||
<option value="3013">Icom IC-718</option>
|
||||
<option value="3014">Icom IC-725</option>
|
||||
<option value="3015">Icom IC-726</option>
|
||||
<option value="3016">Icom IC-728</option>
|
||||
<option value="3017">Icom IC-729</option>
|
||||
<option value="3019">Icom IC-735</option>
|
||||
<option value="3020">Icom IC-736</option>
|
||||
<option value="3021">Icom IC-737</option>
|
||||
<option value="3022">Icom IC-738</option>
|
||||
<option value="3023">Icom IC-746</option>
|
||||
<option value="3024">Icom IC-751</option>
|
||||
<option value="3026">Icom IC-756</option>
|
||||
<option value="3027">Icom IC-756PRO</option>
|
||||
<option value="3028">Icom IC-761</option>
|
||||
<option value="3029">Icom IC-765</option>
|
||||
<option value="3030">Icom IC-775</option>
|
||||
<option value="3031">Icom IC-781</option>
|
||||
<option value="3032">Icom IC-820H</option>
|
||||
<option value="3034">Icom IC-821H</option>
|
||||
<option value="3035">Icom IC-970</option>
|
||||
<option value="3036">Icom IC-R10</option>
|
||||
<option value="3037">Icom IC-R71</option>
|
||||
<option value="3038">Icom IC-R72</option>
|
||||
<option value="3039">Icom IC-R75</option>
|
||||
<option value="3040">Icom IC-R7000</option>
|
||||
<option value="3041">Icom IC-R7100</option>
|
||||
<option value="3042">Icom ICR-8500</option>
|
||||
<option value="3043">Icom IC-R9000</option>
|
||||
<option value="3044">Icom IC-910</option>
|
||||
<option value="3045">Icom IC-78</option>
|
||||
<option value="3046">Icom IC-746PRO</option>
|
||||
<option value="3047">Icom IC-756PROII</option>
|
||||
<option value="3051">Ten-Tec Omni VI Plus</option>
|
||||
<option value="3052">Optoelectronics OptoScan535</option>
|
||||
<option value="3053">Optoelectronics OptoScan456</option>
|
||||
<option value="3054">Icom IC ID-1</option>
|
||||
<option value="3055">Icom IC-703</option>
|
||||
<option value="3056">Icom IC-7800</option>
|
||||
<option value="3057">Icom IC-756PROIII</option>
|
||||
<option value="3058">Icom IC-R20</option>
|
||||
<option value="3060">Icom IC-7000</option>
|
||||
<option value="3061">Icom IC-7200</option>
|
||||
<option value="3062">Icom IC-7700</option>
|
||||
<option value="3063">Icom IC-7600</option>
|
||||
<option value="3064">Ten-Tec Delta II</option>
|
||||
<option value="3065">Icom IC-92D</option>
|
||||
<option value="3066">Icom IC-R9500</option>
|
||||
<option value="3067">Icom IC-7410</option>
|
||||
<option value="3068">Icom IC-9100</option>
|
||||
<option value="3069">Icom IC-RX7</option>
|
||||
<option value="3070">Icom IC-7100</option>
|
||||
<option value="3071">Icom ID-5100</option>
|
||||
<option value="3072">Icom IC-2730</option>
|
||||
<option value="3073">Icom IC-7300</option>
|
||||
<option value="3074">Microtelecom Perseus</option>
|
||||
<option value="3075">Icom IC-785x</option>
|
||||
<option value="3076">Xeigu X108G</option>
|
||||
<option value="3077">Icom IC-R6</option>
|
||||
<option value="3078">Icom IC-7610</option>
|
||||
<option value="3079">Icom IC-R8600</option>
|
||||
<option value="3080">Icom IC-R30</option>
|
||||
<option value="3081">Icom IC-9700</option>
|
||||
<option value="3082">Icom ID-4100</option>
|
||||
<option value="3083">Icom ID-31</option>
|
||||
<option value="3084">Icom ID-51</option>
|
||||
<option value="3085">Icom IC-705</option>
|
||||
<option value="4001">Icom IC-PCR1000</option>
|
||||
<option value="4002">Icom IC-PCR100</option>
|
||||
<option value="4003">Icom IC-PCR1500</option>
|
||||
<option value="4004">Icom IC-PCR2500</option>
|
||||
<option value="5001">AOR AR8200</option>
|
||||
<option value="5002">AOR AR8000</option>
|
||||
<option value="5003">AOR AR7030</option>
|
||||
<option value="5004">AOR AR5000</option>
|
||||
<option value="5005">AOR AR3030</option>
|
||||
<option value="5006">AOR AR3000A</option>
|
||||
<option value="5008">AOR AR2700</option>
|
||||
<option value="5013">AOR AR8600</option>
|
||||
<option value="5014">AOR AR5000A</option>
|
||||
<option value="5015">AOR AR7030 Plus</option>
|
||||
<option value="5016">AOR SR2200</option>
|
||||
<option value="6005">JRC NRD-525</option>
|
||||
<option value="6006">JRC NRD-535D</option>
|
||||
<option value="6007">JRC NRD-545 DSP</option>
|
||||
<option value="8001">Uniden BC780xlt</option>
|
||||
<option value="8002">Uniden BC245xlt</option>
|
||||
<option value="8003">Uniden BC895xlt</option>
|
||||
<option value="8004">Radio Shack PRO-2052</option>
|
||||
<option value="8006">Uniden BC250D</option>
|
||||
<option value="8010">Uniden BCD-396T</option>
|
||||
<option value="8011">Uniden BCD-996T</option>
|
||||
<option value="8012">Uniden BC898T</option>
|
||||
<option value="9002">Drake R-8A</option>
|
||||
<option value="9003">Drake R-8B</option>
|
||||
<option value="10004">Lowe HF-235</option>
|
||||
<option value="11003">Racal RA6790/GM</option>
|
||||
<option value="11005">Racal RA3702</option>
|
||||
<option value="12004">Watkins-Johnson WJ-8888</option>
|
||||
<option value="14002">Skanti TRP8000</option>
|
||||
<option value="14004">Skanti TRP 8255 S R</option>
|
||||
<option value="15001">Winradio WR-1000</option>
|
||||
<option value="15002">Winradio WR-1500</option>
|
||||
<option value="15003">Winradio WR-1550</option>
|
||||
<option value="15004">Winradio WR-3100</option>
|
||||
<option value="15005">Winradio WR-3150</option>
|
||||
<option value="15006">Winradio WR-3500</option>
|
||||
<option value="15007">Winradio WR-3700</option>
|
||||
<option value="15009">Winradio WR-G313</option>
|
||||
<option value="16001">Ten-Tec TT-550</option>
|
||||
<option value="16002">Ten-Tec TT-538 Jupiter</option>
|
||||
<option value="16003">Ten-Tec RX-320</option>
|
||||
<option value="16004">Ten-Tec RX-340</option>
|
||||
<option value="16005">Ten-Tec RX-350</option>
|
||||
<option value="16007">Ten-Tec TT-516 Argonaut V</option>
|
||||
<option value="16008">Ten-Tec TT-565 Orion</option>
|
||||
<option value="16009">Ten-Tec TT-585 Paragon</option>
|
||||
<option value="16011">Ten-Tec TT-588 Omni VII</option>
|
||||
<option value="16012">Ten-Tec RX-331</option>
|
||||
<option value="16013">Ten-Tec TT-599 Eagle</option>
|
||||
<option value="17001">Alinco DX-77</option>
|
||||
<option value="17002">Alinco DX-SR8</option>
|
||||
<option value="18001">Kachina 505DSP</option>
|
||||
<option value="22001">TAPR DSP-10</option>
|
||||
<option value="23001">Flex-radio SDR-1000</option>
|
||||
<option value="23003">DTTS Microwave Society DttSP IPC</option>
|
||||
<option value="23004">DTTS Microwave Society DttSP UDP</option>
|
||||
<option value="24001">RFT EKD-500</option>
|
||||
<option value="25001">Elektor Elektor 3/04</option>
|
||||
<option value="25002">SAT-Schneider DRT1</option>
|
||||
<option value="25003">Coding Technologies Digital World Traveller</option>
|
||||
<option value="25006">AmQRP DDS-60</option>
|
||||
<option value="25007">Elektor Elektor SDR-USB</option>
|
||||
<option value="25008">mRS miniVNA</option>
|
||||
<option value="25009">SoftRock Si570 AVR-USB</option>
|
||||
<option value="25011">KTH-SDR kit Si570 PIC-USB</option>
|
||||
<option value="25012">FiFi FiFi-SDR</option>
|
||||
<option value="25013">AMSAT-UK FUNcube Dongle</option>
|
||||
<option value="25014">N2ADR HiQSDR</option>
|
||||
<option value="25015">Funkamateur FA-SDR</option>
|
||||
<option value="25016">AE9RB Si570 Peaberry V1</option>
|
||||
<option value="25017">AE9RB Si570 Peaberry V2</option>
|
||||
<option value="25018">AMSAT-UK FUNcube Dongle Pro+</option>
|
||||
<option value="25019">HobbyPCB RS-HFIQ</option>
|
||||
<option value="26001">Video4Linux SW/FM radio</option>
|
||||
<option value="26002">Video4Linux2 SW/FM radio</option>
|
||||
<option value="27001">Rohde&Schwarz ESMC</option>
|
||||
<option value="27002">Rohde&Schwarz EB200</option>
|
||||
<option value="27003">Rohde&Schwarz XK2100</option>
|
||||
<option value="28001">Philips/Simoco PRM8060</option>
|
||||
<option value="29001">ADAT www.adat.ch ADT-200A</option>
|
||||
<option value="30001">Icom IC-M700PRO</option>
|
||||
<option value="30002">Icom IC-M802</option>
|
||||
<option value="30003">Icom IC-M710</option>
|
||||
<option value="30004">Icom IC-M803</option>
|
||||
<option value="31001">Dorji DRA818V</option>
|
||||
<option value="31002">Dorji DRA818U</option>
|
||||
<option value="32001">Barrett 2050</option>
|
||||
<option value="32002">Barrett 950</option>
|
||||
<option value="33001">ELAD FDM-DUO</option>
|
Binary file not shown.
Before Width: | Height: | Size: 110 KiB |
Binary file not shown.
Before Width: | Height: | Size: 590 KiB |
3956
gui/src/index.html
3956
gui/src/index.html
File diff suppressed because it is too large
Load diff
1006
gui/src/js/chatHandler.ts
Normal file
1006
gui/src/js/chatHandler.ts
Normal file
File diff suppressed because it is too large
Load diff
281
gui/src/js/daemon.ts
Normal file
281
gui/src/js/daemon.ts
Normal file
|
@ -0,0 +1,281 @@
|
|||
//var net = require("net");
|
||||
var net = require("node:net");
|
||||
|
||||
// ----------------- init pinia stores -------------
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
import { useAudioStore } from "../store/audioStore.js";
|
||||
const audioStore = useAudioStore(pinia);
|
||||
|
||||
import { useSettingsStore } from "../store/settingsStore.js";
|
||||
const settings = useSettingsStore(pinia);
|
||||
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
var daemon = new net.Socket();
|
||||
var socketchunk = []; // Current message, per connection.
|
||||
|
||||
// global to keep track of daemon connection error emissions
|
||||
var daemonShowConnectStateError = 1;
|
||||
|
||||
setTimeout(connectDAEMON, 500);
|
||||
|
||||
function connectDAEMON() {
|
||||
if (daemonShowConnectStateError == 1) {
|
||||
console.log("connecting to daemon");
|
||||
}
|
||||
|
||||
//clear message buffer after reconnecting or initial connection
|
||||
socketchunk = [];
|
||||
|
||||
daemon.connect(settings.daemon_port, settings.daemon_host);
|
||||
|
||||
//client.setTimeout(5000);
|
||||
}
|
||||
|
||||
daemon.on("connect", function () {
|
||||
console.log("daemon connection established");
|
||||
daemonShowConnectStateError = 1;
|
||||
});
|
||||
|
||||
daemon.on("error", function (err) {
|
||||
if (daemonShowConnectStateError == 1) {
|
||||
console.log("daemon connection error");
|
||||
console.log("Make sure the daemon is started.");
|
||||
console.log('Run "python daemon.py" in the modem directory.');
|
||||
console.log(err);
|
||||
daemonShowConnectStateError = 0;
|
||||
}
|
||||
setTimeout(connectDAEMON, 500);
|
||||
daemon.destroy();
|
||||
});
|
||||
|
||||
/*
|
||||
client.on('close', function(data) {
|
||||
console.log(' Modem connection closed');
|
||||
setTimeout(connectModem, 2000)
|
||||
let Data = {
|
||||
daemon_connection: daemon.readyState,
|
||||
};
|
||||
ipcRenderer.send('request-update-daemon-connection', Data);
|
||||
});
|
||||
*/
|
||||
|
||||
daemon.on("end", function (data) {
|
||||
console.log("daemon connection ended");
|
||||
console.log(data);
|
||||
daemon.destroy();
|
||||
setTimeout(connectDAEMON, 500);
|
||||
});
|
||||
|
||||
//exports.writeDaemonCommand = function(command){
|
||||
//writeDaemonCommand = function (command) {
|
||||
function writeDaemonCommand(command) {
|
||||
// we use the writingCommand function to update our TCPIP state because we are calling this function a lot
|
||||
// if socket opened, we are able to run commands
|
||||
if (daemon.readyState == "open") {
|
||||
//uiMain.setDAEMONconnection('open')
|
||||
daemon.write(command + "\n");
|
||||
}
|
||||
|
||||
if (daemon.readyState == "closed") {
|
||||
//uiMain.setDAEMONconnection('closed')
|
||||
}
|
||||
|
||||
if (daemon.readyState == "opening") {
|
||||
//uiMain.setDAEMONconnection('opening')
|
||||
}
|
||||
}
|
||||
|
||||
// "https://stackoverflow.com/questions/9070700/nodejs-net-createserver-large-amount-of-data-coming-in"
|
||||
|
||||
daemon.on("data", function (socketdata) {
|
||||
/*
|
||||
inspired by:
|
||||
stackoverflow.com questions 9070700 nodejs-net-createserver-large-amount-of-data-coming-in
|
||||
*/
|
||||
|
||||
socketdata = socketchunk.join("\n") + socketdata.toString("utf8"); //append incoming data to socketchunk
|
||||
//socketdata = socketdata.toString("utf8"); // convert data to string
|
||||
|
||||
//socketchunk += socketdata; // append data to buffer so we can stick long data together
|
||||
|
||||
// check if we received begin and end of json data
|
||||
if (socketdata.startsWith('{"') && socketdata.endsWith('"}\n')) {
|
||||
var data = "";
|
||||
|
||||
// split data into chunks if we received multiple commands
|
||||
socketchunk = socketdata.split("\n");
|
||||
data = JSON.parse(socketchunk[0]);
|
||||
|
||||
// search for empty entries in socketchunk and remove them
|
||||
for (var i = 0; i < socketchunk.length; i++) {
|
||||
if (socketchunk[i] === "") {
|
||||
socketchunk.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
//iterate through socketchunks array to execute multiple commands in row
|
||||
for (i = 0; i < socketchunk.length; i++) {
|
||||
//check if data is not empty
|
||||
if (socketchunk[i].length > 0) {
|
||||
//try to parse JSON
|
||||
try {
|
||||
data = JSON.parse(socketchunk[i]);
|
||||
} catch (e) {
|
||||
console.log(e); // "SyntaxError
|
||||
//daemonLog.debug(socketchunk[i]);
|
||||
socketchunk = [];
|
||||
}
|
||||
}
|
||||
|
||||
console.log(data);
|
||||
if (data["command"] == "daemon_state") {
|
||||
// update audio devices by putting them to audio store
|
||||
audioStore.inputDevices = data["input_devices"];
|
||||
audioStore.outputDevices = data["output_devices"];
|
||||
settings.serial_devices = data["serial_devices"];
|
||||
state.python_version = data["python_version"];
|
||||
state.modem_version = data["version"];
|
||||
state.modem_running_state = data["daemon_state"][0]["status"];
|
||||
state.rigctld_started = data["rigctld_state"][0]["status"];
|
||||
//state.rigctld_process = data["daemon_state"][0]["rigctld_process"];
|
||||
}
|
||||
|
||||
if (data["command"] == "test_hamlib") {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
//finally delete message buffer
|
||||
socketchunk = [];
|
||||
}
|
||||
});
|
||||
|
||||
// START Modem
|
||||
// ` `== multi line string
|
||||
export function startModem() {
|
||||
var json_command = JSON.stringify({
|
||||
type: "set",
|
||||
command: "start_modem",
|
||||
parameter: [
|
||||
{
|
||||
mycall: settings.mycall,
|
||||
mygrid: settings.mygrid,
|
||||
rx_audio: audioStore.startupInputDevice,
|
||||
tx_audio: audioStore.startupOutputDevice,
|
||||
radiocontrol: settings.radiocontrol,
|
||||
devicename: settings.hamlib_deviceid,
|
||||
deviceport: settings.hamlib_deviceport,
|
||||
pttprotocol: settings.hamlib_pttprotocol,
|
||||
pttport: settings.hamlib_ptt_port,
|
||||
serialspeed: settings.hamlib_serialspeed,
|
||||
data_bits: settings.hamlib_data_bits,
|
||||
stop_bits: settings.hamlib_stop_bits,
|
||||
handshake: settings.hamlib_handshake,
|
||||
rigctld_port: settings.hamlib_rigctld_port,
|
||||
rigctld_ip: settings.hamlib_rigctld_ip,
|
||||
enable_scatter: settings.enable_scatter,
|
||||
enable_fft: settings.enable_fft,
|
||||
enable_fsk: settings.enable_fsk,
|
||||
low_bandwidth_mode: settings.low_bandwidth_mode,
|
||||
tuning_range_fmin: settings.tuning_range_fmin,
|
||||
tuning_range_fmax: settings.tuning_range_fmax,
|
||||
//tx_audio_level: settings.tx_audio_level,
|
||||
respond_to_cq: settings.respond_to_cq,
|
||||
rx_buffer_size: settings.rx_buffer_size,
|
||||
enable_explorer: settings.enable_explorer,
|
||||
enable_stats: settings.explorer_stats,
|
||||
enable_auto_tune: settings.auto_tune,
|
||||
tx_delay: settings.tx_delay,
|
||||
tci_ip: settings.tci_ip,
|
||||
tci_port: settings.tci_port,
|
||||
enable_mesh: settings.enable_mesh_features,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
console.log(json_command);
|
||||
writeDaemonCommand(json_command);
|
||||
}
|
||||
|
||||
// STOP Modem
|
||||
//exports.stopModem = function () {
|
||||
export function stopModem() {
|
||||
var command =
|
||||
'{"type" : "set", "command": "stop_modem" , "parameter": "---" }';
|
||||
writeDaemonCommand(command);
|
||||
}
|
||||
|
||||
// TEST HAMLIB
|
||||
function testHamlib(
|
||||
//exports.testHamlib = function (
|
||||
radiocontrol,
|
||||
devicename,
|
||||
deviceport,
|
||||
serialspeed,
|
||||
pttprotocol,
|
||||
pttport,
|
||||
data_bits,
|
||||
stop_bits,
|
||||
handshake,
|
||||
rigctld_ip,
|
||||
rigctld_port,
|
||||
) {
|
||||
var json_command = JSON.stringify({
|
||||
type: "get",
|
||||
command: "test_hamlib",
|
||||
parameter: [
|
||||
{
|
||||
radiocontrol: radiocontrol,
|
||||
devicename: devicename,
|
||||
deviceport: deviceport,
|
||||
pttprotocol: pttprotocol,
|
||||
pttport: pttport,
|
||||
serialspeed: serialspeed,
|
||||
data_bits: data_bits,
|
||||
stop_bits: stop_bits,
|
||||
handshake: handshake,
|
||||
rigctld_port: rigctld_port,
|
||||
rigctld_ip: rigctld_ip,
|
||||
},
|
||||
],
|
||||
});
|
||||
console.log(json_command);
|
||||
writeDaemonCommand(json_command);
|
||||
}
|
||||
|
||||
export function startRigctld() {
|
||||
var json_command = JSON.stringify({
|
||||
type: "set",
|
||||
command: "start_rigctld",
|
||||
parameter: [
|
||||
{
|
||||
hamlib_deviceid: settings.hamlib_deviceid,
|
||||
hamlib_deviceport: settings.hamlib_deviceport,
|
||||
hamlib_stop_bits: settings.hamlib_stop_bits,
|
||||
hamlib_data_bits: settings.hamlib_data_bits,
|
||||
hamlib_handshake: settings.hamlib_handshake,
|
||||
hamlib_serialspeed: settings.hamlib_serialspeed,
|
||||
hamlib_dtrstate: settings.hamlib_dtrstate,
|
||||
hamlib_pttprotocol: settings.hamlib_pttprotocol,
|
||||
hamlib_ptt_port: settings.hamlib_ptt_port,
|
||||
hamlib_dcd: settings.hamlib_dcd,
|
||||
hamlbib_serialspeed_ptt: settings.hamlib_serialspeed,
|
||||
hamlib_rigctld_port: settings.hamlib_rigctld_port,
|
||||
hamlib_rigctld_ip: settings.hamlib_rigctld_ip,
|
||||
hamlib_rigctld_path: settings.hamlib_rigctld_path,
|
||||
hamlib_rigctld_server_port: settings.hamlib_rigctld_server_port,
|
||||
hamlib_rigctld_custom_args: settings.hamlib_rigctld_custom_args,
|
||||
},
|
||||
],
|
||||
});
|
||||
console.log(json_command);
|
||||
writeDaemonCommand(json_command);
|
||||
}
|
||||
export function stopRigctld() {
|
||||
let command = '{"type" : "set", "command": "stop_rigctld"}';
|
||||
writeDaemonCommand(command);
|
||||
}
|
|
@ -37,7 +37,7 @@ const dateFormatHours = new Intl.DateTimeFormat(navigator.language, {
|
|||
hour12: false,
|
||||
});
|
||||
// split character
|
||||
const split_char = "\0;\1;";
|
||||
//const split_char = "\0;\1;";
|
||||
// global for our selected file we want to transmit
|
||||
// ----------------- some chat globals
|
||||
var filetype = "";
|
||||
|
@ -80,6 +80,8 @@ PouchDB.plugin(require("pouchdb-find"));
|
|||
//PouchDB.plugin(require('pouchdb-replication'));
|
||||
PouchDB.plugin(require("pouchdb-upsert"));
|
||||
|
||||
|
||||
|
||||
var db = new PouchDB(chatDB);
|
||||
var users = new PouchDB(userDB);
|
||||
|
||||
|
@ -150,7 +152,8 @@ var chatFilter = [
|
|||
window.addEventListener("DOMContentLoaded", () => {
|
||||
updateAllChat(false);
|
||||
// theme selector
|
||||
changeGuiDesign(config.theme);
|
||||
// TODO: Remove for one pager, also remove function!
|
||||
//changeGuiDesign(config.theme);
|
||||
|
||||
const userInfoFields = [
|
||||
"user_info_image",
|
||||
|
@ -276,7 +279,8 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
.then(function (doc) {
|
||||
db.remove(doc)
|
||||
.then(function (doc) {
|
||||
return location.reload();
|
||||
updateAllChat(true);
|
||||
return true;
|
||||
})
|
||||
.catch(function (err) {
|
||||
console.log(err);
|
||||
|
@ -343,7 +347,7 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
lines = 6;
|
||||
}
|
||||
}
|
||||
var message_container_height_offset = 130 + 20 * lines;
|
||||
var message_container_height_offset = 180 + 20 * lines;
|
||||
var message_container_height = `calc(100% - ${message_container_height_offset}px)`;
|
||||
document.getElementById("message-container").style.height =
|
||||
message_container_height;
|
||||
|
@ -373,7 +377,7 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
"bi bi-chevron-compact-up";
|
||||
}
|
||||
|
||||
var message_container_height_offset = 130 + 20 * lines;
|
||||
var message_container_height_offset = 180 + 20 * lines;
|
||||
//var message_container_height_offset = 90 + (23*lines);
|
||||
var message_container_height = `calc(100% - ${message_container_height_offset}px)`;
|
||||
document.getElementById("message-container").style.height =
|
||||
|
@ -456,7 +460,7 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
//Remove non-printable chars from begining and end of string--should save us a byte here and there
|
||||
chatmessage = chatmessage.toString().trim();
|
||||
// reset textarea size
|
||||
var message_container_height_offset = 150;
|
||||
var message_container_height_offset = 200;
|
||||
var message_container_height = `calc(100% - ${message_container_height_offset}px)`;
|
||||
document.getElementById("message-container").style.height =
|
||||
message_container_height;
|
||||
|
@ -669,24 +673,12 @@ ipcRenderer.on("return-select-user-image", (event, arg) => {
|
|||
});
|
||||
});
|
||||
|
||||
ipcRenderer.on("action-update-reception-status", (event, arg) => {
|
||||
var data = arg["data"][0];
|
||||
|
||||
document.getElementById("txtConnectedWithChat").textContent = data.dxcallsign;
|
||||
});
|
||||
ipcRenderer.on("action-clear-reception-status", (event) => {
|
||||
//Clear connected with textbox
|
||||
let cwc = document.getElementById("txtConnectedWithChat");
|
||||
if (cwc.textContent != "------") {
|
||||
cwc.textContent = "------";
|
||||
//console.log("Reseting connected with");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
ipcRenderer.on("action-update-transmission-status", (event, arg) => {
|
||||
var data = arg["data"][0];
|
||||
|
||||
document.getElementById("txtConnectedWithChat").textContent = data.dxcallsign;
|
||||
if (data.status == "opening") return;
|
||||
if (typeof data.uuid === undefined) return;
|
||||
|
||||
|
@ -1064,6 +1056,7 @@ update_chat = function (obj) {
|
|||
var attempt = obj.attempt;
|
||||
}
|
||||
|
||||
|
||||
// add percent and bytes per minute if not existing
|
||||
//console.log(obj.percent)
|
||||
if (typeof obj.percent == "undefined") {
|
||||
|
@ -1266,13 +1259,13 @@ update_chat = function (obj) {
|
|||
`;
|
||||
|
||||
document
|
||||
.getElementById("list-tab")
|
||||
.getElementById("list-tab-chat")
|
||||
.insertAdjacentHTML("beforeend", new_callsign);
|
||||
var message_area = `
|
||||
<div class="tab-pane fade ${callsign_selected}" id="chat-${dxcallsign}" role="tabpanel" aria-labelledby="chat-${dxcallsign}-list"></div>
|
||||
`;
|
||||
document
|
||||
.getElementById("nav-tabContent")
|
||||
.getElementById("nav-tabContent-Chat")
|
||||
.insertAdjacentHTML("beforeend", message_area);
|
||||
|
||||
// finally get and set user information to first selected item
|
||||
|
@ -1392,36 +1385,40 @@ update_chat = function (obj) {
|
|||
showOsPopUp("Message received from " + obj.dxcallsign, obj.msg);
|
||||
}
|
||||
|
||||
// check if message is signed or not for adjusting icon
|
||||
if (
|
||||
typeof obj.hmac_signed !== "undefined" &&
|
||||
obj.hmac_signed !== "False"
|
||||
) {
|
||||
console.log(hmac_signed);
|
||||
var hmac_signed = '<i class="bi bi-shield-fill-check"></i>';
|
||||
} else {
|
||||
var hmac_signed = '<i class="bi bi-shield-x"></i>';
|
||||
}
|
||||
|
||||
// check if message is signed or not for adjusting icon
|
||||
if(typeof obj.hmac_signed !== "undefined" && obj.hmac_signed !== "False"){
|
||||
console.log(hmac_signed)
|
||||
var hmac_signed = '<i class="bi bi-shield-fill-check"></i>';
|
||||
} else {
|
||||
|
||||
var hmac_signed = '<i class="bi bi-shield-x"></i>';
|
||||
|
||||
}
|
||||
|
||||
|
||||
var new_message = `
|
||||
<div class="d-flex align-items-center" style="margin-left: auto;"> <!-- max-width: 75%; -->
|
||||
|
||||
<div class="mt-3 rounded-3 mb-0" style="max-width: 75%;" id="msg-${obj._id}">
|
||||
<!--<p class="font-monospace text-small mb-0 text-muted text-break">${timestamp}</p>-->
|
||||
<div class="card border-light bg-light" id="msg-${obj._id}">
|
||||
<div class="card border-light bg-body-tertiary" id="msg-${obj._id}">
|
||||
${fileheader}
|
||||
|
||||
<div class="card-body rounded-3 p-0">
|
||||
<p class="card-text p-2 mb-0 text-break text-wrap">${message_html}</p>
|
||||
<p class="text-right mb-0 p-1 text-white" style="text-align: left; font-size : 0.9rem">
|
||||
<span class="badge bg-light text-muted">${timestamp}</span>
|
||||
<span class="badge bg-body-tertiary text-muted">${timestamp}</span>
|
||||
|
||||
</p>
|
||||
|
||||
|
||||
<span id="msg-${obj._id}-hmac-badge" class="position-absolute top-0 start-100 translate-middle badge rounded-1 bg-secondary border border-white">
|
||||
<span id="msg-${
|
||||
obj._id
|
||||
}-hmac-badge" class="position-absolute top-0 start-100 translate-middle badge rounded-1 bg-secondary border border-white">
|
||||
|
||||
<span id="msg-${obj._id}-hmac-signed" class="">${hmac_signed}</span>
|
||||
<span id="msg-${
|
||||
obj._id
|
||||
}-hmac-signed" class="">${hmac_signed}</span>
|
||||
</span>
|
||||
|
||||
</div>
|
||||
|
@ -1438,11 +1435,11 @@ update_chat = function (obj) {
|
|||
<div class="d-flex align-items-center" style="margin-left: auto;"> <!-- max-width: 75%; -->
|
||||
<div class="mt-3 rounded-3 mb-0" style="max-width: 75%;" id="msg-${obj._id}">
|
||||
<!--<p class="font-monospace text-small mb-0 text-muted text-break">${timestamp}</p>-->
|
||||
<div class="card border-light bg-light" id="msg-${obj._id}">
|
||||
<div class="card border-light bg-body-tertiary" id="msg-${obj._id}">
|
||||
<div class="card-body rounded-3 p-0">
|
||||
<p class="card-text p-2 mb-0 text-break text-wrap">${message_html}</p>
|
||||
<p class="text-right mb-0 p-1 text-white" style="text-align: left; font-size : 0.9rem">
|
||||
<span class="badge bg-light text-muted">${timestamp}</span>
|
||||
<span class="badge bg-body-tertiary text-muted">${timestamp}</span>
|
||||
|
||||
</p>
|
||||
|
||||
|
@ -2252,9 +2249,9 @@ async function updateAllChat(clear) {
|
|||
callsign_counter = 0;
|
||||
//selected_callsign = "";
|
||||
dxcallsigns.clear();
|
||||
document.getElementById("list-tab").innerHTML = "";
|
||||
document.getElementById("nav-tabContent").innerHTML = "";
|
||||
//document.getElementById("list-tab").childNodes.remove();
|
||||
document.getElementById("list-tab-chat").innerHTML = "";
|
||||
document.getElementById("nav-tabContent-Chat").innerHTML = "";
|
||||
//document.getElementById("list-tab-chat").childNodes.remove();
|
||||
//document.getElementById("nav-tab-content").childrenNodes.remove();
|
||||
}
|
||||
//Ensure we create an index before running db.find
|
||||
|
@ -2905,6 +2902,8 @@ function clearUnreadMessages(dxcall) {
|
|||
.catch(function (err) {
|
||||
console.log(err);
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
//Have the operating system show a notification popup
|
|
@ -1,8 +1,23 @@
|
|||
const path = require("path");
|
||||
const { ipcRenderer, shell, clipboard } = require("electron");
|
||||
const { shell, clipboard } = require("electron");
|
||||
const exec = require("child_process").spawn;
|
||||
const sock = require("./sock.js");
|
||||
const daemon = require("./daemon.js");
|
||||
|
||||
|
||||
//import { useIpcRenderer } from '@vueuse/electron'
|
||||
//const ipcRenderer = useIpcRenderer()
|
||||
|
||||
|
||||
//import * as bootstrap from 'bootstrap'
|
||||
|
||||
|
||||
//require("./preload-chat.js");
|
||||
//require("./preload-mesh.js");
|
||||
//require("./preload-log.js");
|
||||
|
||||
//const daemon = require("./daemon.js");
|
||||
//const sock = require("./sock.js");
|
||||
|
||||
|
||||
const fs = require("fs");
|
||||
const FD = require("./freedata");
|
||||
const {
|
||||
|
@ -398,12 +413,12 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
}
|
||||
|
||||
if (config.enable_mesh_features.toLowerCase() == "true") {
|
||||
document.getElementById("liMeshTable").style.visibility = "visible";
|
||||
document.getElementById("liMeshTable").style.display = "block";
|
||||
document.getElementById("list-mesh-list").style.visibility = "visible";
|
||||
document.getElementById("list-mesh-list").style.display = "block";
|
||||
document.getElementById("enableMeshSwitch").checked = true;
|
||||
} else {
|
||||
document.getElementById("liMeshTable").style.visibility = "hidden";
|
||||
document.getElementById("liMeshTable").style.display = "none";
|
||||
document.getElementById("list-mesh-list").style.visibility = "hidden";
|
||||
document.getElementById("list-mesh-list").style.display = "none";
|
||||
document.getElementById("enableMeshSwitch").checked = false;
|
||||
}
|
||||
|
||||
|
@ -560,7 +575,7 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
document.getElementById("wftheme_selector").value = config.wftheme;
|
||||
spectrum.setColorMap(config.wftheme);
|
||||
|
||||
document.getElementById("btnAbout").addEventListener("click", () => {
|
||||
document.getElementById("list-info-list").addEventListener("click", () => {
|
||||
document.getElementById("aboutVersion").innerText = appVer;
|
||||
let maxcol = 3;
|
||||
let col = 2;
|
||||
|
@ -1258,12 +1273,12 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
document.getElementById("enableMeshSwitch").addEventListener("click", () => {
|
||||
if (document.getElementById("enableMeshSwitch").checked == true) {
|
||||
config.enable_mesh_features = "True";
|
||||
document.getElementById("liMeshTable").style.visibility = "visible";
|
||||
document.getElementById("liMeshTable").style.display = "block";
|
||||
document.getElementById("list-mesh-list").style.visibility = "visible";
|
||||
document.getElementById("list-mesh-list").style.display = "block";
|
||||
} else {
|
||||
config.enable_mesh_features = "False";
|
||||
document.getElementById("liMeshTable").style.visibility = "hidden";
|
||||
document.getElementById("liMeshTable").style.display = "none";
|
||||
document.getElementById("list-mesh-list").style.visibility = "hidden";
|
||||
document.getElementById("list-mesh-list").style.display = "none";
|
||||
}
|
||||
//fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||
FD.saveConfig(config, configPath);
|
||||
|
@ -1458,12 +1473,12 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
|
||||
if (document.getElementById("enableMeshSwitch").checked == true) {
|
||||
var enable_mesh_features = "True";
|
||||
document.getElementById("liMeshTable").style.visibility = "visible";
|
||||
document.getElementById("liMeshTable").style.display = "block";
|
||||
document.getElementById("list-mesh-list").style.visibility = "visible";
|
||||
document.getElementById("list-mesh-list").style.display = "block";
|
||||
} else {
|
||||
var enable_mesh_features = "False";
|
||||
document.getElementById("liMeshTable").style.visibility = "hidden";
|
||||
document.getElementById("liMeshTable").style.display = "none";
|
||||
document.getElementById("list-mesh-list").style.visibility = "hidden";
|
||||
document.getElementById("list-mesh-list").style.display = "none";
|
||||
}
|
||||
|
||||
if (document.getElementById("scatterSwitch").checked == true) {
|
||||
|
@ -1634,13 +1649,6 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
);
|
||||
});
|
||||
|
||||
document.getElementById("tncLog").addEventListener("click", () => {
|
||||
ipcRenderer.send("request-open-tnc-log");
|
||||
});
|
||||
|
||||
document.getElementById("meshtable").addEventListener("click", () => {
|
||||
ipcRenderer.send("request-open-mesh-module");
|
||||
});
|
||||
|
||||
// stopTNC button clicked
|
||||
document.getElementById("stopTNC").addEventListener("click", () => {
|
||||
|
@ -1746,13 +1754,7 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
sock.disconnectARQ();
|
||||
});
|
||||
|
||||
// OPEN CHAT MODULE
|
||||
document.getElementById("openRFChat").addEventListener("click", () => {
|
||||
let Data = {
|
||||
command: "openRFChat",
|
||||
};
|
||||
ipcRenderer.send("request-show-chat-window", Data);
|
||||
});
|
||||
|
||||
|
||||
document.getElementById("thTime").addEventListener("click", () => {
|
||||
if (hslLastSort == 0 && hslLastSortDir == "asc") hslLastSortDir = "desc";
|
||||
|
@ -2015,9 +2017,8 @@ ipcRenderer.on("action-update-tnc-state", (event, arg) => {
|
|||
checkForNewMessageWait = -1;
|
||||
}
|
||||
checkForNewMessageWait++;
|
||||
// update FFT
|
||||
if (typeof arg.fft !== "undefined") {
|
||||
// FIXME: WE need to fix this when disabled waterfall chart
|
||||
// update FFT only if data available
|
||||
if (typeof arg.fft !== "undefined" && arg.fft !== "[]") {
|
||||
try {
|
||||
var array = JSON.parse("[" + arg.fft + "]");
|
||||
spectrum.addData(array[0]);
|
||||
|
@ -2757,7 +2758,6 @@ ipcRenderer.on("action-update-hamlib-test", (event, arg) => {
|
|||
//document.getElementById("testHamlibAdvanced").className = "btn btn-sm btn-danger";
|
||||
}
|
||||
});
|
||||
|
||||
ipcRenderer.on("action-update-daemon-connection", (event, arg) => {
|
||||
if (arg.daemon_connection == "open") {
|
||||
document.getElementById("daemon_connection_state").className =
|
||||
|
@ -2775,88 +2775,6 @@ ipcRenderer.on("action-update-daemon-connection", (event, arg) => {
|
|||
//document.getElementById("blurdiv").style.webkitFilter = "blur(10px)";
|
||||
}
|
||||
});
|
||||
|
||||
ipcRenderer.on("action-update-tnc-connection", (event, arg) => {
|
||||
if (arg.tnc_connection == "open") {
|
||||
/*
|
||||
document.getElementById('hamlib_deviceid').disabled = true;
|
||||
document.getElementById('hamlib_deviceport').disabled = true;
|
||||
document.getElementById('testHamlib').disabled = true;
|
||||
document.getElementById('hamlib_ptt_protocol').disabled = true;
|
||||
document.getElementById('audio_input_selectbox').disabled = true;
|
||||
document.getElementById('audio_output_selectbox').disabled = true;
|
||||
//document.getElementById('stopTNC').disabled = false;
|
||||
document.getElementById('startTNC').disabled = true;
|
||||
document.getElementById('dxCall').disabled = false;
|
||||
document.getElementById("hamlib_serialspeed").disabled = true;
|
||||
document.getElementById("openDataModule").disabled = false;
|
||||
*/
|
||||
|
||||
// collapse settings screen
|
||||
var collapseFirstRow = new bootstrap.Collapse(
|
||||
document.getElementById("collapseFirstRow"),
|
||||
{ toggle: false },
|
||||
);
|
||||
collapseFirstRow.hide();
|
||||
var collapseSecondRow = new bootstrap.Collapse(
|
||||
document.getElementById("collapseSecondRow"),
|
||||
{ toggle: false },
|
||||
);
|
||||
collapseSecondRow.hide();
|
||||
var collapseThirdRow = new bootstrap.Collapse(
|
||||
document.getElementById("collapseThirdRow"),
|
||||
{ toggle: false },
|
||||
);
|
||||
collapseThirdRow.show();
|
||||
var collapseFourthRow = new bootstrap.Collapse(
|
||||
document.getElementById("collapseFourthRow"),
|
||||
{ toggle: false },
|
||||
);
|
||||
collapseFourthRow.show();
|
||||
|
||||
//Set tuning for fancy graphics mode (high/low CPU)
|
||||
set_CPU_mode();
|
||||
|
||||
//GUI will auto connect to TNC if already running, if that is the case increment start count if 0
|
||||
if (tncStartCount == 0) tncStartCount++;
|
||||
} else {
|
||||
/*
|
||||
document.getElementById('hamlib_deviceid').disabled = false;
|
||||
document.getElementById('hamlib_deviceport').disabled = false;
|
||||
document.getElementById('testHamlib').disabled = false;
|
||||
document.getElementById('hamlib_ptt_protocol').disabled = false;
|
||||
document.getElementById('audio_input_selectbox').disabled = false;
|
||||
document.getElementById('audio_output_selectbox').disabled = false;
|
||||
//document.getElementById('stopTNC').disabled = true;
|
||||
document.getElementById('startTNC').disabled = false;
|
||||
document.getElementById('dxCall').disabled = true;
|
||||
document.getElementById("hamlib_serialspeed").disabled = false;
|
||||
document.getElementById("openDataModule").disabled = true;
|
||||
*/
|
||||
// collapse settings screen
|
||||
var collapseFirstRow = new bootstrap.Collapse(
|
||||
document.getElementById("collapseFirstRow"),
|
||||
{ toggle: false },
|
||||
);
|
||||
collapseFirstRow.show();
|
||||
var collapseSecondRow = new bootstrap.Collapse(
|
||||
document.getElementById("collapseSecondRow"),
|
||||
{ toggle: false },
|
||||
);
|
||||
collapseSecondRow.show();
|
||||
var collapseThirdRow = new bootstrap.Collapse(
|
||||
document.getElementById("collapseThirdRow"),
|
||||
{ toggle: false },
|
||||
);
|
||||
collapseThirdRow.hide();
|
||||
var collapseFourthRow = new bootstrap.Collapse(
|
||||
document.getElementById("collapseFourthRow"),
|
||||
{ toggle: false },
|
||||
);
|
||||
collapseFourthRow.hide();
|
||||
}
|
||||
});
|
||||
|
||||
ipcRenderer.on("action-update-rx-buffer", (event, arg) => {
|
||||
var data = arg.data["data"];
|
||||
|
||||
|
@ -2976,11 +2894,11 @@ ipcRenderer.on("run-tnc-command-fec-iswriting", (event) => {
|
|||
ipcRenderer.on("action-update-unread-messages-main", (event, data) => {
|
||||
//Do something
|
||||
if (data == true) {
|
||||
document.getElementById("openRFChat").classList.add("btn-warning");
|
||||
document.getElementById("openRFChat").classList.remove("btn-secondary");
|
||||
document.getElementById("list-messages-list").classList.add("bg-warning");
|
||||
document.getElementById("list-messages-list").classList.remove("bg-secondary");
|
||||
} else {
|
||||
document.getElementById("openRFChat").classList.remove("btn-warning");
|
||||
document.getElementById("openRFChat").classList.add("btn-secondary");
|
||||
document.getElementById("list-messages-list").classList.remove("bg-warning");
|
||||
document.getElementById("list-messages-list").classList.add("bg-secondary");
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -3143,7 +3061,6 @@ ipcRenderer.on("action-updater", (event, arg) => {
|
|||
});
|
||||
|
||||
// ----------- INFO MODAL ACTIONS -------------------------------
|
||||
|
||||
// CQ TRANSMITTING
|
||||
ipcRenderer.on("action-show-cq-toast-transmitting", (event, data) => {
|
||||
displayToast(
|
|
@ -1,37 +1,27 @@
|
|||
const fs = require("fs");
|
||||
const { ipcRenderer } = require("electron");
|
||||
|
||||
/**
|
||||
* Save config and update config setting globally
|
||||
* @param {string} config - config data
|
||||
* @param {string} configPath
|
||||
*/
|
||||
exports.saveConfig = function (config, configPath) {
|
||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||
ipcRenderer.send("set-config-global", config);
|
||||
};
|
||||
|
||||
/**
|
||||
* Binary to ASCII replacement
|
||||
* @param {string} data in normal/usual utf-8 format
|
||||
* @returns base64 encoded string
|
||||
*/
|
||||
exports.btoa_FD = function (data) {
|
||||
export function btoa_FD(data) {
|
||||
//exports.btoa_FD = function (data) {
|
||||
return Buffer.from(data, "utf-8").toString("base64");
|
||||
};
|
||||
}
|
||||
/**
|
||||
* ASCII to Binary replacement
|
||||
* @param {string} data in base64 encoding
|
||||
* @returns utf-8 normal/usual string
|
||||
*/
|
||||
exports.atob_FD = function (data) {
|
||||
export function atob_FD(data) {
|
||||
//exports.atob_FD = function (data) {
|
||||
return Buffer.from(data, "base64").toString("utf-8");
|
||||
};
|
||||
}
|
||||
/**
|
||||
* UTF8 to ASCII btoa
|
||||
* @param {string} data in base64 encoding
|
||||
* @returns base64 bota compatible data for use in browser
|
||||
*/
|
||||
exports.atob = function (data) {
|
||||
export function atob(data) {
|
||||
//exports.atob = function (data) {
|
||||
return window.btoa(Buffer.from(data, "base64").toString("utf8"));
|
||||
};
|
||||
}
|
44
gui/src/js/popupHandler.ts
Normal file
44
gui/src/js/popupHandler.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
const { v4: uuidv4 } = require("uuid");
|
||||
import * as bootstrap from "bootstrap";
|
||||
|
||||
export function displayToast(type, icon, content, duration) {
|
||||
let mainToastContainer = document.getElementById("mainToastContainer");
|
||||
|
||||
let randomID = uuidv4();
|
||||
let toastCode = `
|
||||
<div class="toast align-items-center bg-outline-${type} border-1" id="${randomID}" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="d-flex">
|
||||
<div class="toast-body p-0 m-0 bg-white rounded-2 w-100">
|
||||
<div class="row p-1 m-0">
|
||||
<div class="col-auto bg-${type} rounded-start rounded-2 d-flex align-items-center">
|
||||
<i class="bi ${icon}" style="font-size: 1rem; color: white"></i>
|
||||
</div>
|
||||
<div class="col p-2">
|
||||
${content}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn-close me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// insert toast to toast container
|
||||
mainToastContainer.insertAdjacentHTML("beforeend", toastCode);
|
||||
|
||||
// register toast
|
||||
let toastHTMLElement = document.getElementById(randomID);
|
||||
let toast = bootstrap.Toast.getOrCreateInstance(toastHTMLElement); // Returns a Bootstrap toast instance
|
||||
toast._config.delay = duration;
|
||||
|
||||
// show toast
|
||||
toast.show();
|
||||
|
||||
//register event listener if toast is hidden
|
||||
toastHTMLElement.addEventListener("hidden.bs.toast", () => {
|
||||
// remove eventListener
|
||||
toastHTMLElement.removeEventListener("hidden.bs.toast", this);
|
||||
// remove toast
|
||||
toastHTMLElement.remove();
|
||||
});
|
||||
}
|
152
gui/src/js/settingsHandler.ts
Normal file
152
gui/src/js/settingsHandler.ts
Normal file
|
@ -0,0 +1,152 @@
|
|||
import path from "node:path";
|
||||
import fs from "fs";
|
||||
|
||||
// pinia store setup
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useSettingsStore } from "../store/settingsStore.js";
|
||||
const settings = useSettingsStore(pinia);
|
||||
// ---------------------------------
|
||||
|
||||
console.log(process.env);
|
||||
var appDataFolder = "undefined";
|
||||
if (typeof process.env["APPDATA"] !== "undefined") {
|
||||
appDataFolder = process.env["APPDATA"];
|
||||
console.log(appDataFolder);
|
||||
} else {
|
||||
switch (process.platform) {
|
||||
case "darwin":
|
||||
appDataFolder = process.env["HOME"] + "/Library/Application Support";
|
||||
console.log(appDataFolder);
|
||||
break;
|
||||
case "linux":
|
||||
appDataFolder = process.env["HOME"] + "/.config";
|
||||
console.log(appDataFolder);
|
||||
break;
|
||||
case "win32":
|
||||
appDataFolder = "undefined";
|
||||
break;
|
||||
default:
|
||||
appDataFolder = "undefined";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var configFolder = path.join(appDataFolder, "FreeDATA");
|
||||
var configPath = path.join(configFolder, "config.json");
|
||||
|
||||
console.log(appDataFolder);
|
||||
console.log(configFolder);
|
||||
console.log(configPath);
|
||||
|
||||
// create config folder if not exists
|
||||
if (!fs.existsSync(configFolder)) {
|
||||
fs.mkdirSync(configFolder);
|
||||
}
|
||||
|
||||
// create config file if not exists with defaults
|
||||
const configDefaultSettings =
|
||||
'{\
|
||||
"modem_host": "127.0.0.1",\
|
||||
"modem_port": 3000,\
|
||||
"daemon_host": "127.0.0.1",\
|
||||
"daemon_port": 3001,\
|
||||
"rx_audio" : "",\
|
||||
"tx_audio" : "",\
|
||||
"mycall": "AA0AA-0",\
|
||||
"myssid": "0",\
|
||||
"mygrid": "JN40aa",\
|
||||
"radiocontrol" : "disabled",\
|
||||
"hamlib_deviceid": 6,\
|
||||
"hamlib_deviceport": "ignore",\
|
||||
"hamlib_stop_bits": "ignore",\
|
||||
"hamlib_data_bits": "ignore",\
|
||||
"hamlib_handshake": "ignore",\
|
||||
"hamlib_serialspeed": "ignore",\
|
||||
"hamlib_dtrstate": "ignore",\
|
||||
"hamlib_pttprotocol": "ignore",\
|
||||
"hamlib_ptt_port": "ignore",\
|
||||
"hamlib_dcd": "ignore",\
|
||||
"hamlbib_serialspeed_ptt": 9600,\
|
||||
"hamlib_rigctld_port" : 4532,\
|
||||
"hamlib_rigctld_ip" : "127.0.0.1",\
|
||||
"hamlib_rigctld_path" : "",\
|
||||
"hamlib_rigctld_server_port" : 4532,\
|
||||
"hamlib_rigctld_custom_args": "",\
|
||||
"tci_port" : 50001,\
|
||||
"tci_ip" : "127.0.0.1",\
|
||||
"spectrum": "waterfall",\
|
||||
"enable_scatter" : "False",\
|
||||
"enable_fft" : "False",\
|
||||
"enable_fsk" : "False",\
|
||||
"low_bandwidth_mode" : "False",\
|
||||
"theme" : "default",\
|
||||
"screen_height" : 430,\
|
||||
"screen_width" : 1050,\
|
||||
"update_channel" : "latest",\
|
||||
"beacon_interval" : 300,\
|
||||
"received_files_folder" : "None",\
|
||||
"tuning_range_fmin" : "-50.0",\
|
||||
"tuning_range_fmax" : "50.0",\
|
||||
"respond_to_cq" : "True",\
|
||||
"rx_buffer_size" : 16, \
|
||||
"enable_explorer" : "False", \
|
||||
"wftheme": 2, \
|
||||
"high_graphics" : "True",\
|
||||
"explorer_stats" : "False", \
|
||||
"auto_tune" : "False", \
|
||||
"enable_is_writing" : "True", \
|
||||
"shared_folder_path" : ".", \
|
||||
"enable_request_profile" : "True", \
|
||||
"enable_request_shared_folder" : "False", \
|
||||
"max_retry_attempts" : 5, \
|
||||
"enable_auto_retry" : "False", \
|
||||
"tx_delay" : 0, \
|
||||
"auto_start": 0, \
|
||||
"enable_sys_notification": 1, \
|
||||
"enable_mesh_features": "False" \
|
||||
}';
|
||||
|
||||
if (!fs.existsSync(configPath)) {
|
||||
fs.writeFileSync(configPath, configDefaultSettings);
|
||||
}
|
||||
|
||||
export function loadSettings() {
|
||||
// load settings
|
||||
var config = require(configPath);
|
||||
|
||||
//config validation
|
||||
// check running config against default config.
|
||||
// if parameter not exists, add it to running config to prevent errors
|
||||
console.log("CONFIG VALIDATION ----------------------------- ");
|
||||
|
||||
var parsedConfig = JSON.parse(configDefaultSettings);
|
||||
for (var key in parsedConfig) {
|
||||
if (config.hasOwnProperty(key)) {
|
||||
console.log("FOUND SETTTING [" + key + "]: " + config[key]);
|
||||
} else {
|
||||
console.log("MISSING SETTTING [" + key + "] : " + parsedConfig[key]);
|
||||
config[key] = parsedConfig[key];
|
||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||
}
|
||||
try {
|
||||
if (key == "mycall") {
|
||||
settings.mycall = config[key].split("-")[0];
|
||||
settings.myssid = config[key].split("-")[1];
|
||||
} else {
|
||||
settings[key] = config[key];
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function saveSettingsToFile() {
|
||||
console.log("save settings to file...");
|
||||
let config = settings.getJSON();
|
||||
console.log(config);
|
||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||
}
|
870
gui/src/js/sock.js
Normal file
870
gui/src/js/sock.js
Normal file
|
@ -0,0 +1,870 @@
|
|||
var net = require("net");
|
||||
import { atob_FD, btoa_FD } from "./freedata";
|
||||
import { addDataToWaterfall } from "../js/waterfallHandler.js";
|
||||
|
||||
import {
|
||||
newMessageReceived,
|
||||
newBeaconReceived,
|
||||
updateTransmissionStatus,
|
||||
setStateSuccess,
|
||||
setStateFailed,
|
||||
} from "./chatHandler";
|
||||
import { displayToast } from "./popupHandler";
|
||||
|
||||
// ----------------- init pinia stores -------------
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const stateStore = useStateStore(pinia);
|
||||
|
||||
import { useSettingsStore } from "../store/settingsStore.js";
|
||||
const settings = useSettingsStore(pinia);
|
||||
|
||||
var client = new net.Socket();
|
||||
var socketchunk = ""; // Current message, per connection.
|
||||
|
||||
// split character
|
||||
//const split_char = "\0;\1;";
|
||||
const split_char = "0;1;";
|
||||
|
||||
// global to keep track of Modem connection error emissions
|
||||
var modemShowConnectStateError = 1;
|
||||
|
||||
// network connection Timeout
|
||||
setTimeout(connectModem, 2000);
|
||||
|
||||
function connectModem() {
|
||||
//exports.connectModem = function(){
|
||||
//console.log('connecting to Modem...')
|
||||
|
||||
//clear message buffer after reconnecting or initial connection
|
||||
socketchunk = "";
|
||||
|
||||
client.connect(settings.modem_port, settings.modem_host);
|
||||
}
|
||||
|
||||
client.on("connect", function () {
|
||||
console.log("Modem connection established");
|
||||
|
||||
stateStore.busy_state = "-";
|
||||
stateStore.arq_state = "-";
|
||||
stateStore.frequency = "-";
|
||||
stateStore.mode = "-";
|
||||
stateStore.bandwidth = "-";
|
||||
stateStore.dbfs_level = 0;
|
||||
stateStore.updateTncState(client.readyState);
|
||||
|
||||
modemShowConnectStateError = 1;
|
||||
});
|
||||
|
||||
client.on("error", function (err) {
|
||||
if (modemShowConnectStateError == 1) {
|
||||
console.log("Modem connection error");
|
||||
console.log(err);
|
||||
modemShowConnectStateError = 0;
|
||||
}
|
||||
setTimeout(connectModem, 500);
|
||||
client.destroy();
|
||||
stateStore.busy_state = "-";
|
||||
stateStore.arq_state = "-";
|
||||
stateStore.frequency = "-";
|
||||
stateStore.mode = "-";
|
||||
stateStore.bandwidth = "-";
|
||||
stateStore.dbfs_level = 0;
|
||||
stateStore.updateTncState(client.readyState);
|
||||
});
|
||||
|
||||
/*
|
||||
client.on('close', function(data) {
|
||||
console.log(' Modem connection closed');
|
||||
setTimeout(connectModem, 2000)
|
||||
});
|
||||
*/
|
||||
|
||||
client.on("end", function (data) {
|
||||
console.log("Modem connection ended");
|
||||
console.log(data);
|
||||
stateStore.busy_state = "-";
|
||||
stateStore.arq_state = "-";
|
||||
stateStore.frequency = "-";
|
||||
stateStore.mode = "-";
|
||||
stateStore.bandwidth = "-";
|
||||
stateStore.dbfs_level = 0;
|
||||
stateStore.updateTncState(client.readyState);
|
||||
client.destroy();
|
||||
|
||||
setTimeout(connectModem, 500);
|
||||
});
|
||||
|
||||
function writeTncCommand(command) {
|
||||
console.log(command);
|
||||
// we use the writingCommand function to update our TCPIP state because we are calling this function a lot
|
||||
// if socket opened, we are able to run commands
|
||||
|
||||
if (client.readyState == "open") {
|
||||
client.write(command + "\n");
|
||||
}
|
||||
|
||||
if (client.readyState == "closed") {
|
||||
console.log("Modem SOCKET CONNECTION CLOSED!");
|
||||
}
|
||||
|
||||
if (client.readyState == "opening") {
|
||||
console.log("connecting to Modem...");
|
||||
}
|
||||
}
|
||||
|
||||
client.on("data", function (socketdata) {
|
||||
stateStore.updateTncState(client.readyState);
|
||||
|
||||
/*
|
||||
inspired by:
|
||||
stackoverflow.com questions 9070700 nodejs-net-createserver-large-amount-of-data-coming-in
|
||||
*/
|
||||
|
||||
socketdata = socketdata.toString("utf8"); // convert data to string
|
||||
socketchunk += socketdata; // append data to buffer so we can stick long data together
|
||||
|
||||
// check if we received begin and end of json data
|
||||
if (socketchunk.startsWith('{"') && socketchunk.endsWith('"}\n')) {
|
||||
var data = "";
|
||||
|
||||
// split data into chunks if we received multiple commands
|
||||
socketchunk = socketchunk.split("\n");
|
||||
//don't think this is needed anymore
|
||||
//data = JSON.parse(socketchunk[0])
|
||||
|
||||
// search for empty entries in socketchunk and remove them
|
||||
for (let i = 0; i < socketchunk.length; i++) {
|
||||
if (socketchunk[i] === "") {
|
||||
socketchunk.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
//iterate through socketchunks array to execute multiple commands in row
|
||||
for (let i = 0; i < socketchunk.length; i++) {
|
||||
//check if data is not empty
|
||||
if (socketchunk[i].length > 0) {
|
||||
//try to parse JSON
|
||||
try {
|
||||
data = JSON.parse(socketchunk[i]);
|
||||
} catch (e) {
|
||||
console.log("Throwing away data!!!!\n" + e); // "SyntaxError
|
||||
//console.log(e); // "SyntaxError
|
||||
console.log(socketchunk[i]);
|
||||
socketchunk = "";
|
||||
//If we're here, I don't think we want to process any data that may be in data variable
|
||||
continue;
|
||||
}
|
||||
}
|
||||
//console.log(data)
|
||||
if (data["command"] == "modem_state") {
|
||||
//console.log(data)
|
||||
|
||||
stateStore.rx_buffer_length = data["rx_buffer_length"];
|
||||
stateStore.frequency = data["frequency"];
|
||||
stateStore.busy_state = data["modem_state"];
|
||||
stateStore.arq_state = data["arq_state"];
|
||||
stateStore.mode = data["mode"];
|
||||
stateStore.bandwidth = data["bandwidth"];
|
||||
stateStore.dbfs_level = data["audio_dbfs"];
|
||||
stateStore.ptt_state = data["ptt_state"];
|
||||
stateStore.speed_level = data["speed_level"];
|
||||
stateStore.fft = JSON.parse(data["fft"]);
|
||||
stateStore.channel_busy = data["channel_busy"];
|
||||
stateStore.channel_busy_slot = data["channel_busy_slot"];
|
||||
|
||||
addDataToWaterfall(JSON.parse(data["fft"]));
|
||||
|
||||
if (data["scatter"].length > 0) {
|
||||
stateStore.scatter = data["scatter"];
|
||||
}
|
||||
// s meter strength
|
||||
stateStore.s_meter_strength_raw = data["strength"];
|
||||
if (stateStore.s_meter_strength_raw == "") {
|
||||
stateStore.s_meter_strength_raw = "Unsupported";
|
||||
stateStore.s_meter_strength_percent = 0;
|
||||
} else {
|
||||
// https://www.moellerstudios.org/converting-amplitude-representations/
|
||||
stateStore.s_meter_strength_percent = Math.round(
|
||||
Math.pow(10, stateStore.s_meter_strength_raw / 20) * 100,
|
||||
);
|
||||
}
|
||||
|
||||
stateStore.dbfs_level_percent = Math.round(
|
||||
Math.pow(10, stateStore.dbfs_level / 20) * 100,
|
||||
);
|
||||
stateStore.dbfs_level = Math.round(stateStore.dbfs_level);
|
||||
|
||||
stateStore.arq_total_bytes = data["total_bytes"];
|
||||
stateStore.heard_stations = data["stations"];
|
||||
stateStore.dxcallsign = data["dxcallsign"];
|
||||
|
||||
stateStore.beacon_state = data["beacon_state"];
|
||||
stateStore.audio_recording = data["audio_recording"];
|
||||
|
||||
stateStore.hamlib_status = data["hamlib_status"];
|
||||
stateStore.alc = data["alc"];
|
||||
stateStore.rf_level = data["rf_level"];
|
||||
|
||||
stateStore.is_codec2_traffic = data["is_codec2_traffic"];
|
||||
|
||||
stateStore.arq_session_state = data["arq_session"];
|
||||
stateStore.arq_state = data["arq_state"];
|
||||
stateStore.arq_transmission_percent = data["arq_transmission_percent"];
|
||||
stateStore.arq_seconds_until_finish = data["arq_seconds_until_finish"];
|
||||
stateStore.arq_seconds_until_timeout =
|
||||
data["arq_seconds_until_timeout"];
|
||||
stateStore.arq_seconds_until_timeout_percent =
|
||||
(stateStore.arq_seconds_until_timeout / 180) * 100;
|
||||
|
||||
if (data["speed_list"].length > 0) {
|
||||
prepareStatsDataForStore(data["speed_list"]);
|
||||
}
|
||||
|
||||
// TODO: Remove ported objects
|
||||
/*
|
||||
let Data = {
|
||||
mycallsign: data["mycallsign"],
|
||||
mygrid: data["mygrid"],
|
||||
//channel_state: data['CHANNEL_STATE'],
|
||||
|
||||
info: data["info"],
|
||||
rx_msg_buffer_length: data["rx_msg_buffer_length"],
|
||||
tx_n_max_retries: data["tx_n_max_retries"],
|
||||
arq_tx_n_frames_per_burst: data["arq_tx_n_frames_per_burst"],
|
||||
arq_tx_n_bursts: data["arq_tx_n_bursts"],
|
||||
arq_tx_n_current_arq_frame: data["arq_tx_n_current_arq_frame"],
|
||||
arq_tx_n_total_arq_frames: data["arq_tx_n_total_arq_frames"],
|
||||
arq_rx_frame_n_bursts: data["arq_rx_frame_n_bursts"],
|
||||
arq_rx_n_current_arq_frame: data["arq_rx_n_current_arq_frame"],
|
||||
arq_n_arq_frames_per_data_frame:
|
||||
data["arq_n_arq_frames_per_data_frame"],
|
||||
arq_bytes_per_minute: data["arq_bytes_per_minute"],
|
||||
arq_compression_factor: data["arq_compression_factor"],
|
||||
routing_table: data["routing_table"],
|
||||
mesh_signalling_table: data["mesh_signalling_table"],
|
||||
listen: data["listen"],
|
||||
//speed_table: [{"bpm" : 5200, "snr": -3, "timestamp":1673555399},{"bpm" : 2315, "snr": 12, "timestamp":1673555500}],
|
||||
};
|
||||
*/
|
||||
//continue to next for loop iteration, nothing else needs to be done here
|
||||
continue;
|
||||
}
|
||||
|
||||
// ----------- catch modem messages START -----------
|
||||
//init message variable
|
||||
var message = "";
|
||||
if (data["freedata"] == "modem-message") {
|
||||
// break early if we received a dummy callsign
|
||||
// thats a kind of hotfix, as long as the modem isnt handling this better
|
||||
if (
|
||||
data["dxcallsign"] == "AA0AA-0" ||
|
||||
data["dxcallsign"] == "ZZ9YY-0"
|
||||
) {
|
||||
break;
|
||||
}
|
||||
|
||||
console.log(data);
|
||||
|
||||
switch (data["fec"]) {
|
||||
case "is_writing":
|
||||
// RX'd FECiswriting
|
||||
break;
|
||||
|
||||
case "broadcast":
|
||||
// RX'd FEC BROADCAST
|
||||
var encoded_data = atob_FD(data["data"]);
|
||||
var splitted_data = encoded_data.split(split_char);
|
||||
var messageArray = [];
|
||||
if (splitted_data[0] == "m") {
|
||||
messageArray.push(data);
|
||||
console.log(data);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
switch (data["cq"]) {
|
||||
case "transmitting":
|
||||
// CQ TRANSMITTING
|
||||
displayToast(
|
||||
"success",
|
||||
"bi-arrow-left-right",
|
||||
"Transmitting CQ",
|
||||
5000,
|
||||
);
|
||||
break;
|
||||
|
||||
case "received":
|
||||
// CQ RECEIVED
|
||||
message = "CQ from " + data["dxcallsign"];
|
||||
displayToast("success", "bi-person-arms-up", message, 5000);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (data["qrv"]) {
|
||||
case "transmitting":
|
||||
// QRV TRANSMITTING
|
||||
displayToast(
|
||||
"info",
|
||||
"bi-person-raised-hand",
|
||||
"Transmitting QRV ",
|
||||
5000,
|
||||
);
|
||||
break;
|
||||
|
||||
case "received":
|
||||
// QRV RECEIVED
|
||||
message = "QRV from " + data["dxcallsign"] + " | " + data["dxgrid"];
|
||||
displayToast("success", "bi-person-raised-hand", message, 5000);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (data["beacon"]) {
|
||||
case "transmitting":
|
||||
// BEACON TRANSMITTING
|
||||
displayToast(
|
||||
"success",
|
||||
"bi-broadcast-pin",
|
||||
"Transmitting beacon",
|
||||
5000,
|
||||
);
|
||||
break;
|
||||
|
||||
case "received":
|
||||
// BEACON RECEIVED
|
||||
newBeaconReceived(data);
|
||||
|
||||
message =
|
||||
"Beacon from " + data["dxcallsign"] + " | " + data["dxgrid"];
|
||||
displayToast("info", "bi-broadcast", message, 5000);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (data["ping"]) {
|
||||
case "transmitting":
|
||||
// PING TRANSMITTING
|
||||
message = "Sending ping to " + data["dxcallsign"];
|
||||
displayToast("success", "bi-arrow-right", message, 5000);
|
||||
break;
|
||||
|
||||
case "received":
|
||||
// PING RECEIVED
|
||||
message =
|
||||
"Ping request from " +
|
||||
data["dxcallsign"] +
|
||||
" | " +
|
||||
data["dxgrid"];
|
||||
displayToast("success", "bi-arrow-right-short", message, 5000);
|
||||
break;
|
||||
|
||||
case "acknowledge":
|
||||
// PING ACKNOWLEDGE
|
||||
message =
|
||||
"Received ping-ack from " +
|
||||
data["dxcallsign"] +
|
||||
" | " +
|
||||
data["dxgrid"];
|
||||
displayToast("success", "bi-arrow-left-right", message, 5000);
|
||||
break;
|
||||
}
|
||||
|
||||
// ARQ SESSION && freedata == modem-message
|
||||
if (data["arq"] == "session") {
|
||||
switch (data["status"]) {
|
||||
case "connecting":
|
||||
// ARQ Open
|
||||
break;
|
||||
|
||||
case "connected":
|
||||
// ARQ Opening
|
||||
break;
|
||||
|
||||
case "waiting":
|
||||
// ARQ Opening
|
||||
break;
|
||||
|
||||
case "close":
|
||||
// ARQ Closing
|
||||
break;
|
||||
|
||||
case "failed":
|
||||
// ARQ Failed
|
||||
break;
|
||||
}
|
||||
}
|
||||
// ARQ TRANSMISSION && freedata == modem-message
|
||||
if (data["arq"] == "transmission") {
|
||||
switch (data["status"]) {
|
||||
case "opened":
|
||||
// ARQ Open
|
||||
message = "ARQ session opened: " + data["dxcallsign"];
|
||||
displayToast("success", "bi-arrow-left-right", message, 5000);
|
||||
break;
|
||||
|
||||
case "opening":
|
||||
// ARQ Opening IRS/ISS
|
||||
if (data["irs"] == "False") {
|
||||
message = "ARQ session opening: " + data["dxcallsign"];
|
||||
displayToast("info", "bi-arrow-left-right", message, 5000);
|
||||
break;
|
||||
} else {
|
||||
message = "ARQ sesson request from: " + data["dxcallsign"];
|
||||
displayToast("success", "bi-arrow-left-right", message, 5000);
|
||||
break;
|
||||
}
|
||||
|
||||
case "waiting":
|
||||
// ARQ waiting
|
||||
message = "Channel busy | ARQ protocol is waiting";
|
||||
displayToast("warning", "bi-hourglass-split", message, 5000);
|
||||
break;
|
||||
|
||||
case "receiving":
|
||||
// ARQ RX
|
||||
break;
|
||||
|
||||
case "failed":
|
||||
// ARQ TX Failed
|
||||
if (data["reason"] == "protocol version missmatch") {
|
||||
message = "Protocol version mismatch!";
|
||||
displayToast("danger", "bi-chevron-bar-expand", message, 5000);
|
||||
setStateFailed();
|
||||
|
||||
break;
|
||||
} else {
|
||||
message = "Transmission failed";
|
||||
displayToast("danger", "bi-x-octagon", message, 5000);
|
||||
updateTransmissionStatus(data);
|
||||
setStateFailed();
|
||||
break;
|
||||
}
|
||||
switch (data["irs"]) {
|
||||
case "True":
|
||||
updateTransmissionStatus(data);
|
||||
|
||||
break;
|
||||
default:
|
||||
updateTransmissionStatus(data);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case "received":
|
||||
// ARQ data received
|
||||
|
||||
console.log(data);
|
||||
// we need to encode here to do a deep check for checking if file or message
|
||||
//var encoded_data = atob(data['data'])
|
||||
var encoded_data = atob_FD(data["data"]);
|
||||
var splitted_data = encoded_data.split(split_char);
|
||||
|
||||
// new message received
|
||||
if (splitted_data[0] == "m") {
|
||||
console.log(splitted_data);
|
||||
newMessageReceived(splitted_data, data);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "transmitting":
|
||||
// ARQ transmitting
|
||||
updateTransmissionStatus(data);
|
||||
break;
|
||||
|
||||
case "transmitted":
|
||||
// ARQ transmitted
|
||||
message = "Data transmitted";
|
||||
displayToast("success", "bi-check-sqaure", message, 5000);
|
||||
updateTransmissionStatus(data);
|
||||
setStateSuccess();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//finally delete message buffer
|
||||
socketchunk = "";
|
||||
}
|
||||
});
|
||||
|
||||
// Send Ping
|
||||
//exports.sendPing = function (dxcallsign) {
|
||||
export function sendPing(dxcallsign) {
|
||||
var command =
|
||||
'{"type" : "ping", "command" : "ping", "dxcallsign" : "' +
|
||||
dxcallsign +
|
||||
'"}';
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// Send Mesh Ping
|
||||
//exports.sendMeshPing = function (dxcallsign) {
|
||||
function sendMeshPing(dxcallsign) {
|
||||
var command =
|
||||
'{"type" : "mesh", "command" : "ping", "dxcallsign" : "' +
|
||||
dxcallsign +
|
||||
'"}';
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// Send CQ
|
||||
//exports.sendCQ = function () {
|
||||
export function sendCQ() {
|
||||
var command = '{"type" : "broadcast", "command" : "cqcqcq"}';
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// Set AUDIO Level
|
||||
export function setTxAudioLevel(value) {
|
||||
var command =
|
||||
'{"type" : "set", "command" : "tx_audio_level", "value" : "' + value + '"}';
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// Send Message
|
||||
export function sendMessage(obj) {
|
||||
let dxcallsign = obj.dxcallsign;
|
||||
let checksum = obj.checksum;
|
||||
let uuid = obj.uuid;
|
||||
let command = obj.command;
|
||||
|
||||
let filename = Object.keys(obj._attachments)[0];
|
||||
//let filetype = filename.split(".")[1]
|
||||
let filetype = obj._attachments[filename].content_type;
|
||||
let file = obj._attachments[filename].data;
|
||||
|
||||
//console.log(obj._attachments)
|
||||
//console.log(filename)
|
||||
//console.log(filetype)
|
||||
//console.log(file)
|
||||
|
||||
let data_with_attachment =
|
||||
obj.timestamp +
|
||||
split_char +
|
||||
obj.msg +
|
||||
split_char +
|
||||
filename +
|
||||
split_char +
|
||||
filetype +
|
||||
split_char +
|
||||
file;
|
||||
|
||||
let data = btoa_FD(
|
||||
"m" +
|
||||
split_char +
|
||||
command +
|
||||
split_char +
|
||||
checksum +
|
||||
split_char +
|
||||
uuid +
|
||||
split_char +
|
||||
data_with_attachment,
|
||||
);
|
||||
|
||||
// TODO: REMOVE mode and frames from Modem!
|
||||
var mode = 255;
|
||||
var frames = 5;
|
||||
|
||||
command =
|
||||
'{"type" : "arq", "command" : "send_raw", "uuid" : "' +
|
||||
uuid +
|
||||
'", "parameter" : [{"dxcallsign" : "' +
|
||||
dxcallsign +
|
||||
'", "mode" : "' +
|
||||
mode +
|
||||
'", "n_frames" : "' +
|
||||
frames +
|
||||
'", "data" : "' +
|
||||
data +
|
||||
'", "attempts": "10"}]}';
|
||||
console.log(command);
|
||||
writeTncCommand(command);
|
||||
}
|
||||
/*
|
||||
// Send Request message
|
||||
//It would be then „m + split + request + split + request-type“
|
||||
function sendRequest(dxcallsign, mode, frames, data, command) {
|
||||
data = btoa_FD("m" + split_char + command + split_char + data);
|
||||
command =
|
||||
'{"type" : "arq", "command" : "send_raw", "parameter" : [{"dxcallsign" : "' +
|
||||
dxcallsign +
|
||||
'", "mode" : "' +
|
||||
mode +
|
||||
'", "n_frames" : "' +
|
||||
frames +
|
||||
'", "data" : "' +
|
||||
data +
|
||||
'", "attempts": "10"}]}';
|
||||
console.log(command);
|
||||
console.log("--------------REQ--------------------");
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// Send Response message
|
||||
//It would be then „m + split + request + split + request-type“
|
||||
function sendResponse(dxcallsign, mode, frames, data, command) {
|
||||
data = btoa_FD("m" + split_char + command + split_char + data);
|
||||
command =
|
||||
'{"type" : "arq", "command" : "send_raw", "parameter" : [{"dxcallsign" : "' +
|
||||
dxcallsign +
|
||||
'", "mode" : "' +
|
||||
mode +
|
||||
'", "n_frames" : "' +
|
||||
frames +
|
||||
'", "data" : "' +
|
||||
data +
|
||||
'", "attempts": "10"}]}';
|
||||
console.log(command);
|
||||
console.log("--------------RES--------------------");
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
//Send station info request
|
||||
//exports.sendRequestInfo = function (dxcallsign) {
|
||||
function sendRequestInfo(dxcallsign) {
|
||||
//Command 0 = user/station information
|
||||
//Command 1 = shared folder list
|
||||
//Command 2 = shared file transfer
|
||||
sendRequest(dxcallsign, 255, 1, "0", "req");
|
||||
}
|
||||
|
||||
//Send shared folder file list request
|
||||
//exports.sendRequestSharedFolderList = function (dxcallsign) {
|
||||
function sendRequestSharedFolderList(dxcallsign) {
|
||||
//Command 0 = user/station information
|
||||
//Command 1 = shared folder list
|
||||
//Command 2 = shared file transfer
|
||||
sendRequest(dxcallsign, 255, 1, "1", "req");
|
||||
}
|
||||
|
||||
//Send shared file request
|
||||
//exports.sendRequestSharedFile = function (dxcallsign, file) {
|
||||
function sendRequestSharedFile(dxcallsign, file) {
|
||||
//Command 0 = user/station information
|
||||
//Command 1 = shared folder list
|
||||
//Command 2 = shared file transfer
|
||||
sendRequest(dxcallsign, 255, 1, "2" + file, "req");
|
||||
}
|
||||
|
||||
//Send station info response
|
||||
//exports.sendResponseInfo = function (dxcallsign, userinfo) {
|
||||
function sendResponseInfo(dxcallsign, userinfo) {
|
||||
//Command 0 = user/station information
|
||||
//Command 1 = shared folder list
|
||||
//Command 2 = shared file transfer
|
||||
sendResponse(dxcallsign, 255, 1, userinfo, "res-0");
|
||||
}
|
||||
|
||||
//Send shared folder response
|
||||
//exports.sendResponseSharedFolderList = function (dxcallsign, sharedFolderList) {
|
||||
function sendResponseSharedFolderList(dxcallsign, sharedFolderList) {
|
||||
//Command 0 = user/station information
|
||||
//Command 1 = shared folder list
|
||||
//Command 2 = shared file transfer
|
||||
sendResponse(dxcallsign, 255, 1, sharedFolderList, "res-1");
|
||||
}
|
||||
|
||||
//Send shared file response
|
||||
//exports.sendResponseSharedFile = function (
|
||||
function sendResponseSharedFile(dxcallsign, sharedFile, sharedFileData) {
|
||||
console.log(
|
||||
"In sendResponseSharedFile",
|
||||
dxcallsign,
|
||||
sharedFile,
|
||||
sharedFileData,
|
||||
);
|
||||
//Command 0 = user/station information
|
||||
//Command 1 = shared folder list
|
||||
//Command 2 = shared file transfer
|
||||
sendResponse(dxcallsign, 255, 1, sharedFile + "/" + sharedFileData, "res-2");
|
||||
}
|
||||
*/
|
||||
//STOP TRANSMISSION
|
||||
export function stopTransmission() {
|
||||
var command = '{"type" : "arq", "command": "stop_transmission"}';
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// Get RX BUffer
|
||||
export function getRxBuffer() {
|
||||
var command = '{"type" : "get", "command" : "rx_buffer"}';
|
||||
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// START BEACON
|
||||
export function startBeacon(interval) {
|
||||
var command =
|
||||
'{"type" : "broadcast", "command" : "start_beacon", "parameter": "' +
|
||||
interval +
|
||||
'"}';
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// STOP BEACON
|
||||
export function stopBeacon() {
|
||||
var command = '{"type" : "broadcast", "command" : "stop_beacon"}';
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// OPEN ARQ SESSION
|
||||
export function connectARQ(dxcallsign) {
|
||||
var command =
|
||||
'{"type" : "arq", "command" : "connect", "dxcallsign": "' +
|
||||
dxcallsign +
|
||||
'", "attempts": "10"}';
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// CLOSE ARQ SESSION
|
||||
export function disconnectARQ() {
|
||||
var command = '{"type" : "arq", "command" : "disconnect"}';
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// SEND TEST FRAME
|
||||
export function sendTestFrame() {
|
||||
var command = '{"type" : "set", "command" : "send_test_frame"}';
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// SEND FEC
|
||||
export function sendFEC(mode, payload) {
|
||||
var command =
|
||||
'{"type" : "fec", "command" : "transmit", "mode" : "' +
|
||||
mode +
|
||||
'", "payload" : "' +
|
||||
payload +
|
||||
'"}';
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// SEND FEC IS WRITING
|
||||
export function sendFecIsWriting(mycallsign) {
|
||||
var command =
|
||||
'{"type" : "fec", "command" : "transmit_is_writing", "mycallsign" : "' +
|
||||
mycallsign +
|
||||
'"}';
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// SEND FEC TO BROADCASTCHANNEL
|
||||
//export function sendBroadcastChannel(channel, data_out, uuid) {
|
||||
export function sendBroadcastChannel(obj) {
|
||||
//let checksum = obj.checksum;
|
||||
let command = obj.command;
|
||||
let uuid = obj.uuid;
|
||||
let channel = obj.dxcallsign;
|
||||
let data_out = obj.msg;
|
||||
|
||||
let data = btoa_FD(
|
||||
"m" +
|
||||
split_char +
|
||||
channel +
|
||||
//split_char +
|
||||
//checksum +
|
||||
split_char +
|
||||
uuid +
|
||||
split_char +
|
||||
data_out,
|
||||
);
|
||||
console.log(data.length);
|
||||
let payload = data;
|
||||
command =
|
||||
'{"type" : "fec", "command" : "transmit", "mode": "datac4", "wakeup": "True", "payload" : "' +
|
||||
payload +
|
||||
'"}';
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// RECORD AUDIO
|
||||
export function record_audio() {
|
||||
var command = '{"type" : "set", "command" : "record_audio"}';
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// SET FREQUENCY
|
||||
export function set_frequency(frequency) {
|
||||
var command =
|
||||
'{"type" : "set", "command" : "frequency", "frequency": ' + frequency + "}";
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// SET MODE
|
||||
export function set_mode(mode) {
|
||||
var command = '{"type" : "set", "command" : "mode", "mode": "' + mode + '"}';
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// SET rf_level
|
||||
export function set_rf_level(rf_level) {
|
||||
var command =
|
||||
'{"type" : "set", "command" : "rf_level", "rf_level": "' + rf_level + '"}';
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/50579690
|
||||
// crc32 calculation
|
||||
//console.log(crc32('abc'));
|
||||
//console.log(crc32('abc').toString(16).toUpperCase()); // hex
|
||||
/*
|
||||
function crc32(r) {
|
||||
for (var a, o = [], c = 0; c < 256; c++) {
|
||||
a = c;
|
||||
for (var f = 0; f < 8; f++) a = 1 & a ? 3988292384 ^ (a >>> 1) : a >>> 1;
|
||||
o[c] = a;
|
||||
}
|
||||
for (var n = -1, t = 0; t < r.length; t++)
|
||||
n = (n >>> 8) ^ o[255 & (n ^ r.charCodeAt(t))];
|
||||
return (-1 ^ n) >>> 0;
|
||||
};
|
||||
*/
|
||||
|
||||
// TODO Maybe moving this to another module
|
||||
function prepareStatsDataForStore(data) {
|
||||
// dummy data
|
||||
//state.arq_speed_list = [{"snr":0.0,"bpm":104,"timestamp":1696189769},{"snr":0.0,"bpm":80,"timestamp":1696189778},{"snr":0.0,"bpm":70,"timestamp":1696189783},{"snr":0.0,"bpm":58,"timestamp":1696189792},{"snr":0.0,"bpm":52,"timestamp":1696189797},{"snr":"NaN","bpm":42,"timestamp":1696189811},{"snr":0.0,"bpm":22,"timestamp":1696189875},{"snr":0.0,"bpm":21,"timestamp":1696189881},{"snr":0.0,"bpm":17,"timestamp":1696189913},{"snr":0.0,"bpm":15,"timestamp":1696189932},{"snr":0.0,"bpm":15,"timestamp":1696189937},{"snr":0.0,"bpm":14,"timestamp":1696189946},{"snr":-6.1,"bpm":14,"timestamp":1696189954},{"snr":-6.1,"bpm":14,"timestamp":1696189955},{"snr":-5.5,"bpm":28,"timestamp":1696189963},{"snr":-5.5,"bpm":27,"timestamp":1696189963}]
|
||||
|
||||
var speed_listSize = 0;
|
||||
if (typeof data == "undefined") {
|
||||
speed_listSize = 0;
|
||||
} else {
|
||||
speed_listSize = data.length;
|
||||
}
|
||||
|
||||
var speed_list_bpm = [];
|
||||
|
||||
for (let i = 0; i < speed_listSize; i++) {
|
||||
speed_list_bpm.push(data[i].bpm);
|
||||
}
|
||||
|
||||
var speed_list_timestamp = [];
|
||||
|
||||
for (let i = 0; i < speed_listSize; i++) {
|
||||
let timestamp = data[i].timestamp * 1000;
|
||||
let h = new Date(timestamp).getHours();
|
||||
let m = new Date(timestamp).getMinutes();
|
||||
let s = new Date(timestamp).getSeconds();
|
||||
let time = h + ":" + m + ":" + s;
|
||||
speed_list_timestamp.push(time);
|
||||
}
|
||||
|
||||
var speed_list_snr = [];
|
||||
for (let i = 0; i < speed_listSize; i++) {
|
||||
let snr = NaN;
|
||||
if (data[i].snr !== 0) {
|
||||
snr = data[i].snr;
|
||||
} else {
|
||||
snr = NaN;
|
||||
}
|
||||
speed_list_snr.push(snr);
|
||||
}
|
||||
|
||||
stateStore.arq_speed_list_bpm = speed_list_bpm;
|
||||
stateStore.arq_speed_list_timestamp = speed_list_timestamp;
|
||||
stateStore.arq_speed_list_snr = speed_list_snr;
|
||||
}
|
29
gui/src/js/waterfallHandler.js
Normal file
29
gui/src/js/waterfallHandler.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { Spectrum } from "../assets/waterfall/spectrum.js";
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useSettingsStore } from "../store/settingsStore.js";
|
||||
const settings = useSettingsStore(pinia);
|
||||
|
||||
var spectrum = new Object();
|
||||
|
||||
export function initWaterfall() {
|
||||
spectrum = new Spectrum("waterfall", {
|
||||
spectrumPercent: 0,
|
||||
wf_rows: 192, //Assuming 1 row = 1 pixe1, 192 is the height of the spectrum container
|
||||
});
|
||||
|
||||
console.log(settings.wftheme);
|
||||
spectrum.setColorMap(settings.wftheme);
|
||||
}
|
||||
|
||||
export function addDataToWaterfall(data) {
|
||||
//console.log(spectrum)
|
||||
try {
|
||||
spectrum.addData(data);
|
||||
} catch (e) {
|
||||
//console.log(e);
|
||||
}
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self';" />
|
||||
|
||||
<!-- Bootstrap CSS -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="../node_modules/bootstrap/dist/css/bootstrap.min.css"
|
||||
/>
|
||||
<link rel="stylesheet" type="text/css" href="styles.css" />
|
||||
<title>FreeDATA - Live Log</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- bootstrap -->
|
||||
<script src="../node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<!-- chart.js -->
|
||||
|
||||
<nav class="navbar fixed-top bg-light">
|
||||
<div class="container-fluid">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="btn-check"
|
||||
id="enable_filter_info"
|
||||
autocomplete="off"
|
||||
checked
|
||||
/>
|
||||
<label class="btn btn-outline-info" for="enable_filter_info"
|
||||
>info</label
|
||||
>
|
||||
|
||||
<input
|
||||
type="checkbox"
|
||||
class="btn-check"
|
||||
id="enable_filter_debug"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<label class="btn btn-outline-primary" for="enable_filter_debug"
|
||||
>debug</label
|
||||
>
|
||||
|
||||
<input
|
||||
type="checkbox"
|
||||
class="btn-check"
|
||||
id="enable_filter_warning"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<label class="btn btn-outline-warning" for="enable_filter_warning"
|
||||
>warning</label
|
||||
>
|
||||
|
||||
<input
|
||||
type="checkbox"
|
||||
class="btn-check"
|
||||
id="enable_filter_error"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<label class="btn btn-outline-danger" for="enable_filter_error"
|
||||
>error</label
|
||||
>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container-fluid mt-5">
|
||||
<div class="tableFixHead">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Timestamp</th>
|
||||
<th scope="col">Type</th>
|
||||
<th scope="col">Area</th>
|
||||
<th scope="col">Log entry</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="log">
|
||||
<!--
|
||||
<tr>
|
||||
<th scope="row">1</th>
|
||||
<td>Mark</td>
|
||||
<td>Otto</td>
|
||||
<td>@mdo</td>
|
||||
</tr>
|
||||
-->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
29
gui/src/main.ts
Normal file
29
gui/src/main.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { createApp } from "vue";
|
||||
import { createPinia } from "pinia";
|
||||
import { loadSettings } from "./js/settingsHandler";
|
||||
|
||||
import "./styles.css";
|
||||
|
||||
// Import all of Bootstrap's JS
|
||||
//import * as bootstrap from 'bootstrap'
|
||||
|
||||
import "bootstrap/dist/js/bootstrap.bundle.min.js";
|
||||
import "bootstrap/dist/css/bootstrap.css";
|
||||
import "bootstrap-icons/font/bootstrap-icons.css";
|
||||
|
||||
// Import our custom CSS
|
||||
//import './scss/styles.scss'
|
||||
|
||||
import App from "./App.vue";
|
||||
const app = createApp(App);
|
||||
//.mount('#app').$nextTick(() => postMessage({ payload: 'removeLoading' }, '*'))
|
||||
const pinia = createPinia();
|
||||
app.mount("#app");
|
||||
|
||||
app.use(pinia);
|
||||
loadSettings();
|
||||
|
||||
//import './js/settingsHandler.js'
|
||||
import "./js/daemon";
|
||||
import "./js/sock.js";
|
||||
//import './js/settingsHandler.js'
|
|
@ -1,147 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self';" />
|
||||
|
||||
<!-- Bootstrap CSS -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="../node_modules/bootstrap/dist/css/bootstrap.min.css"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="../node_modules/bootstrap-icons/font/bootstrap-icons.css"
|
||||
/>
|
||||
<link rel="stylesheet" type="text/css" href="styles.css" />
|
||||
<title>FreeDATA - Mesh Table</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- bootstrap -->
|
||||
<script src="../node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<!-- chart.js -->
|
||||
|
||||
<nav>
|
||||
<div class="nav nav-tabs" id="nav-tab" role="tablist">
|
||||
<button
|
||||
class="nav-link active"
|
||||
id="nav-route-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#nav-route"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="nav-route"
|
||||
aria-selected="true"
|
||||
>
|
||||
Routes
|
||||
</button>
|
||||
<button
|
||||
class="nav-link"
|
||||
id="nav-signaling-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#nav-signaling"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="nav-signaling"
|
||||
aria-selected="false"
|
||||
>
|
||||
Signaling
|
||||
</button>
|
||||
<button
|
||||
class="nav-link"
|
||||
id="nav-actions-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#nav-actions"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="nav-actions"
|
||||
aria-selected="false"
|
||||
>
|
||||
Actions
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="tab-content" id="nav-tabContent">
|
||||
<div
|
||||
class="tab-pane fade show active vw-100 vh-90 overflow-auto"
|
||||
id="nav-route"
|
||||
role="tabpanel"
|
||||
aria-labelledby="nav-route-tab"
|
||||
>
|
||||
<div class="container-fluid">
|
||||
<div
|
||||
class="table-responsive overflow-auto"
|
||||
style="max-width: 99vw; max-height: 99vh"
|
||||
>
|
||||
<table class="table table-hover table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Timestamp</th>
|
||||
<th scope="col">DXCall</th>
|
||||
<th scope="col">Router</th>
|
||||
<th scope="col">Hops</th>
|
||||
<th scope="col">Score</th>
|
||||
<th scope="col">SNR</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="mesh-table"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
id="nav-signaling"
|
||||
role="tabpanel"
|
||||
aria-labelledby="nav-signaling-tab"
|
||||
>
|
||||
<div class="container-fluid">
|
||||
<div
|
||||
class="table-responsive overflow-auto"
|
||||
style="max-width: 99vw; max-height: 99vh"
|
||||
>
|
||||
<table class="table table-hover table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Timestamp</th>
|
||||
<th scope="col">Destination</th>
|
||||
<th scope="col">Origin</th>
|
||||
<th scope="col">Frametype</th>
|
||||
<th scope="col">Payload</th>
|
||||
<th scope="col">Attempt</th>
|
||||
<th scope="col">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="mesh-signalling-table"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
id="nav-actions"
|
||||
role="tabpanel"
|
||||
aria-labelledby="nav-actions-tab"
|
||||
>
|
||||
<div class="input-group mt-1">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
style="max-width: 6rem; text-transform: uppercase"
|
||||
placeholder="DXcall"
|
||||
pattern="[A-Z]*"
|
||||
id="dxCallMesh"
|
||||
maxlength="11"
|
||||
aria-label="Input group"
|
||||
aria-describedby="btnGroupAddon"
|
||||
/>
|
||||
<button id="transmit_mesh_ping" type="button" class="btn btn-primary">
|
||||
mesh ping
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
2
gui/src/scss/styles.scss
Normal file
2
gui/src/scss/styles.scss
Normal file
|
@ -0,0 +1,2 @@
|
|||
// Import all of Bootstrap's CSS
|
||||
@import "bootstrap/scss/bootstrap";
|
|
@ -1,11 +0,0 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self';" />
|
||||
</head>
|
||||
<body style="overflow: hidden">
|
||||
<img src="img/icon_cube_border.png" width="100%" height="100%" />
|
||||
</body>
|
||||
</html>
|
55
gui/src/store/audioStore.js
Normal file
55
gui/src/store/audioStore.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { ref } from "vue";
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useSettingsStore } from "../store/settingsStore.js";
|
||||
const settings = useSettingsStore(pinia);
|
||||
|
||||
export const useAudioStore = defineStore("audioStore", () => {
|
||||
var inputDevices = ref([{ id: 0, name: "no input devices" }]);
|
||||
var outputDevices = ref([{ id: 0, name: "no output devices" }]);
|
||||
|
||||
var startupInputDevice = ref(0);
|
||||
var startupOutputDevice = ref(0);
|
||||
|
||||
function getInputDevices() {
|
||||
var html = "";
|
||||
for (var key in inputDevices.value) {
|
||||
let selected = "";
|
||||
if (inputDevices.value[key]["name"] == settings.rx_audio) {
|
||||
selected = "selected";
|
||||
} else {
|
||||
selected = "";
|
||||
}
|
||||
|
||||
html += `<option value=${inputDevices.value[key]["id"]} ${selected}>${inputDevices.value[key]["name"]}</option>`;
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
function getOutputDevices() {
|
||||
var html = "";
|
||||
for (var key in outputDevices.value) {
|
||||
let selected = "";
|
||||
if (outputDevices.value[key]["name"] == settings.tx_audio) {
|
||||
selected = "selected";
|
||||
} else {
|
||||
selected = "";
|
||||
}
|
||||
html += `<option value=${outputDevices.value[key]["id"]} ${selected}>${outputDevices.value[key]["name"]}</option>`;
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
return {
|
||||
inputDevices,
|
||||
outputDevices,
|
||||
getInputDevices,
|
||||
getOutputDevices,
|
||||
startupInputDevice,
|
||||
startupOutputDevice,
|
||||
};
|
||||
});
|
92
gui/src/store/chatStore.js
Normal file
92
gui/src/store/chatStore.js
Normal file
|
@ -0,0 +1,92 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { ref } from "vue";
|
||||
|
||||
export const useChatStore = defineStore("chatStore", () => {
|
||||
var chat_filter = ref([
|
||||
{ type: "newchat" },
|
||||
{ type: "received" },
|
||||
{ type: "transmit" },
|
||||
{ type: "ping-ack" },
|
||||
{ type: "broadcast_received" },
|
||||
{ type: "broadcast_transmit" },
|
||||
//{ type: "request" },
|
||||
//{ type: "response" },
|
||||
]);
|
||||
|
||||
var selectedCallsign = ref();
|
||||
// we need a default value in our ref because of our message info modal
|
||||
var selectedMessageObject = ref({
|
||||
command: "msg",
|
||||
hmac_signed: false,
|
||||
percent: 0,
|
||||
is_new: false,
|
||||
_id: "2ead6698",
|
||||
timestamp: 1697289795,
|
||||
dxcallsign: "DJ2LS-0",
|
||||
dxgrid: "null",
|
||||
msg: "test",
|
||||
checksum: "",
|
||||
type: "transmit",
|
||||
status: "transmitting",
|
||||
attempt: 1,
|
||||
uuid: "2ead6698",
|
||||
duration: 0,
|
||||
nacks: 0,
|
||||
speed_list: "null",
|
||||
_attachments: {
|
||||
"": {
|
||||
content_type: "text",
|
||||
data: "",
|
||||
},
|
||||
},
|
||||
});
|
||||
var inputText = ref("");
|
||||
var inputFile = ref();
|
||||
var inputFileName = ref();
|
||||
var inputFileType = ref();
|
||||
var inputFileSize = ref();
|
||||
|
||||
var callsign_list = ref();
|
||||
var sorted_chat_list = ref();
|
||||
var unsorted_chat_list = ref([]);
|
||||
|
||||
var sorted_beacon_list = ref({});
|
||||
var unsorted_beacon_list = ref({});
|
||||
|
||||
var chartSpeedPER0 = ref();
|
||||
var chartSpeedPER25 = ref();
|
||||
var chartSpeedPER75 = ref();
|
||||
|
||||
// var beaconDataArray = ref([-3, 10, 8, 5, 3, 0, -5, 10, 8, 5, 3, 0, -5, 10, 8, 5, 3, 0, -5, 10, 8, 5, 3, 0, -5])
|
||||
// var beaconLabelArray = ref(['18:10', '19:00', '23:00', '01:13', '04:25', '08:15', '09:12', '18:10', '19:00', '23:00', '01:13', '04:25', '08:15', '09:12', '18:10', '19:00', '23:00', '01:13', '04:25', '08:15', '09:12', '01:13', '04:25', '08:15', '09:12'])
|
||||
var beaconDataArray = ref([]);
|
||||
var beaconLabelArray = ref([]);
|
||||
|
||||
var arq_speed_list_bpm = ref([]);
|
||||
var arq_speed_list_timestamp = ref([]);
|
||||
var arq_speed_list_snr = ref([]);
|
||||
|
||||
return {
|
||||
selectedCallsign,
|
||||
selectedMessageObject,
|
||||
inputText,
|
||||
chat_filter,
|
||||
callsign_list,
|
||||
sorted_chat_list,
|
||||
unsorted_chat_list,
|
||||
inputFileName,
|
||||
inputFileSize,
|
||||
inputFileType,
|
||||
inputFile,
|
||||
chartSpeedPER0,
|
||||
chartSpeedPER25,
|
||||
chartSpeedPER75,
|
||||
beaconDataArray,
|
||||
beaconLabelArray,
|
||||
unsorted_beacon_list,
|
||||
sorted_beacon_list,
|
||||
arq_speed_list_bpm,
|
||||
arq_speed_list_snr,
|
||||
arq_speed_list_timestamp,
|
||||
};
|
||||
});
|
5
gui/src/store/index.js
Normal file
5
gui/src/store/index.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { createPinia } from "pinia";
|
||||
|
||||
const pinia = createPinia();
|
||||
|
||||
export default pinia;
|
228
gui/src/store/settingsStore.js
Normal file
228
gui/src/store/settingsStore.js
Normal file
|
@ -0,0 +1,228 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { ref } from "vue";
|
||||
|
||||
export const useSettingsStore = defineStore("settingsStore", () => {
|
||||
// audio
|
||||
var tx_audio = ref();
|
||||
var rx_audio = ref();
|
||||
|
||||
// network
|
||||
var modem_host = ref("127.0.0.1");
|
||||
var modem_port = ref(3000);
|
||||
var daemon_host = ref(modem_host.value);
|
||||
var daemon_port = ref(modem_port.value + 1);
|
||||
|
||||
// app
|
||||
var screen_height = ref(430);
|
||||
var screen_width = ref(1050);
|
||||
var theme = ref("default");
|
||||
var wftheme = ref(2);
|
||||
var high_graphics = ref("False");
|
||||
var auto_start = ref(0);
|
||||
var enable_sys_notification = ref(1);
|
||||
|
||||
// chat
|
||||
var shared_folder_path = ref(".");
|
||||
var enable_request_profile = ref("True");
|
||||
var enable_request_shared_folder = ref("False");
|
||||
var max_retry_attempts = ref(5);
|
||||
var enable_auto_retry = ref("False");
|
||||
|
||||
// station
|
||||
var mycall = ref("AA0AA-5");
|
||||
var myssid = ref(0);
|
||||
var mygrid = ref("JN20aa");
|
||||
|
||||
// rigctld
|
||||
var hamlib_rigctld_port = ref(4532);
|
||||
var hamlib_rigctld_ip = ref("127.0.0.1");
|
||||
var radiocontrol = ref("disabled");
|
||||
var hamlib_deviceid = ref("RIG_MODEL_DUMMY_NOVFO");
|
||||
var hamlib_deviceport = ref("ignore");
|
||||
var hamlib_stop_bits = ref("ignore");
|
||||
var hamlib_data_bits = ref("ignore");
|
||||
var hamlib_handshake = ref("ignore");
|
||||
var hamlib_serialspeed = ref("ignore");
|
||||
var hamlib_dtrstate = ref("ignore");
|
||||
var hamlib_pttprotocol = ref("ignore");
|
||||
var hamlib_ptt_port = ref("ignore");
|
||||
var hamlib_dcd = ref("ignore");
|
||||
var hamlbib_serialspeed_ptt = ref(9600);
|
||||
var hamlib_rigctld_path = ref("");
|
||||
var hamlib_rigctld_server_port = ref(4532);
|
||||
var hamlib_rigctld_custom_args = ref("");
|
||||
|
||||
// tci
|
||||
var tci_ip = ref("127.0.0.1");
|
||||
var tci_port = ref(50001);
|
||||
|
||||
//modem
|
||||
var spectrum = ref("waterfall");
|
||||
var enable_scatter = ref("False");
|
||||
var enable_fft = ref("False");
|
||||
var enable_fsk = ref("False");
|
||||
var low_bandwidth_mode = ref("False");
|
||||
var update_channel = ref("latest");
|
||||
var beacon_interval = ref(300);
|
||||
var received_files_folder = ref("None");
|
||||
var tuning_range_fmin = ref(-50.0);
|
||||
var tuning_range_fmax = ref(50.0);
|
||||
var respond_to_cq = ref("True");
|
||||
var rx_buffer_size = ref(16);
|
||||
var enable_explorer = ref("False");
|
||||
var explorer_stats = ref("False");
|
||||
var auto_tune = ref("False");
|
||||
var enable_is_writing = ref("True");
|
||||
var tx_delay = ref(0);
|
||||
var enable_mesh_features = ref("False");
|
||||
var serial_devices = ref();
|
||||
|
||||
function getSerialDevices() {
|
||||
if (this.hamlib_deviceport == "ignore")
|
||||
var html =
|
||||
'<option value ="ignore" selected>None - (use custom options for hamlib)</option>';
|
||||
else
|
||||
var html =
|
||||
'<option value ="ignore">None - (use custom options for hamlib)</option>';
|
||||
for (var key in serial_devices.value) {
|
||||
let selected = "";
|
||||
if (serial_devices.value[key]["port"] == this.hamlib_deviceport) {
|
||||
selected = "selected";
|
||||
} else {
|
||||
selected = "";
|
||||
}
|
||||
|
||||
html += `<option value="${serial_devices.value[key]["port"]}" ${selected}>${serial_devices.value[key]["port"]} - ${serial_devices.value[key]["description"]}</option>`;
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
function getJSON() {
|
||||
var config_export = {
|
||||
modem_host: modem_host.value,
|
||||
modem_port: modem_port.value,
|
||||
daemon_host: modem_host.value,
|
||||
daemon_port: (parseInt(modem_port.value) + 1).toString(),
|
||||
mycall: mycall.value,
|
||||
myssid: myssid.value,
|
||||
mygrid: mygrid.value,
|
||||
radiocontrol: radiocontrol.value,
|
||||
hamlib_deviceid: hamlib_deviceid.value,
|
||||
hamlib_deviceport: hamlib_deviceport.value,
|
||||
hamlib_stop_bits: hamlib_stop_bits.value,
|
||||
hamlib_data_bits: hamlib_data_bits.value,
|
||||
hamlib_handshake: hamlib_handshake.value,
|
||||
hamlib_serialspeed: hamlib_serialspeed.value,
|
||||
hamlib_dtrstate: hamlib_dtrstate.value,
|
||||
hamlib_pttprotocol: hamlib_pttprotocol.value,
|
||||
hamlib_ptt_port: hamlib_ptt_port.value,
|
||||
hamlib_dcd: hamlib_dcd.value,
|
||||
hamlbib_serialspeed_ptt: hamlib_serialspeed.value,
|
||||
hamlib_rigctld_port: hamlib_rigctld_port.value,
|
||||
hamlib_rigctld_ip: hamlib_rigctld_ip.value,
|
||||
hamlib_rigctld_path: hamlib_rigctld_path.value,
|
||||
hamlib_rigctld_server_port: hamlib_rigctld_server_port.value,
|
||||
hamlib_rigctld_custom_args: hamlib_rigctld_custom_args.value,
|
||||
tci_port: tci_port.value,
|
||||
tci_ip: tci_ip.value,
|
||||
spectrum: spectrum.value,
|
||||
enable_scatter: enable_scatter.value,
|
||||
enable_fft: enable_fft.value,
|
||||
enable_fsk: enable_fsk.value,
|
||||
low_bandwidth_mode: low_bandwidth_mode.value,
|
||||
theme: theme.value,
|
||||
screen_height: screen_height.value,
|
||||
screen_width: screen_width.value,
|
||||
update_channel: update_channel.value,
|
||||
beacon_interval: beacon_interval.value,
|
||||
received_files_folder: received_files_folder.value,
|
||||
tuning_range_fmin: tuning_range_fmin.value,
|
||||
tuning_range_fmax: tuning_range_fmax.value,
|
||||
respond_to_cq: respond_to_cq.value,
|
||||
rx_buffer_size: rx_buffer_size.value,
|
||||
enable_explorer: enable_explorer.value,
|
||||
wftheme: wftheme.value,
|
||||
high_graphics: high_graphics.value,
|
||||
explorer_stats: explorer_stats.value,
|
||||
auto_tune: auto_tune.value,
|
||||
enable_is_writing: enable_is_writing.value,
|
||||
shared_folder_path: shared_folder_path.value,
|
||||
enable_request_profile: enable_request_profile.value,
|
||||
enable_request_shared_folder: enable_request_shared_folder.value,
|
||||
max_retry_attempts: max_retry_attempts.value,
|
||||
enable_auto_retry: enable_auto_retry.value,
|
||||
tx_delay: tx_delay.value,
|
||||
auto_start: auto_start.value,
|
||||
enable_sys_notification: enable_sys_notification.value,
|
||||
enable_mesh_features: enable_mesh_features.value,
|
||||
tx_audio: tx_audio.value,
|
||||
rx_audio: rx_audio.value,
|
||||
};
|
||||
|
||||
return config_export;
|
||||
}
|
||||
|
||||
return {
|
||||
modem_host,
|
||||
modem_port,
|
||||
daemon_host,
|
||||
daemon_port,
|
||||
screen_height,
|
||||
screen_width,
|
||||
theme,
|
||||
wftheme,
|
||||
high_graphics,
|
||||
auto_start,
|
||||
enable_sys_notification,
|
||||
shared_folder_path,
|
||||
enable_request_profile,
|
||||
enable_request_shared_folder,
|
||||
max_retry_attempts,
|
||||
enable_auto_retry,
|
||||
mycall,
|
||||
myssid,
|
||||
mygrid,
|
||||
hamlib_rigctld_port,
|
||||
hamlib_rigctld_ip,
|
||||
radiocontrol,
|
||||
hamlib_deviceid,
|
||||
hamlib_deviceport,
|
||||
hamlib_stop_bits,
|
||||
hamlib_data_bits,
|
||||
hamlib_handshake,
|
||||
hamlib_serialspeed,
|
||||
hamlib_dtrstate,
|
||||
hamlib_pttprotocol,
|
||||
hamlib_ptt_port,
|
||||
hamlib_dcd,
|
||||
hamlbib_serialspeed_ptt,
|
||||
hamlib_rigctld_path,
|
||||
hamlib_rigctld_server_port,
|
||||
hamlib_rigctld_custom_args,
|
||||
tci_ip,
|
||||
tci_port,
|
||||
spectrum,
|
||||
enable_scatter,
|
||||
enable_fft,
|
||||
enable_fsk,
|
||||
low_bandwidth_mode,
|
||||
update_channel,
|
||||
beacon_interval,
|
||||
received_files_folder,
|
||||
tuning_range_fmin,
|
||||
tuning_range_fmax,
|
||||
respond_to_cq,
|
||||
rx_buffer_size,
|
||||
enable_explorer,
|
||||
explorer_stats,
|
||||
auto_tune,
|
||||
enable_is_writing,
|
||||
tx_delay,
|
||||
enable_mesh_features,
|
||||
getJSON,
|
||||
tx_audio,
|
||||
rx_audio,
|
||||
getSerialDevices,
|
||||
serial_devices,
|
||||
};
|
||||
});
|
183
gui/src/store/stateStore.js
Normal file
183
gui/src/store/stateStore.js
Normal file
|
@ -0,0 +1,183 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { ref } from "vue";
|
||||
import * as bootstrap from "bootstrap";
|
||||
|
||||
export const useStateStore = defineStore("stateStore", () => {
|
||||
var busy_state = ref("-");
|
||||
var arq_state = ref("-");
|
||||
var frequency = ref("-");
|
||||
var new_frequency = ref(0);
|
||||
var mode = ref("-");
|
||||
var rf_level = ref("10");
|
||||
var bandwidth = ref("-");
|
||||
var dbfs_level_percent = ref(0);
|
||||
var dbfs_level = ref(0);
|
||||
|
||||
var ptt_state = ref("False");
|
||||
|
||||
var speed_level = ref(0);
|
||||
var fft = ref();
|
||||
var channel_busy = ref("");
|
||||
var channel_busy_slot = ref();
|
||||
var scatter = ref();
|
||||
var s_meter_strength_percent = ref(0);
|
||||
var s_meter_strength_raw = ref(0);
|
||||
|
||||
var modem_connection = ref("disconnected");
|
||||
var modemStartCount = ref(0);
|
||||
var modem_running_state = ref("--------");
|
||||
|
||||
var arq_total_bytes = ref(0);
|
||||
var arq_transmission_percent = ref(0);
|
||||
|
||||
var heard_stations = ref("");
|
||||
var dxcallsign = ref("");
|
||||
|
||||
var arq_session_state = ref("");
|
||||
var arq_state = ref("");
|
||||
var beacon_state = ref("False");
|
||||
|
||||
var audio_recording = ref("");
|
||||
|
||||
var hamlib_status = ref("");
|
||||
var audio_level = ref("");
|
||||
var alc = ref("");
|
||||
|
||||
var is_codec2_traffic = ref("");
|
||||
|
||||
var arq_speed_list_timestamp = ref([]);
|
||||
var arq_speed_list_bpm = ref([]);
|
||||
var arq_speed_list_snr = ref([]);
|
||||
|
||||
var arq_seconds_until_finish = ref();
|
||||
var arq_seconds_until_timeout = ref();
|
||||
var arq_seconds_until_timeout_percent = ref();
|
||||
|
||||
var rigctld_started = ref();
|
||||
var rigctld_process = ref();
|
||||
|
||||
var python_version = ref();
|
||||
var modem_version = ref();
|
||||
|
||||
var rx_buffer_length = ref();
|
||||
|
||||
function getChannelBusySlotState(slot) {
|
||||
const slot_state = channel_busy_slot.value;
|
||||
|
||||
if (typeof slot_state !== "undefined") {
|
||||
// Replace 'False' with 'false' to match JavaScript's boolean representation
|
||||
const string = slot_state
|
||||
.replace(/False/g, "false")
|
||||
.replace(/True/g, "true");
|
||||
|
||||
// Parse the string to get an array
|
||||
const arr = JSON.parse(string);
|
||||
|
||||
return arr[slot];
|
||||
} else {
|
||||
// Handle the undefined case
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function updateTncState(state) {
|
||||
modem_connection.value = state;
|
||||
|
||||
if (modem_connection.value == "open") {
|
||||
// collapse settings screen
|
||||
var collapseFirstRow = new bootstrap.Collapse(
|
||||
document.getElementById("collapseFirstRow"),
|
||||
{ toggle: false },
|
||||
);
|
||||
collapseFirstRow.hide();
|
||||
var collapseSecondRow = new bootstrap.Collapse(
|
||||
document.getElementById("collapseSecondRow"),
|
||||
{ toggle: false },
|
||||
);
|
||||
collapseSecondRow.hide();
|
||||
var collapseThirdRow = new bootstrap.Collapse(
|
||||
document.getElementById("collapseThirdRow"),
|
||||
{ toggle: false },
|
||||
);
|
||||
collapseThirdRow.show();
|
||||
var collapseFourthRow = new bootstrap.Collapse(
|
||||
document.getElementById("collapseFourthRow"),
|
||||
{ toggle: false },
|
||||
);
|
||||
collapseFourthRow.show();
|
||||
|
||||
//Set tuning for fancy graphics mode (high/low CPU)
|
||||
//set_CPU_mode();
|
||||
|
||||
//GUI will auto connect to TNC if already running, if that is the case increment start count if 0
|
||||
if (modemStartCount.value == 0) modemStartCount.value++;
|
||||
} else {
|
||||
// collapse settings screen
|
||||
var collapseFirstRow = new bootstrap.Collapse(
|
||||
document.getElementById("collapseFirstRow"),
|
||||
{ toggle: false },
|
||||
);
|
||||
collapseFirstRow.show();
|
||||
var collapseSecondRow = new bootstrap.Collapse(
|
||||
document.getElementById("collapseSecondRow"),
|
||||
{ toggle: false },
|
||||
);
|
||||
collapseSecondRow.show();
|
||||
var collapseThirdRow = new bootstrap.Collapse(
|
||||
document.getElementById("collapseThirdRow"),
|
||||
{ toggle: false },
|
||||
);
|
||||
collapseThirdRow.hide();
|
||||
var collapseFourthRow = new bootstrap.Collapse(
|
||||
document.getElementById("collapseFourthRow"),
|
||||
{ toggle: false },
|
||||
);
|
||||
collapseFourthRow.hide();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
dxcallsign,
|
||||
busy_state,
|
||||
arq_state,
|
||||
new_frequency,
|
||||
frequency,
|
||||
mode,
|
||||
bandwidth,
|
||||
dbfs_level,
|
||||
dbfs_level_percent,
|
||||
speed_level,
|
||||
fft,
|
||||
channel_busy,
|
||||
channel_busy_slot,
|
||||
getChannelBusySlotState,
|
||||
scatter,
|
||||
ptt_state,
|
||||
s_meter_strength_percent,
|
||||
s_meter_strength_raw,
|
||||
arq_total_bytes,
|
||||
audio_recording,
|
||||
hamlib_status,
|
||||
audio_level,
|
||||
alc,
|
||||
updateTncState,
|
||||
arq_transmission_percent,
|
||||
arq_speed_list_bpm,
|
||||
arq_speed_list_timestamp,
|
||||
arq_speed_list_snr,
|
||||
arq_seconds_until_finish,
|
||||
arq_seconds_until_timeout,
|
||||
arq_seconds_until_timeout_percent,
|
||||
modem_running_state,
|
||||
arq_session_state,
|
||||
is_codec2_traffic,
|
||||
rf_level,
|
||||
heard_stations,
|
||||
beacon_state,
|
||||
rigctld_started,
|
||||
rigctld_process,
|
||||
python_version,
|
||||
modem_version,
|
||||
rx_buffer_length,
|
||||
};
|
||||
});
|
0
gui/src/style.css
Normal file
0
gui/src/style.css
Normal file
|
@ -107,3 +107,11 @@ https://stackoverflow.com/a/9622873
|
|||
[data-bs-theme="dark"] {
|
||||
/* default dark theme mods */
|
||||
}
|
||||
.modal-backdrop {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.modal-backdrop.in {
|
||||
filter: alpha(opacity=10);
|
||||
opacity: .1
|
||||
}
|
||||
|
|
15
gui/src/vite-env.d.ts
vendored
Normal file
15
gui/src/vite-env.d.ts
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue'
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly PACKAGE_VERSION: string;
|
||||
// more env variables...
|
||||
}
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv
|
||||
}
|
|
@ -1,149 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (c) 2019 Jeppe Ledet-Pedersen
|
||||
# This software is released under the MIT license.
|
||||
# See the LICENSE file for further details.
|
||||
|
||||
import sys
|
||||
import json
|
||||
import argparse
|
||||
|
||||
from gnuradio import gr
|
||||
from gnuradio import uhd
|
||||
from gnuradio.fft import logpwrfft
|
||||
|
||||
import numpy as np
|
||||
|
||||
from gevent.pywsgi import WSGIServer
|
||||
from geventwebsocket import WebSocketError
|
||||
from geventwebsocket.handler import WebSocketHandler
|
||||
|
||||
from bottle import request, Bottle, abort, static_file
|
||||
|
||||
|
||||
app = Bottle()
|
||||
connections = set()
|
||||
opts = {}
|
||||
|
||||
|
||||
@app.route('/websocket')
|
||||
def handle_websocket():
|
||||
wsock = request.environ.get('wsgi.websocket')
|
||||
if not wsock:
|
||||
abort(400, 'Expected WebSocket request.')
|
||||
|
||||
connections.add(wsock)
|
||||
|
||||
# Send center frequency and span
|
||||
wsock.send(json.dumps(opts))
|
||||
|
||||
while True:
|
||||
try:
|
||||
wsock.receive()
|
||||
except WebSocketError:
|
||||
break
|
||||
|
||||
connections.remove(wsock)
|
||||
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return static_file('index.html', root='.')
|
||||
|
||||
|
||||
@app.route('/<filename>')
|
||||
def static(filename):
|
||||
return static_file(filename, root='.')
|
||||
|
||||
|
||||
class fft_broadcast_sink(gr.sync_block):
|
||||
def __init__(self, fft_size):
|
||||
gr.sync_block.__init__(self,
|
||||
name="plotter",
|
||||
in_sig=[(np.float32, fft_size)],
|
||||
out_sig=[])
|
||||
|
||||
def work(self, input_items, output_items):
|
||||
ninput_items = len(input_items[0])
|
||||
|
||||
for bins in input_items[0]:
|
||||
p = np.around(bins).astype(int)
|
||||
p = np.fft.fftshift(p)
|
||||
for c in connections.copy():
|
||||
try:
|
||||
c.send(json.dumps({'s': p.tolist()}, separators=(',', ':')))
|
||||
except Exception:
|
||||
connections.remove(c)
|
||||
|
||||
self.consume(0, ninput_items)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
class fft_receiver(gr.top_block):
|
||||
def __init__(self, samp_rate, freq, gain, fft_size, framerate):
|
||||
gr.top_block.__init__(self, "Top Block")
|
||||
|
||||
self.usrp = uhd.usrp_source(
|
||||
",".join(("", "")),
|
||||
uhd.stream_args(
|
||||
cpu_format="fc32",
|
||||
channels=range(1),
|
||||
),
|
||||
)
|
||||
self.usrp.set_samp_rate(samp_rate)
|
||||
self.usrp.set_center_freq(freq, 0)
|
||||
self.usrp.set_gain(gain, 0)
|
||||
|
||||
self.fft = logpwrfft.logpwrfft_c(
|
||||
sample_rate=samp_rate,
|
||||
fft_size=fft_size,
|
||||
ref_scale=1,
|
||||
frame_rate=framerate,
|
||||
avg_alpha=1,
|
||||
average=False,
|
||||
)
|
||||
self.fft_broadcast = fft_broadcast_sink(fft_size)
|
||||
|
||||
self.connect((self.fft, 0), (self.fft_broadcast, 0))
|
||||
self.connect((self.usrp, 0), (self.fft, 0))
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('-s', '--sample-rate', type=float, default=40e6)
|
||||
parser.add_argument('-f', '--frequency', type=float, default=940e6)
|
||||
parser.add_argument('-g', '--gain', type=float, default=40)
|
||||
parser.add_argument('-n', '--fft-size', type=int, default=4096)
|
||||
parser.add_argument('-r', '--frame-rate', type=int, default=25)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if gr.enable_realtime_scheduling() != gr.RT_OK or 0:
|
||||
print("Error: failed to enable real-time scheduling.")
|
||||
|
||||
tb = fft_receiver(
|
||||
samp_rate=args.sample_rate,
|
||||
freq=args.frequency,
|
||||
gain=args.gain,
|
||||
fft_size=args.fft_size,
|
||||
framerate=args.frame_rate
|
||||
)
|
||||
tb.start()
|
||||
|
||||
opts['center'] = args.frequency
|
||||
opts['span'] = args.sample_rate
|
||||
|
||||
server = WSGIServer(("0.0.0.0", 8000), app,
|
||||
handler_class=WebSocketHandler)
|
||||
try:
|
||||
server.serve_forever()
|
||||
except Exception:
|
||||
sys.exit(0)
|
||||
|
||||
tb.stop()
|
||||
tb.wait()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,607 +0,0 @@
|
|||
/*=============================================================
|
||||
Filename: Spectrogram-1v00.js
|
||||
|
||||
JavaScript graphics functions to draw Spectrograms.
|
||||
|
||||
Date Description By
|
||||
-------|-------------------------------------------------|---
|
||||
12Nov18 First beta ARC
|
||||
17Nov18 Added offset into data buffer ARC
|
||||
08May19 this.imageURL URL added
|
||||
bugfix: fixed isNaN test
|
||||
Changed sgStart, sgStop to start, stop
|
||||
Added options object to constructors ARC
|
||||
10May19 Enabled Left to Right as well as Top to Bottom ARC
|
||||
11May19 Added RasterscanSVG ARC
|
||||
12May19 Added blnkline for horizontal ratser scans ARC
|
||||
13May19 Eliminated unneccessary putImageData ARC
|
||||
14May19 Removed toDataURL, not used drawImage is better
|
||||
bugfix: SVG RHC names swapped ARC
|
||||
02Jun19 bugfix: startOfs not honored in horizontalNewLine ARC
|
||||
03Jun19 Flipped the SVG and RHC names for waterfalls ARC
|
||||
04Jun19 Unflip SVG and RHC for horizontal mode ARC
|
||||
Swap "SVG" & "RHC" strings to match fn names ARC
|
||||
05Jun19 bugfix: WaterfallSVG scrolling wrong way ARC
|
||||
10Jun19 bugfix: support lineRate=0 for static display
|
||||
bugfix: ipBufPtr must be a ptr to a ptr ARC
|
||||
11Jun19 Make ipBuffers an Array of Arrays, if lineRate=0
|
||||
use all buffers else use only ipBuffer[0] ARC
|
||||
13Jun19 Use Waterfall and Rasterscan plus direction
|
||||
Use Boolean rater than string compare ARC
|
||||
16Jun19 Use const and let ARC
|
||||
20Jun19 Change order of parameters ARC
|
||||
21Jun19 Add setLineRate method ARC
|
||||
06Jul19 Released as Rev 1v00 ARC
|
||||
==============================================================*/
|
||||
|
||||
var Waterfall, Rasterscan;
|
||||
|
||||
(function () {
|
||||
Waterfall = function (ipBufAry, w, h, dir, options) {
|
||||
var direction = typeof dir === "string" ? dir.toLowerCase() : "down";
|
||||
|
||||
switch (direction) {
|
||||
case "up":
|
||||
return new Spectrogram(ipBufAry, w, h, "WF", false, true, options);
|
||||
case "down":
|
||||
default:
|
||||
return new Spectrogram(ipBufAry, w, h, "WF", true, true, options);
|
||||
case "left":
|
||||
return new Spectrogram(ipBufAry, w, h, "WF", false, false, options);
|
||||
case "right":
|
||||
return new Spectrogram(ipBufAry, w, h, "WF", true, false, options);
|
||||
}
|
||||
};
|
||||
|
||||
Rasterscan = function (ipBufAry, w, h, dir, options) {
|
||||
const direction = typeof dir === "string" ? dir.toLowerCase() : "down";
|
||||
|
||||
switch (direction) {
|
||||
case "up":
|
||||
return new Spectrogram(ipBufAry, w, h, "RS", true, true, options);
|
||||
case "down":
|
||||
default:
|
||||
return new Spectrogram(ipBufAry, w, h, "RS", false, true, options);
|
||||
case "left":
|
||||
return new Spectrogram(ipBufAry, w, h, "RS", false, false, options);
|
||||
case "right":
|
||||
return new Spectrogram(ipBufAry, w, h, "RS", true, false, options);
|
||||
}
|
||||
};
|
||||
|
||||
function Spectrogram(ipBufAry, w, h, sgMode, rhc, vert, options) {
|
||||
const opt = typeof options === "object" ? options : {}; // avoid undeclared object errors
|
||||
let offScreenCtx; // offscreen canvas drawing context
|
||||
const pxPerLine = w || 200;
|
||||
const lines = h || 200;
|
||||
let lineRate = 30; // requested line rate for dynamic waterfalls
|
||||
let interval = 0; // msec
|
||||
let startOfs = 0;
|
||||
const lineBuf = new ArrayBuffer(pxPerLine * 4); // 1 line
|
||||
const lineBuf8 = new Uint8ClampedArray(lineBuf);
|
||||
const lineImgData = new ImageData(lineBuf8, pxPerLine, 1); // 1 line of canvas pixels
|
||||
let pageImgData; // lines * pxPerLine of canvas pixels
|
||||
let ipBuf8; // map input data to 0..255 unsigned bytes
|
||||
const blankBuf = new ArrayBuffer(pxPerLine * 4); // 1 line
|
||||
const blankBuf8 = new Uint8ClampedArray(blankBuf);
|
||||
const blankImgData = new ImageData(blankBuf8, pxPerLine, 1); // 1 line of canvas pixels
|
||||
const clearBuf = new ArrayBuffer(pxPerLine * lines * 4); // fills with 0s ie. rgba 0,0,0,0 = transparent
|
||||
const clearBuf8 = new Uint8ClampedArray(clearBuf);
|
||||
let clearImgData;
|
||||
let nextLine = 0;
|
||||
let timerID = null;
|
||||
let running = false;
|
||||
let sgTime = 0;
|
||||
let sgStartTime = 0;
|
||||
|
||||
// Matlab Jet ref: stackoverflow.com grayscale-to-red-green-blue-matlab-jet-color-scale
|
||||
let colMap = [
|
||||
[0, 0, 128, 255],
|
||||
[0, 0, 131, 255],
|
||||
[0, 0, 135, 255],
|
||||
[0, 0, 139, 255],
|
||||
[0, 0, 143, 255],
|
||||
[0, 0, 147, 255],
|
||||
[0, 0, 151, 255],
|
||||
[0, 0, 155, 255],
|
||||
[0, 0, 159, 255],
|
||||
[0, 0, 163, 255],
|
||||
[0, 0, 167, 255],
|
||||
[0, 0, 171, 255],
|
||||
[0, 0, 175, 255],
|
||||
[0, 0, 179, 255],
|
||||
[0, 0, 183, 255],
|
||||
[0, 0, 187, 255],
|
||||
[0, 0, 191, 255],
|
||||
[0, 0, 195, 255],
|
||||
[0, 0, 199, 255],
|
||||
[0, 0, 203, 255],
|
||||
[0, 0, 207, 255],
|
||||
[0, 0, 211, 255],
|
||||
[0, 0, 215, 255],
|
||||
[0, 0, 219, 255],
|
||||
[0, 0, 223, 255],
|
||||
[0, 0, 227, 255],
|
||||
[0, 0, 231, 255],
|
||||
[0, 0, 235, 255],
|
||||
[0, 0, 239, 255],
|
||||
[0, 0, 243, 255],
|
||||
[0, 0, 247, 255],
|
||||
[0, 0, 251, 255],
|
||||
[0, 0, 255, 255],
|
||||
[0, 4, 255, 255],
|
||||
[0, 8, 255, 255],
|
||||
[0, 12, 255, 255],
|
||||
[0, 16, 255, 255],
|
||||
[0, 20, 255, 255],
|
||||
[0, 24, 255, 255],
|
||||
[0, 28, 255, 255],
|
||||
[0, 32, 255, 255],
|
||||
[0, 36, 255, 255],
|
||||
[0, 40, 255, 255],
|
||||
[0, 44, 255, 255],
|
||||
[0, 48, 255, 255],
|
||||
[0, 52, 255, 255],
|
||||
[0, 56, 255, 255],
|
||||
[0, 60, 255, 255],
|
||||
[0, 64, 255, 255],
|
||||
[0, 68, 255, 255],
|
||||
[0, 72, 255, 255],
|
||||
[0, 76, 255, 255],
|
||||
[0, 80, 255, 255],
|
||||
[0, 84, 255, 255],
|
||||
[0, 88, 255, 255],
|
||||
[0, 92, 255, 255],
|
||||
[0, 96, 255, 255],
|
||||
[0, 100, 255, 255],
|
||||
[0, 104, 255, 255],
|
||||
[0, 108, 255, 255],
|
||||
[0, 112, 255, 255],
|
||||
[0, 116, 255, 255],
|
||||
[0, 120, 255, 255],
|
||||
[0, 124, 255, 255],
|
||||
[0, 128, 255, 255],
|
||||
[0, 131, 255, 255],
|
||||
[0, 135, 255, 255],
|
||||
[0, 139, 255, 255],
|
||||
[0, 143, 255, 255],
|
||||
[0, 147, 255, 255],
|
||||
[0, 151, 255, 255],
|
||||
[0, 155, 255, 255],
|
||||
[0, 159, 255, 255],
|
||||
[0, 163, 255, 255],
|
||||
[0, 167, 255, 255],
|
||||
[0, 171, 255, 255],
|
||||
[0, 175, 255, 255],
|
||||
[0, 179, 255, 255],
|
||||
[0, 183, 255, 255],
|
||||
[0, 187, 255, 255],
|
||||
[0, 191, 255, 255],
|
||||
[0, 195, 255, 255],
|
||||
[0, 199, 255, 255],
|
||||
[0, 203, 255, 255],
|
||||
[0, 207, 255, 255],
|
||||
[0, 211, 255, 255],
|
||||
[0, 215, 255, 255],
|
||||
[0, 219, 255, 255],
|
||||
[0, 223, 255, 255],
|
||||
[0, 227, 255, 255],
|
||||
[0, 231, 255, 255],
|
||||
[0, 235, 255, 255],
|
||||
[0, 239, 255, 255],
|
||||
[0, 243, 255, 255],
|
||||
[0, 247, 255, 255],
|
||||
[0, 251, 255, 255],
|
||||
[0, 255, 255, 255],
|
||||
[4, 255, 251, 255],
|
||||
[8, 255, 247, 255],
|
||||
[12, 255, 243, 255],
|
||||
[16, 255, 239, 255],
|
||||
[20, 255, 235, 255],
|
||||
[24, 255, 231, 255],
|
||||
[28, 255, 227, 255],
|
||||
[32, 255, 223, 255],
|
||||
[36, 255, 219, 255],
|
||||
[40, 255, 215, 255],
|
||||
[44, 255, 211, 255],
|
||||
[48, 255, 207, 255],
|
||||
[52, 255, 203, 255],
|
||||
[56, 255, 199, 255],
|
||||
[60, 255, 195, 255],
|
||||
[64, 255, 191, 255],
|
||||
[68, 255, 187, 255],
|
||||
[72, 255, 183, 255],
|
||||
[76, 255, 179, 255],
|
||||
[80, 255, 175, 255],
|
||||
[84, 255, 171, 255],
|
||||
[88, 255, 167, 255],
|
||||
[92, 255, 163, 255],
|
||||
[96, 255, 159, 255],
|
||||
[100, 255, 155, 255],
|
||||
[104, 255, 151, 255],
|
||||
[108, 255, 147, 255],
|
||||
[112, 255, 143, 255],
|
||||
[116, 255, 139, 255],
|
||||
[120, 255, 135, 255],
|
||||
[124, 255, 131, 255],
|
||||
[128, 255, 128, 255],
|
||||
[131, 255, 124, 255],
|
||||
[135, 255, 120, 255],
|
||||
[139, 255, 116, 255],
|
||||
[143, 255, 112, 255],
|
||||
[147, 255, 108, 255],
|
||||
[151, 255, 104, 255],
|
||||
[155, 255, 100, 255],
|
||||
[159, 255, 96, 255],
|
||||
[163, 255, 92, 255],
|
||||
[167, 255, 88, 255],
|
||||
[171, 255, 84, 255],
|
||||
[175, 255, 80, 255],
|
||||
[179, 255, 76, 255],
|
||||
[183, 255, 72, 255],
|
||||
[187, 255, 68, 255],
|
||||
[191, 255, 64, 255],
|
||||
[195, 255, 60, 255],
|
||||
[199, 255, 56, 255],
|
||||
[203, 255, 52, 255],
|
||||
[207, 255, 48, 255],
|
||||
[211, 255, 44, 255],
|
||||
[215, 255, 40, 255],
|
||||
[219, 255, 36, 255],
|
||||
[223, 255, 32, 255],
|
||||
[227, 255, 28, 255],
|
||||
[231, 255, 24, 255],
|
||||
[235, 255, 20, 255],
|
||||
[239, 255, 16, 255],
|
||||
[243, 255, 12, 255],
|
||||
[247, 255, 8, 255],
|
||||
[251, 255, 4, 255],
|
||||
[255, 255, 0, 255],
|
||||
[255, 251, 0, 255],
|
||||
[255, 247, 0, 255],
|
||||
[255, 243, 0, 255],
|
||||
[255, 239, 0, 255],
|
||||
[255, 235, 0, 255],
|
||||
[255, 231, 0, 255],
|
||||
[255, 227, 0, 255],
|
||||
[255, 223, 0, 255],
|
||||
[255, 219, 0, 255],
|
||||
[255, 215, 0, 255],
|
||||
[255, 211, 0, 255],
|
||||
[255, 207, 0, 255],
|
||||
[255, 203, 0, 255],
|
||||
[255, 199, 0, 255],
|
||||
[255, 195, 0, 255],
|
||||
[255, 191, 0, 255],
|
||||
[255, 187, 0, 255],
|
||||
[255, 183, 0, 255],
|
||||
[255, 179, 0, 255],
|
||||
[255, 175, 0, 255],
|
||||
[255, 171, 0, 255],
|
||||
[255, 167, 0, 255],
|
||||
[255, 163, 0, 255],
|
||||
[255, 159, 0, 255],
|
||||
[255, 155, 0, 255],
|
||||
[255, 151, 0, 255],
|
||||
[255, 147, 0, 255],
|
||||
[255, 143, 0, 255],
|
||||
[255, 139, 0, 255],
|
||||
[255, 135, 0, 255],
|
||||
[255, 131, 0, 255],
|
||||
[255, 128, 0, 255],
|
||||
[255, 124, 0, 255],
|
||||
[255, 120, 0, 255],
|
||||
[255, 116, 0, 255],
|
||||
[255, 112, 0, 255],
|
||||
[255, 108, 0, 255],
|
||||
[255, 104, 0, 255],
|
||||
[255, 100, 0, 255],
|
||||
[255, 96, 0, 255],
|
||||
[255, 92, 0, 255],
|
||||
[255, 88, 0, 255],
|
||||
[255, 84, 0, 255],
|
||||
[255, 80, 0, 255],
|
||||
[255, 76, 0, 255],
|
||||
[255, 72, 0, 255],
|
||||
[255, 68, 0, 255],
|
||||
[255, 64, 0, 255],
|
||||
[255, 60, 0, 255],
|
||||
[255, 56, 0, 255],
|
||||
[255, 52, 0, 255],
|
||||
[255, 48, 0, 255],
|
||||
[255, 44, 0, 255],
|
||||
[255, 40, 0, 255],
|
||||
[255, 36, 0, 255],
|
||||
[255, 32, 0, 255],
|
||||
[255, 28, 0, 255],
|
||||
[255, 24, 0, 255],
|
||||
[255, 20, 0, 255],
|
||||
[255, 16, 0, 255],
|
||||
[255, 12, 0, 255],
|
||||
[255, 8, 0, 255],
|
||||
[255, 4, 0, 255],
|
||||
[255, 0, 0, 255],
|
||||
[251, 0, 0, 255],
|
||||
[247, 0, 0, 255],
|
||||
[243, 0, 0, 255],
|
||||
[239, 0, 0, 255],
|
||||
[235, 0, 0, 255],
|
||||
[231, 0, 0, 255],
|
||||
[227, 0, 0, 255],
|
||||
[223, 0, 0, 255],
|
||||
[219, 0, 0, 255],
|
||||
[215, 0, 0, 255],
|
||||
[211, 0, 0, 255],
|
||||
[207, 0, 0, 255],
|
||||
[203, 0, 0, 255],
|
||||
[199, 0, 0, 255],
|
||||
[195, 0, 0, 255],
|
||||
[191, 0, 0, 255],
|
||||
[187, 0, 0, 255],
|
||||
[183, 0, 0, 255],
|
||||
[179, 0, 0, 255],
|
||||
[175, 0, 0, 255],
|
||||
[171, 0, 0, 255],
|
||||
[167, 0, 0, 255],
|
||||
[163, 0, 0, 255],
|
||||
[159, 0, 0, 255],
|
||||
[155, 0, 0, 255],
|
||||
[151, 0, 0, 255],
|
||||
[147, 0, 0, 255],
|
||||
[143, 0, 0, 255],
|
||||
[139, 0, 0, 255],
|
||||
[135, 0, 0, 255],
|
||||
[131, 0, 0, 255],
|
||||
[0, 0, 0, 0],
|
||||
];
|
||||
|
||||
function incrLine() {
|
||||
if ((vert && !rhc) || (!vert && rhc)) {
|
||||
nextLine++;
|
||||
if (nextLine >= lines) {
|
||||
nextLine = 0;
|
||||
}
|
||||
} else {
|
||||
nextLine--;
|
||||
if (nextLine < 0) {
|
||||
nextLine = lines - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateWaterfall() {
|
||||
// update dynamic waterfalls at a fixed rate
|
||||
let sgDiff;
|
||||
|
||||
// grab latest line of data, write it to off screen buffer, inc 'nextLine'
|
||||
sgNewLine();
|
||||
// loop to write data data at the desired rate, data is being updated asynchronously
|
||||
// ref for accurate timeout: http://www.sitepoint.com/creating-accurate-timers-in-javascript
|
||||
sgTime += interval;
|
||||
sgDiff = Date.now() - sgStartTime - sgTime;
|
||||
if (running) {
|
||||
timerID = setTimeout(updateWaterfall, interval - sgDiff);
|
||||
}
|
||||
}
|
||||
|
||||
function sgSetLineRate(newRate) {
|
||||
if (isNaN(newRate) || newRate > 50 || newRate < 0) {
|
||||
console.error("invalid line rate [0 <= lineRate < 50 lines/sec]");
|
||||
// don't change the lineRate;
|
||||
} else if (newRate === 0) {
|
||||
// static (one pass) raster
|
||||
lineRate = 0;
|
||||
} else {
|
||||
lineRate = newRate;
|
||||
interval = 1000 / lineRate; // msec
|
||||
}
|
||||
}
|
||||
|
||||
this.setLineRate = sgSetLineRate;
|
||||
|
||||
function setProperty(propertyName, value) {
|
||||
if (typeof propertyName !== "string" || value === undefined) {
|
||||
// null is OK, forces default
|
||||
return;
|
||||
}
|
||||
switch (propertyName.toLowerCase()) {
|
||||
case "linerate":
|
||||
sgSetLineRate(value); // setLine does checks for number etc
|
||||
break;
|
||||
case "startbin":
|
||||
if (!isNaN(value) && value > 0) {
|
||||
startOfs = value;
|
||||
}
|
||||
break;
|
||||
case "onscreenparentid":
|
||||
if (typeof value === "string" && document.getElementById(value)) {
|
||||
demoCvsId = value;
|
||||
}
|
||||
break;
|
||||
case "colormap":
|
||||
if (
|
||||
Array.isArray(value) &&
|
||||
Array.isArray(value[0]) &&
|
||||
value[0].length == 4
|
||||
) {
|
||||
colMap = value; // value must be an array of 4 element arrays to get here
|
||||
if (colMap.length < 256) {
|
||||
// fill out the remaining colors with last color
|
||||
for (let i = colMap.length; i < 256; i++) {
|
||||
colMap[i] = colMap[colMap.length - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function verticalNewLine() {
|
||||
let tmpImgData, ipBuf8;
|
||||
|
||||
if (sgMode == "WF") {
|
||||
if (rhc) {
|
||||
// shift the current display down 1 line, oldest line drops off
|
||||
tmpImgData = offScreenCtx.getImageData(0, 0, pxPerLine, lines - 1);
|
||||
offScreenCtx.putImageData(tmpImgData, 0, 1);
|
||||
} else {
|
||||
// shift the current display up 1 line, oldest line drops off
|
||||
tmpImgData = offScreenCtx.getImageData(0, 1, pxPerLine, lines - 1);
|
||||
offScreenCtx.putImageData(tmpImgData, 0, 0);
|
||||
}
|
||||
}
|
||||
ipBuf8 = Uint8ClampedArray.from(ipBufAry[0]);
|
||||
for (
|
||||
let sigVal, rgba, opIdx = 0, ipIdx = startOfs;
|
||||
ipIdx < pxPerLine + startOfs;
|
||||
opIdx += 4, ipIdx++
|
||||
) {
|
||||
sigVal = ipBuf8[ipIdx] || 0; // if input line too short add zeros
|
||||
rgba = colMap[sigVal]; // array of rgba values
|
||||
// byte reverse so number aa bb gg rr
|
||||
lineBuf8[opIdx] = rgba[0]; // red
|
||||
lineBuf8[opIdx + 1] = rgba[1]; // green
|
||||
lineBuf8[opIdx + 2] = rgba[2]; // blue
|
||||
lineBuf8[opIdx + 3] = rgba[3]; // alpha
|
||||
}
|
||||
offScreenCtx.putImageData(lineImgData, 0, nextLine);
|
||||
if (sgMode === "RS") {
|
||||
incrLine();
|
||||
// if not static draw a white line in front of the current line to indicate new data point
|
||||
if (lineRate) {
|
||||
offScreenCtx.putImageData(blankImgData, 0, nextLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function horizontalNewLine() {
|
||||
let tmpImgData, ipBuf8;
|
||||
|
||||
if (sgMode == "WF") {
|
||||
if (rhc) {
|
||||
// shift the current display right 1 line, oldest line drops off
|
||||
tmpImgData = offScreenCtx.getImageData(0, 0, lines - 1, pxPerLine);
|
||||
offScreenCtx.putImageData(tmpImgData, 1, 0);
|
||||
} else {
|
||||
// shift the current display left 1 line, oldest line drops off
|
||||
tmpImgData = offScreenCtx.getImageData(1, 0, lines - 1, pxPerLine);
|
||||
offScreenCtx.putImageData(tmpImgData, 0, 0);
|
||||
}
|
||||
}
|
||||
// refresh the page image (it was just shifted)
|
||||
pageImgData = offScreenCtx.getImageData(0, 0, lines, pxPerLine);
|
||||
if (ipBufAry[0].constructor !== Uint8Array) {
|
||||
ipBuf8 = Uint8ClampedArray.from(ipBufAry[0]); // clamp input values to 0..255 range
|
||||
} else {
|
||||
ipBuf8 = ipBufAry[0]; // conversion already done
|
||||
}
|
||||
|
||||
for (let sigVal, rgba, opIdx, ipIdx = 0; ipIdx < pxPerLine; ipIdx++) {
|
||||
sigVal = ipBuf8[ipIdx + startOfs] || 0; // if input line too short add zeros
|
||||
rgba = colMap[sigVal]; // array of rgba values
|
||||
opIdx = 4 * ((pxPerLine - ipIdx - 1) * lines + nextLine);
|
||||
// byte reverse so number aa bb gg rr
|
||||
pageImgData.data[opIdx] = rgba[0]; // red
|
||||
pageImgData.data[opIdx + 1] = rgba[1]; // green
|
||||
pageImgData.data[opIdx + 2] = rgba[2]; // blue
|
||||
pageImgData.data[opIdx + 3] = rgba[3]; // alpha
|
||||
}
|
||||
if (sgMode === "RS") {
|
||||
incrLine();
|
||||
// if not draw a white line in front of the current line to indicate new data point
|
||||
if (lineRate) {
|
||||
for (let j = 0; j < pxPerLine; j++) {
|
||||
if (rhc) {
|
||||
opIdx = 4 * (j * lines + nextLine);
|
||||
} else {
|
||||
opIdx = 4 * ((pxPerLine - j - 1) * lines + nextLine);
|
||||
}
|
||||
// byte reverse so number aa bb gg rr
|
||||
pageImgData.data[opIdx] = 255; // red
|
||||
pageImgData.data[opIdx + 1] = 255; // green
|
||||
pageImgData.data[opIdx + 2] = 255; // blue
|
||||
pageImgData.data[opIdx + 3] = 255; // alpha
|
||||
}
|
||||
}
|
||||
}
|
||||
offScreenCtx.putImageData(pageImgData, 0, 0);
|
||||
}
|
||||
|
||||
const sgNewLine = vert ? verticalNewLine : horizontalNewLine; // function pointers
|
||||
|
||||
//===== set all the options ================
|
||||
for (let prop in opt) {
|
||||
// check that this is opt's own property, not inherited from prototype
|
||||
if (opt.hasOwnProperty(prop)) {
|
||||
setProperty(prop, opt[prop]);
|
||||
}
|
||||
}
|
||||
|
||||
// ===== now make the exposed properties and methods ===============
|
||||
this.newLine = sgNewLine;
|
||||
|
||||
this.offScreenCvs = document.createElement("canvas");
|
||||
if (vert) {
|
||||
this.offScreenCvs.setAttribute("width", pxPerLine); // reset canvas pixels width
|
||||
this.offScreenCvs.setAttribute("height", lines); // don't use style for this
|
||||
clearImgData = new ImageData(clearBuf8, pxPerLine, lines);
|
||||
} // data written in columns
|
||||
else {
|
||||
this.offScreenCvs.setAttribute("width", lines); // reset canvas pixels width
|
||||
this.offScreenCvs.setAttribute("height", pxPerLine); // don't use style for this
|
||||
clearImgData = new ImageData(clearBuf8, lines, pxPerLine);
|
||||
}
|
||||
offScreenCtx = this.offScreenCvs.getContext("2d");
|
||||
|
||||
this.clear = function () {
|
||||
offScreenCtx.putImageData(clearImgData, 0, 0);
|
||||
};
|
||||
|
||||
this.start = function () {
|
||||
sgStartTime = Date.now();
|
||||
sgTime = 0;
|
||||
running = true;
|
||||
updateWaterfall(); // start the update loop
|
||||
};
|
||||
|
||||
this.stop = function () {
|
||||
running = false;
|
||||
if (timerID) {
|
||||
clearTimeout(timerID);
|
||||
}
|
||||
// reset where the next line is to be written
|
||||
if (sgMode === "RS") {
|
||||
if (vert) {
|
||||
nextLine = rhc ? lines - 1 : 0;
|
||||
} else {
|
||||
nextLine = rhc ? 0 : lines - 1;
|
||||
}
|
||||
} // WF
|
||||
else {
|
||||
nextLine = rhc ? 0 : lines - 1;
|
||||
}
|
||||
};
|
||||
|
||||
// make a white line, it will show the input line for RS displays
|
||||
blankBuf8.fill(255);
|
||||
// make a full canvas of the color map 0 values
|
||||
for (let i = 0; i < pxPerLine * lines * 4; i += 4) {
|
||||
// byte reverse so number aa bb gg rr
|
||||
clearBuf8[i] = colMap[0][0]; // red
|
||||
clearBuf8[i + 1] = colMap[0][1]; // green
|
||||
clearBuf8[i + 2] = colMap[0][2]; // blue
|
||||
clearBuf8[i + 3] = colMap[0][3]; // alpha
|
||||
}
|
||||
// for diagnostics only
|
||||
if (typeof demoCvsId == "string") {
|
||||
document.getElementById(demoCvsId).appendChild(this.offScreenCvs);
|
||||
}
|
||||
// initialize the direction and first line position
|
||||
this.stop();
|
||||
|
||||
// everything is set
|
||||
// if dynamic, wait for the start or newLine methods to be called
|
||||
}
|
||||
})();
|
|
@ -1,488 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Jeppe Ledet-Pedersen
|
||||
* This software is released under the MIT license.
|
||||
* See the LICENSE file for further details.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
Spectrum.prototype.squeeze = function (value, out_min, out_max) {
|
||||
if (value <= this.min_db) return out_min;
|
||||
else if (value >= this.max_db) return out_max;
|
||||
else
|
||||
return Math.round(
|
||||
((value - this.min_db) / (this.max_db - this.min_db)) * out_max
|
||||
);
|
||||
};
|
||||
|
||||
Spectrum.prototype.rowToImageData = function (bins) {
|
||||
for (var i = 0; i < this.imagedata.data.length; i += 4) {
|
||||
var cindex = this.squeeze(bins[i / 4], 0, 255);
|
||||
var color = this.colormap[cindex];
|
||||
this.imagedata.data[i + 0] = color[0];
|
||||
this.imagedata.data[i + 1] = color[1];
|
||||
this.imagedata.data[i + 2] = color[2];
|
||||
this.imagedata.data[i + 3] = 255;
|
||||
}
|
||||
};
|
||||
|
||||
Spectrum.prototype.addWaterfallRow = function (bins) {
|
||||
// Shift waterfall 1 row down
|
||||
this.ctx_wf.drawImage(
|
||||
this.ctx_wf.canvas,
|
||||
0,
|
||||
0,
|
||||
this.wf_size,
|
||||
this.wf_rows - 1,
|
||||
0,
|
||||
1,
|
||||
this.wf_size,
|
||||
this.wf_rows - 1
|
||||
);
|
||||
|
||||
// Draw new line on waterfall canvas
|
||||
this.rowToImageData(bins);
|
||||
this.ctx_wf.putImageData(this.imagedata, 0, 0);
|
||||
|
||||
var width = this.ctx.canvas.width;
|
||||
var height = this.ctx.canvas.height;
|
||||
|
||||
// Copy scaled FFT canvas to screen. Only copy the number of rows that will
|
||||
// fit in waterfall area to avoid vertical scaling.
|
||||
this.ctx.imageSmoothingEnabled = false;
|
||||
var rows = Math.min(this.wf_rows, height - this.spectrumHeight);
|
||||
this.ctx.drawImage(
|
||||
this.ctx_wf.canvas,
|
||||
0,
|
||||
0,
|
||||
this.wf_size,
|
||||
rows,
|
||||
0,
|
||||
this.spectrumHeight,
|
||||
width,
|
||||
height - this.spectrumHeight
|
||||
);
|
||||
};
|
||||
|
||||
Spectrum.prototype.drawFFT = function (bins) {
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(-1, this.spectrumHeight + 1);
|
||||
for (var i = 0; i < bins.length; i++) {
|
||||
var y = this.spectrumHeight - this.squeeze(bins[i], 0, this.spectrumHeight);
|
||||
if (y > this.spectrumHeight - 1) y = this.spectrumHeight + 1; // Hide underflow
|
||||
if (y < 0) y = 0;
|
||||
if (i == 0) this.ctx.lineTo(-1, y);
|
||||
this.ctx.lineTo(i, y);
|
||||
if (i == bins.length - 1) this.ctx.lineTo(this.wf_size + 1, y);
|
||||
}
|
||||
this.ctx.lineTo(this.wf_size + 1, this.spectrumHeight + 1);
|
||||
this.ctx.strokeStyle = "#fefefe";
|
||||
this.ctx.stroke();
|
||||
};
|
||||
|
||||
//Spectrum.prototype.drawSpectrum = function(bins) {
|
||||
Spectrum.prototype.drawSpectrum = function () {
|
||||
var width = this.ctx.canvas.width;
|
||||
var height = this.ctx.canvas.height;
|
||||
|
||||
// Modification by DJ2LS
|
||||
// Draw bandwidth lines
|
||||
// TODO: Math not correct. But a first attempt
|
||||
// it seems position is more or less equal to frequenzy by factor 10
|
||||
// eg. position 150 == 1500Hz
|
||||
/*
|
||||
// CENTER LINE
|
||||
this.ctx_wf.beginPath();
|
||||
this.ctx_wf.moveTo(150,0);
|
||||
this.ctx_wf.lineTo(150, height);
|
||||
this.ctx_wf.lineWidth = 1;
|
||||
this.ctx_wf.strokeStyle = '#8C8C8C';
|
||||
this.ctx_wf.stroke()
|
||||
*/
|
||||
|
||||
// 586Hz and 1700Hz LINES
|
||||
var linePositionLow = 121.6; //150 - bandwith/20
|
||||
var linePositionHigh = 178.4; //150 + bandwidth/20
|
||||
var linePositionLow2 = 65; //150 - bandwith/20
|
||||
var linePositionHigh2 = 235; //150 + bandwith/20
|
||||
this.ctx_wf.beginPath();
|
||||
this.ctx_wf.moveTo(linePositionLow, 0);
|
||||
this.ctx_wf.lineTo(linePositionLow, height);
|
||||
this.ctx_wf.moveTo(linePositionHigh, 0);
|
||||
this.ctx_wf.lineTo(linePositionHigh, height);
|
||||
this.ctx_wf.moveTo(linePositionLow2, 0);
|
||||
this.ctx_wf.lineTo(linePositionLow2, height);
|
||||
this.ctx_wf.moveTo(linePositionHigh2, 0);
|
||||
this.ctx_wf.lineTo(linePositionHigh2, height);
|
||||
this.ctx_wf.lineWidth = 1;
|
||||
this.ctx_wf.strokeStyle = "#C3C3C3";
|
||||
this.ctx_wf.stroke();
|
||||
|
||||
// ---- END OF MODIFICATION ------
|
||||
|
||||
// Fill with black
|
||||
this.ctx.fillStyle = "white";
|
||||
this.ctx.fillRect(0, 0, width, height);
|
||||
|
||||
//Commenting out the remainder of this code, it's not needed and unused as of 6.9.11 and saves three if statements
|
||||
return;
|
||||
/*
|
||||
// FFT averaging
|
||||
if (this.averaging > 0) {
|
||||
if (!this.binsAverage || this.binsAverage.length != bins.length) {
|
||||
this.binsAverage = Array.from(bins);
|
||||
} else {
|
||||
for (var i = 0; i < bins.length; i++) {
|
||||
this.binsAverage[i] += this.alpha * (bins[i] - this.binsAverage[i]);
|
||||
}
|
||||
}
|
||||
bins = this.binsAverage;
|
||||
}
|
||||
|
||||
// Max hold
|
||||
if (this.maxHold) {
|
||||
if (!this.binsMax || this.binsMax.length != bins.length) {
|
||||
this.binsMax = Array.from(bins);
|
||||
} else {
|
||||
for (var i = 0; i < bins.length; i++) {
|
||||
if (bins[i] > this.binsMax[i]) {
|
||||
this.binsMax[i] = bins[i];
|
||||
} else {
|
||||
// Decay
|
||||
this.binsMax[i] = 1.0025 * this.binsMax[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Do not draw anything if spectrum is not visible
|
||||
if (this.ctx_axes.canvas.height < 1)
|
||||
return;
|
||||
|
||||
// Scale for FFT
|
||||
this.ctx.save();
|
||||
this.ctx.scale(width / this.wf_size, 1);
|
||||
|
||||
// Draw maxhold
|
||||
if (this.maxHold)
|
||||
this.drawFFT(this.binsMax);
|
||||
|
||||
// Draw FFT bins
|
||||
this.drawFFT(bins);
|
||||
|
||||
// Restore scale
|
||||
this.ctx.restore();
|
||||
|
||||
// Fill scaled path
|
||||
this.ctx.fillStyle = this.gradient;
|
||||
this.ctx.fill();
|
||||
|
||||
// Copy axes from offscreen canvas
|
||||
this.ctx.drawImage(this.ctx_axes.canvas, 0, 0);
|
||||
*/
|
||||
};
|
||||
|
||||
//Allow setting colormap
|
||||
Spectrum.prototype.setColorMap = function (index) {
|
||||
this.colormap = colormaps[index];
|
||||
};
|
||||
|
||||
Spectrum.prototype.updateAxes = function () {
|
||||
var width = this.ctx_axes.canvas.width;
|
||||
var height = this.ctx_axes.canvas.height;
|
||||
|
||||
// Clear axes canvas
|
||||
this.ctx_axes.clearRect(0, 0, width, height);
|
||||
|
||||
// Draw axes
|
||||
this.ctx_axes.font = "12px sans-serif";
|
||||
this.ctx_axes.fillStyle = "white";
|
||||
this.ctx_axes.textBaseline = "middle";
|
||||
|
||||
this.ctx_axes.textAlign = "left";
|
||||
var step = 10;
|
||||
for (var i = this.min_db + 10; i <= this.max_db - 10; i += step) {
|
||||
var y = height - this.squeeze(i, 0, height);
|
||||
this.ctx_axes.fillText(i, 5, y);
|
||||
|
||||
this.ctx_axes.beginPath();
|
||||
this.ctx_axes.moveTo(20, y);
|
||||
this.ctx_axes.lineTo(width, y);
|
||||
this.ctx_axes.strokeStyle = "rgba(200, 200, 200, 0.10)";
|
||||
this.ctx_axes.stroke();
|
||||
}
|
||||
|
||||
this.ctx_axes.textBaseline = "bottom";
|
||||
for (var i = 0; i < 11; i++) {
|
||||
var x = Math.round(width / 10) * i;
|
||||
|
||||
if (this.spanHz > 0) {
|
||||
var adjust = 0;
|
||||
if (i == 0) {
|
||||
this.ctx_axes.textAlign = "left";
|
||||
adjust = 3;
|
||||
} else if (i == 10) {
|
||||
this.ctx_axes.textAlign = "right";
|
||||
adjust = -3;
|
||||
} else {
|
||||
this.ctx_axes.textAlign = "center";
|
||||
}
|
||||
|
||||
var freq = this.centerHz + (this.spanHz / 10) * (i - 5);
|
||||
if (this.centerHz + this.spanHz > 1e6) freq = freq / 1e6 + "M";
|
||||
else if (this.centerHz + this.spanHz > 1e3) freq = freq / 1e3 + "k";
|
||||
this.ctx_axes.fillText(freq, x + adjust, height - 3);
|
||||
}
|
||||
|
||||
this.ctx_axes.beginPath();
|
||||
this.ctx_axes.moveTo(x, 0);
|
||||
this.ctx_axes.lineTo(x, height);
|
||||
this.ctx_axes.strokeStyle = "rgba(200, 200, 200, 0.10)";
|
||||
this.ctx_axes.stroke();
|
||||
}
|
||||
};
|
||||
|
||||
Spectrum.prototype.addData = function (data) {
|
||||
if (!this.paused) {
|
||||
if (data.length != this.wf_size) {
|
||||
this.wf_size = data.length;
|
||||
this.ctx_wf.canvas.width = data.length;
|
||||
this.ctx_wf.fillStyle = "white";
|
||||
this.ctx_wf.fillRect(0, 0, this.wf.width, this.wf.height);
|
||||
this.imagedata = this.ctx_wf.createImageData(data.length, 1);
|
||||
}
|
||||
//this.drawSpectrum(data);
|
||||
this.drawSpectrum();
|
||||
this.addWaterfallRow(data);
|
||||
this.resize();
|
||||
}
|
||||
};
|
||||
|
||||
Spectrum.prototype.updateSpectrumRatio = function () {
|
||||
this.spectrumHeight = Math.round(
|
||||
(this.canvas.height * this.spectrumPercent) / 100.0
|
||||
);
|
||||
|
||||
this.gradient = this.ctx.createLinearGradient(0, 0, 0, this.spectrumHeight);
|
||||
for (var i = 0; i < this.colormap.length; i++) {
|
||||
var c = this.colormap[this.colormap.length - 1 - i];
|
||||
this.gradient.addColorStop(
|
||||
i / this.colormap.length,
|
||||
"rgba(" + c[0] + "," + c[1] + "," + c[2] + ", 1.0)"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Spectrum.prototype.resize = function () {
|
||||
var width = this.canvas.clientWidth;
|
||||
var height = this.canvas.clientHeight;
|
||||
|
||||
if (this.canvas.width != width || this.canvas.height != height) {
|
||||
this.canvas.width = width;
|
||||
this.canvas.height = height;
|
||||
this.updateSpectrumRatio();
|
||||
}
|
||||
|
||||
if (this.axes.width != width || this.axes.height != this.spectrumHeight) {
|
||||
this.axes.width = width;
|
||||
this.axes.height = this.spectrumHeight;
|
||||
this.updateAxes();
|
||||
}
|
||||
};
|
||||
|
||||
Spectrum.prototype.setSpectrumPercent = function (percent) {
|
||||
if (percent >= 0 && percent <= 100) {
|
||||
this.spectrumPercent = percent;
|
||||
this.updateSpectrumRatio();
|
||||
}
|
||||
};
|
||||
|
||||
Spectrum.prototype.incrementSpectrumPercent = function () {
|
||||
if (this.spectrumPercent + this.spectrumPercentStep <= 100) {
|
||||
this.setSpectrumPercent(this.spectrumPercent + this.spectrumPercentStep);
|
||||
}
|
||||
};
|
||||
|
||||
Spectrum.prototype.decrementSpectrumPercent = function () {
|
||||
if (this.spectrumPercent - this.spectrumPercentStep >= 0) {
|
||||
this.setSpectrumPercent(this.spectrumPercent - this.spectrumPercentStep);
|
||||
}
|
||||
};
|
||||
|
||||
Spectrum.prototype.toggleColor = function () {
|
||||
this.colorindex++;
|
||||
if (this.colorindex >= colormaps.length) this.colorindex = 0;
|
||||
this.colormap = colormaps[this.colorindex];
|
||||
this.updateSpectrumRatio();
|
||||
};
|
||||
|
||||
Spectrum.prototype.setRange = function (min_db, max_db) {
|
||||
this.min_db = min_db;
|
||||
this.max_db = max_db;
|
||||
this.updateAxes();
|
||||
};
|
||||
|
||||
Spectrum.prototype.rangeUp = function () {
|
||||
this.setRange(this.min_db - 5, this.max_db - 5);
|
||||
};
|
||||
|
||||
Spectrum.prototype.rangeDown = function () {
|
||||
this.setRange(this.min_db + 5, this.max_db + 5);
|
||||
};
|
||||
|
||||
Spectrum.prototype.rangeIncrease = function () {
|
||||
this.setRange(this.min_db - 5, this.max_db + 5);
|
||||
};
|
||||
|
||||
Spectrum.prototype.rangeDecrease = function () {
|
||||
if (this.max_db - this.min_db > 10)
|
||||
this.setRange(this.min_db + 5, this.max_db - 5);
|
||||
};
|
||||
|
||||
Spectrum.prototype.setCenterHz = function (hz) {
|
||||
this.centerHz = hz;
|
||||
this.updateAxes();
|
||||
};
|
||||
|
||||
Spectrum.prototype.setSpanHz = function (hz) {
|
||||
this.spanHz = hz;
|
||||
this.updateAxes();
|
||||
};
|
||||
|
||||
Spectrum.prototype.setAveraging = function (num) {
|
||||
if (num >= 0) {
|
||||
this.averaging = num;
|
||||
this.alpha = 2 / (this.averaging + 1);
|
||||
}
|
||||
};
|
||||
|
||||
Spectrum.prototype.incrementAveraging = function () {
|
||||
this.setAveraging(this.averaging + 1);
|
||||
};
|
||||
|
||||
Spectrum.prototype.decrementAveraging = function () {
|
||||
if (this.averaging > 0) {
|
||||
this.setAveraging(this.averaging - 1);
|
||||
}
|
||||
};
|
||||
|
||||
Spectrum.prototype.setPaused = function (paused) {
|
||||
this.paused = paused;
|
||||
};
|
||||
|
||||
Spectrum.prototype.togglePaused = function () {
|
||||
this.setPaused(!this.paused);
|
||||
};
|
||||
|
||||
Spectrum.prototype.setMaxHold = function (maxhold) {
|
||||
this.maxHold = maxhold;
|
||||
this.binsMax = undefined;
|
||||
};
|
||||
|
||||
Spectrum.prototype.toggleMaxHold = function () {
|
||||
this.setMaxHold(!this.maxHold);
|
||||
};
|
||||
|
||||
Spectrum.prototype.toggleFullscreen = function () {
|
||||
if (!this.fullscreen) {
|
||||
if (this.canvas.requestFullscreen) {
|
||||
this.canvas.requestFullscreen();
|
||||
} else if (this.canvas.mozRequestFullScreen) {
|
||||
this.canvas.mozRequestFullScreen();
|
||||
} else if (this.canvas.webkitRequestFullscreen) {
|
||||
this.canvas.webkitRequestFullscreen();
|
||||
} else if (this.canvas.msRequestFullscreen) {
|
||||
this.canvas.msRequestFullscreen();
|
||||
}
|
||||
this.fullscreen = true;
|
||||
} else {
|
||||
if (document.exitFullscreen) {
|
||||
document.exitFullscreen();
|
||||
} else if (document.mozCancelFullScreen) {
|
||||
document.mozCancelFullScreen();
|
||||
} else if (document.webkitExitFullscreen) {
|
||||
document.webkitExitFullscreen();
|
||||
} else if (document.msExitFullscreen) {
|
||||
document.msExitFullscreen();
|
||||
}
|
||||
this.fullscreen = false;
|
||||
}
|
||||
};
|
||||
|
||||
Spectrum.prototype.onKeypress = function (e) {
|
||||
if (e.key == " ") {
|
||||
this.togglePaused();
|
||||
} else if (e.key == "f") {
|
||||
this.toggleFullscreen();
|
||||
} else if (e.key == "c") {
|
||||
this.toggleColor();
|
||||
} else if (e.key == "ArrowUp") {
|
||||
this.rangeUp();
|
||||
} else if (e.key == "ArrowDown") {
|
||||
this.rangeDown();
|
||||
} else if (e.key == "ArrowLeft") {
|
||||
this.rangeDecrease();
|
||||
} else if (e.key == "ArrowRight") {
|
||||
this.rangeIncrease();
|
||||
} else if (e.key == "s") {
|
||||
this.incrementSpectrumPercent();
|
||||
} else if (e.key == "w") {
|
||||
this.decrementSpectrumPercent();
|
||||
} else if (e.key == "+") {
|
||||
this.incrementAveraging();
|
||||
} else if (e.key == "-") {
|
||||
this.decrementAveraging();
|
||||
} else if (e.key == "m") {
|
||||
this.toggleMaxHold();
|
||||
}
|
||||
};
|
||||
|
||||
function Spectrum(id, options) {
|
||||
// Handle options
|
||||
this.centerHz = options && options.centerHz ? options.centerHz : 1500;
|
||||
this.spanHz = options && options.spanHz ? options.spanHz : 0;
|
||||
this.wf_size = options && options.wf_size ? options.wf_size : 0;
|
||||
this.wf_rows = options && options.wf_rows ? options.wf_rows : 1024;
|
||||
this.spectrumPercent =
|
||||
options && options.spectrumPercent ? options.spectrumPercent : 0;
|
||||
this.spectrumPercentStep =
|
||||
options && options.spectrumPercentStep ? options.spectrumPercentStep : 0;
|
||||
this.averaging = options && options.averaging ? options.averaging : 0;
|
||||
this.maxHold = options && options.maxHold ? options.maxHold : false;
|
||||
|
||||
// Setup state
|
||||
this.paused = false;
|
||||
this.fullscreen = false;
|
||||
this.min_db = 0;
|
||||
this.max_db = 70;
|
||||
this.spectrumHeight = 0;
|
||||
|
||||
// Colors
|
||||
this.colorindex = 0;
|
||||
this.colormap = colormaps[2];
|
||||
|
||||
// Create main canvas and adjust dimensions to match actual
|
||||
this.canvas = document.getElementById(id);
|
||||
this.canvas.height = this.canvas.clientHeight;
|
||||
this.canvas.width = this.canvas.clientWidth;
|
||||
this.ctx = this.canvas.getContext("2d");
|
||||
this.ctx.fillStyle = "white";
|
||||
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
|
||||
// Create offscreen canvas for axes
|
||||
this.axes = document.createElement("canvas");
|
||||
this.axes.height = 1; // Updated later
|
||||
this.axes.width = this.canvas.width;
|
||||
this.ctx_axes = this.axes.getContext("2d");
|
||||
|
||||
// Create offscreen canvas for waterfall
|
||||
this.wf = document.createElement("canvas");
|
||||
this.wf.height = this.wf_rows;
|
||||
this.wf.width = this.wf_size;
|
||||
this.ctx_wf = this.wf.getContext("2d");
|
||||
|
||||
// Trigger first render
|
||||
this.setAveraging(this.averaging);
|
||||
this.updateSpectrumRatio();
|
||||
this.resize();
|
||||
}
|
39
gui/tsconfig.json
Normal file
39
gui/tsconfig.json
Normal file
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"strict": false,
|
||||
"jsx": "preserve",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"esModuleInterop": true,
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"skipLibCheck": true,
|
||||
"noEmit": true,
|
||||
"allowJs": true,
|
||||
"noImplicitAny": false
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules"],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
10
gui/tsconfig.node.json
Normal file
10
gui/tsconfig.node.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"resolveJsonModule": true,
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts", "package.json", "electron"]
|
||||
}
|
94
gui/vite.config.ts
Normal file
94
gui/vite.config.ts
Normal file
|
@ -0,0 +1,94 @@
|
|||
import { rmSync } from "node:fs";
|
||||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import electron from "vite-plugin-electron";
|
||||
import renderer from "vite-plugin-electron-renderer";
|
||||
import { notBundle } from "vite-plugin-electron/plugin";
|
||||
import pkg from "./package.json";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig(({ command }) => {
|
||||
rmSync("dist-electron", { recursive: true, force: true });
|
||||
|
||||
const isServe = command === "serve";
|
||||
const isBuild = command === "build";
|
||||
const sourcemap = isServe || !!process.env.VSCODE_DEBUG;
|
||||
|
||||
return {
|
||||
plugins: [
|
||||
vue(),
|
||||
electron([
|
||||
{
|
||||
// Main process entry file of the Electron App.
|
||||
entry: "electron/main/index.ts",
|
||||
onstart({ startup }) {
|
||||
if (process.env.VSCODE_DEBUG) {
|
||||
console.log(
|
||||
/* For `.vscode/.debug.script.mjs` */ "[startup] Electron App",
|
||||
);
|
||||
} else {
|
||||
startup();
|
||||
}
|
||||
},
|
||||
vite: {
|
||||
build: {
|
||||
sourcemap,
|
||||
minify: isBuild,
|
||||
outDir: "dist-electron/main",
|
||||
rollupOptions: {
|
||||
// Some third-party Node.js libraries may not be built correctly by Vite, especially `C/C++` addons,
|
||||
// we can use `external` to exclude them to ensure they work correctly.
|
||||
// Others need to put them in `dependencies` to ensure they are collected into `app.asar` after the app is built.
|
||||
// Of course, this is not absolute, just this way is relatively simple. :)
|
||||
external: Object.keys(
|
||||
"dependencies" in pkg ? pkg.dependencies : {},
|
||||
),
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
// This is just an option to improve build performance, it's non-deterministic!
|
||||
// e.g. `import log from 'electron-log'` -> `const log = require('electron-log')`
|
||||
isServe && notBundle(),
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
entry: "electron/preload/index.ts",
|
||||
onstart({ reload }) {
|
||||
// Notify the Renderer process to reload the page when the Preload scripts build is complete,
|
||||
// instead of restarting the entire Electron App.
|
||||
reload();
|
||||
},
|
||||
vite: {
|
||||
build: {
|
||||
sourcemap: sourcemap ? "inline" : undefined, // #332
|
||||
minify: isBuild,
|
||||
outDir: "dist-electron/preload",
|
||||
rollupOptions: {
|
||||
external: Object.keys(
|
||||
"dependencies" in pkg ? pkg.dependencies : {},
|
||||
),
|
||||
},
|
||||
},
|
||||
plugins: [isServe && notBundle()],
|
||||
},
|
||||
},
|
||||
]),
|
||||
// Use Node.js API in the Renderer process
|
||||
renderer(),
|
||||
],
|
||||
server:
|
||||
process.env.VSCODE_DEBUG &&
|
||||
(() => {
|
||||
const url = new URL(pkg.debug.env.VITE_DEV_SERVER_URL);
|
||||
return {
|
||||
host: url.hostname,
|
||||
port: +url.port,
|
||||
};
|
||||
})(),
|
||||
define: {
|
||||
"import.meta.env.PACKAGE_VERSION": JSON.stringify(pkg.version),
|
||||
},
|
||||
clearScreen: false,
|
||||
};
|
||||
});
|
0
tnc/.gitignore → modem/.gitignore
vendored
0
tnc/.gitignore → modem/.gitignore
vendored
|
@ -4,7 +4,7 @@ import helpers
|
|||
import time
|
||||
import modem
|
||||
import base64
|
||||
from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC
|
||||
from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem
|
||||
import sock
|
||||
import ujson as json
|
||||
|
||||
|
@ -34,7 +34,7 @@ class broadcastHandler:
|
|||
modem.RECEIVE_DATAC4 = True
|
||||
|
||||
self.send_data_to_socket_queue(
|
||||
freedata="tnc-message",
|
||||
freedata="modem-message",
|
||||
fec="wakeup",
|
||||
mode=self.wakeup_mode,
|
||||
bursts=bursts,
|
||||
|
@ -42,7 +42,7 @@ class broadcastHandler:
|
|||
)
|
||||
|
||||
self.log.info(
|
||||
"[TNC] FRAME WAKEUP RCVD ["
|
||||
"[Modem] FRAME WAKEUP RCVD ["
|
||||
+ str(self.fec_wakeup_callsign, "UTF-8")
|
||||
+ "] ", mode=self.wakeup_mode, bursts=bursts,
|
||||
)
|
||||
|
@ -51,13 +51,13 @@ class broadcastHandler:
|
|||
print(self.fec_wakeup_callsign)
|
||||
|
||||
self.send_data_to_socket_queue(
|
||||
freedata="tnc-message",
|
||||
freedata="modem-message",
|
||||
fec="broadcast",
|
||||
dxcallsign=str(self.fec_wakeup_callsign, "UTF-8"),
|
||||
data=base64.b64encode(data_in[1:]).decode("UTF-8")
|
||||
)
|
||||
|
||||
self.log.info("[TNC] FEC DATA RCVD")
|
||||
self.log.info("[Modem] FEC DATA RCVD")
|
||||
|
||||
def send_data_to_socket_queue(self, **jsondata):
|
||||
"""
|
||||
|
@ -67,7 +67,7 @@ class broadcastHandler:
|
|||
Dictionary containing the data to be sent, in the format:
|
||||
key=value, for each item. E.g.:
|
||||
self.send_data_to_socket_queue(
|
||||
freedata="tnc-message",
|
||||
freedata="modem-message",
|
||||
arq="received",
|
||||
status="success",
|
||||
uuid=self.transmission_uuid,
|
||||
|
@ -87,12 +87,12 @@ class broadcastHandler:
|
|||
if "dxcallsign" not in jsondata:
|
||||
jsondata["dxcallsign"] = str(Station.dxcallsign, "UTF-8")
|
||||
except Exception as e:
|
||||
self.log.debug("[TNC] error adding callsigns to network message", e=e)
|
||||
self.log.debug("[Modem] error adding callsigns to network message", e=e)
|
||||
|
||||
# run json dumps
|
||||
json_data_out = json.dumps(jsondata)
|
||||
|
||||
self.log.debug("[TNC] send_data_to_socket_queue:", jsondata=json_data_out)
|
||||
self.log.debug("[Modem] send_data_to_socket_queue:", jsondata=json_data_out)
|
||||
# finally push data to our network queue
|
||||
sock.SOCKET_QUEUE.put(json_data_out)
|
||||
|
||||
|
@ -106,11 +106,11 @@ class broadcastHandler:
|
|||
self.broadcast_timeout_reached = True
|
||||
|
||||
self.log.info(
|
||||
"[TNC] closing broadcast slot ["
|
||||
"[Modem] closing broadcast slot ["
|
||||
+ str(self.fec_wakeup_callsign, "UTF-8")
|
||||
+ "] ", mode=self.wakeup_mode, bursts=self.broadcast_payload_bursts,
|
||||
)
|
||||
# TODO: We need a dynamic way of modifying this
|
||||
# TODO We need a dynamic way of modifying this
|
||||
modem.RECEIVE_DATAC4 = False
|
||||
self.fec_wakeup_callsign = bytes()
|
||||
self.wakeup_received = False
|
1
modem/callsign_reverse_db.txt
Normal file
1
modem/callsign_reverse_db.txt
Normal file
|
@ -0,0 +1 @@
|
|||
{"DJ2LS-0": "22864b", "IW2DHW-0": "fcef94", "BEACON": "5492c7", "EI7IG-0": "0975c8", "G0HWW-0": "2cb363", "LA3QMA-0": "2b9fac", "EA7KOH-0": "9e1c3e", "OK6MS-0": "5f75ed", "N1QM-0": "e045a0"}
|
|
@ -84,7 +84,7 @@ def freedv_get_mode_name_by_value(mode: int) -> str:
|
|||
#else:
|
||||
sys.path.append(os.path.abspath("."))
|
||||
|
||||
log.info("[C2 ] Searching for libcodec2...")
|
||||
#log.info("[C2 ] Searching for libcodec2...")
|
||||
if sys.platform == "linux":
|
||||
files = glob.glob(r"**/*libcodec2*", recursive=True)
|
||||
files.append("libcodec2.so")
|
||||
|
@ -102,14 +102,14 @@ api = None
|
|||
for file in files:
|
||||
try:
|
||||
api = ctypes.CDLL(file)
|
||||
log.info("[C2 ] Libcodec2 loaded", path=file)
|
||||
#log.info("[C2 ] Libcodec2 loaded", path=file)
|
||||
break
|
||||
except OSError as err:
|
||||
log.warning("[C2 ] Libcodec2 found but not loaded", path=file, e=err)
|
||||
log.warning("[C2 ] Error: Libcodec2 found but not loaded", path=file, e=err)
|
||||
|
||||
# Quit module if codec2 cant be loaded
|
||||
if api is None or "api" not in locals():
|
||||
log.critical("[C2 ] Libcodec2 not loaded - Exiting")
|
||||
log.critical("[C2 ] Error: Libcodec2 not loaded - Exiting")
|
||||
sys.exit(1)
|
||||
|
||||
# ctypes function init
|
|
@ -45,7 +45,7 @@ class CONFIG:
|
|||
write entire config
|
||||
"""
|
||||
self.config['NETWORK'] = {'#Network settings': None,
|
||||
'TNCPORT': data[50]
|
||||
'ModemPORT': data[50]
|
||||
}
|
||||
|
||||
self.config['STATION'] = {'#Station settings': None,
|
||||
|
@ -66,7 +66,7 @@ class CONFIG:
|
|||
'rigctld_ip': data[6],
|
||||
'rigctld_port': data[7]
|
||||
}
|
||||
self.config['TNC'] = {'#TNC settings': None,
|
||||
self.config['Modem'] = {'#Modem settings': None,
|
||||
'scatter': data[8],
|
||||
'fft': data[9],
|
||||
'narrowband': data[10],
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue