FreeDATA/gui/src/components/dynamic_components.vue

1028 lines
27 KiB
Vue
Raw Permalink Normal View History

<script setup lang="ts">
2023-12-09 10:21:47 +00:00
import { ref, onMounted, nextTick, shallowRef, render, h } from "vue";
import { Modal } from "bootstrap";
import { setActivePinia } from "pinia";
import pinia from "../store/index";
setActivePinia(pinia);
import "../../node_modules/gridstack/dist/gridstack.min.css";
import { GridStack } from "gridstack";
2023-12-16 16:11:16 +00:00
import { useStateStore } from "../store/stateStore.js";
const state = useStateStore(pinia);
import { setRadioParametersFrequency, setRadioParametersMode, setRadioParametersRFLevel } from "../js/api";
2023-12-19 01:09:43 +00:00
import { saveLocalSettingsToConfig, settingsStore } from "../store/settingsStore";
2023-12-09 06:18:16 +00:00
import active_heard_stations from "./grid/grid_active_heard_stations.vue";
import mini_heard_stations from "./grid/grid_active_heard_stations_mini.vue";
import active_stats from "./grid/grid_active_stats.vue";
import active_audio_level from "./grid/grid_active_audio.vue";
import active_rig_control from "./grid/grid_active_rig_control.vue";
2023-12-12 21:20:02 +00:00
import active_broadcasts from "./grid/grid_active_broadcasts.vue";
import active_broadcasts_vert from "./grid/grid_active_broadcasts_vert.vue";
2023-12-09 06:18:16 +00:00
import s_meter from "./grid/grid_s-meter.vue";
import dbfs_meter from "./grid/grid_dbfs.vue";
import grid_activities from "./grid/grid_activities.vue";
2023-12-09 10:21:47 +00:00
import grid_button from "./grid/button.vue";
2023-12-12 22:20:48 +00:00
import grid_ptt from "./grid/grid_ptt.vue";
2023-12-16 16:11:16 +00:00
import grid_mycall from "./grid/grid_mycall.vue";
2024-01-06 08:31:06 +00:00
import grid_stop from "./grid/grid_stop.vue";
2024-01-12 21:52:56 +00:00
import grid_tune from "./grid/grid_tune.vue";
2023-12-16 16:11:16 +00:00
import grid_CQ_btn from "./grid/grid_CQ.vue";
import grid_ping from "./grid/grid_ping.vue";
import grid_freq from "./grid/grid_frequency.vue";
2024-01-11 02:20:20 +00:00
import grid_beacon from "./grid/grid_beacon.vue";
import grid_mycall_small from "./grid/grid_mycall small.vue";
2024-01-14 09:15:24 +00:00
import grid_scatter from "./grid/grid_scatter.vue";
import { stateDispatcher } from "../js/eventHandler";
2024-01-14 09:15:24 +00:00
import { Scatter } from "vue-chartjs";
2023-11-26 06:02:16 +00:00
let count = ref(0);
let grid = null; // DO NOT use ref(null) as proxies GS will break all logic when comparing structures... see https://github.com/gridstack/gridstack.js/issues/2115
let items = ref([]);
2023-11-26 06:07:52 +00:00
class gridWidget {
2023-12-09 06:08:15 +00:00
//Contains the vue component
2023-11-26 06:02:16 +00:00
component2;
2023-12-09 06:08:15 +00:00
//Initial size and location if autoplace is false
2023-11-26 06:02:16 +00:00
size;
2023-12-17 15:58:19 +00:00
//Text for button label in widget picker
2023-11-26 06:02:16 +00:00
text;
2023-12-09 06:08:15 +00:00
//if true add when quick fill button is clicked
quickFill;
//Auto place; true to add where ever it fits; false uses position information
autoPlace;
2023-12-17 15:58:19 +00:00
//Category to place widget in widget picker
2023-12-09 10:21:47 +00:00
category;
2024-01-11 03:18:34 +00:00
//Unique ID for widget
id;
constructor(component, size, text, quickfill, autoPlace, category, id) {
2023-11-26 06:02:16 +00:00
this.component2 = component;
this.size = size;
this.text = text;
this.quickFill = quickfill;
2023-12-09 06:08:15 +00:00
this.autoPlace = autoPlace;
2023-12-09 10:21:47 +00:00
this.category = category;
2024-01-11 03:18:34 +00:00
this.id = id;
2023-11-26 06:02:16 +00:00
}
}
2023-12-17 15:58:19 +00:00
//Array of grid widgets, do not change array order as it'll affect saved configs
2023-11-26 06:02:16 +00:00
const gridWidgets = [
2024-01-11 03:18:34 +00:00
new gridWidget(
grid_activities,
{ x: 0, y: 53, w: 6, h: 55 },
"Activities list",
true,
true,
"Activity",
8,
),
2023-11-26 06:07:52 +00:00
new gridWidget(
active_heard_stations,
2024-01-11 02:20:20 +00:00
{ x: 0, y: 13, w: 16, h: 40 },
2024-01-11 03:18:34 +00:00
"Heard stations list (detailed)",
true,
true,
"Activity",
2024-01-11 03:18:34 +00:00
0,
2023-11-26 06:07:52 +00:00
),
new gridWidget(
active_stats,
2024-01-11 02:20:20 +00:00
{ x: 16, y: 16, w: 8, h: 72 },
"Stats (waterfall, etc)",
true,
true,
"Stats",
2024-01-11 03:18:34 +00:00
1,
),
new gridWidget(
active_audio_level,
2023-12-12 21:20:02 +00:00
{ x: 16, y: 0, w: 8, h: 15 },
2023-12-09 10:21:47 +00:00
"Audio main",
2023-12-16 16:11:16 +00:00
false,
true,
2023-12-09 10:21:47 +00:00
"Audio",
2024-01-11 03:18:34 +00:00
2,
),
new gridWidget(
grid_freq,
{ x: 20, y: 8, w: 4, h: 8 },
"Frequency widget",
true,
true,
"Rig",
14,
2023-11-26 06:07:52 +00:00
),
new gridWidget(
active_rig_control,
2023-12-12 21:20:02 +00:00
{ x: 6, y: 40, w: 9, h: 15 },
2023-12-09 10:21:47 +00:00
"Rig control main",
2023-12-16 16:11:16 +00:00
false,
true,
"Rig",
2024-01-11 03:18:34 +00:00
3,
),
new gridWidget(
grid_beacon,
{ x: 3, y: 27, w: 3, h: 8 },
"Beacon button",
false,
true,
"Broadcasts",
16,
),
new gridWidget(
2023-12-12 21:20:02 +00:00
active_broadcasts,
{ x: 6, y: 70, w: 6, h: 15 },
"Broadcasts main (horizontal)",
2023-12-16 16:11:16 +00:00
false,
true,
"Broadcasts",
2024-01-11 03:18:34 +00:00
4,
2023-11-26 06:07:52 +00:00
),
new gridWidget(
mini_heard_stations,
2023-12-09 06:08:15 +00:00
{ x: 1, y: 1, w: 6, h: 54 },
2024-01-11 03:18:34 +00:00
"Heard stations list (small)",
false,
true,
"Activity",
2024-01-11 03:18:34 +00:00
5,
),
new gridWidget(
s_meter,
2023-12-16 16:11:16 +00:00
{ x: 16, y: 0, w: 4, h: 8 },
"S-Meter",
2023-12-16 16:11:16 +00:00
true,
true,
"Rig",
2024-01-11 03:18:34 +00:00
6,
),
new gridWidget(
dbfs_meter,
2023-12-16 16:11:16 +00:00
{ x: 20, y: 0, w: 4, h: 8 },
2024-01-11 02:20:20 +00:00
"Dbfs meter",
2023-12-16 16:11:16 +00:00
true,
true,
"Audio",
2024-01-11 03:18:34 +00:00
7,
),
2024-01-11 03:18:34 +00:00
2023-12-12 21:20:02 +00:00
new gridWidget(
active_broadcasts_vert,
2024-01-11 02:20:20 +00:00
{ x: 6, y: 53, w: 10, h: 35 },
2023-12-12 21:20:02 +00:00
"Broadcasts main (vertical)",
2023-12-16 16:11:16 +00:00
true,
2023-12-12 21:20:02 +00:00
true,
"Broadcasts",
2024-01-11 03:18:34 +00:00
9,
2023-12-12 21:20:02 +00:00
),
2023-12-12 22:20:48 +00:00
new gridWidget(
grid_ptt,
2024-01-11 02:20:20 +00:00
{ x: 2, y: 0, w: 5, h: 13 },
2023-12-16 16:11:16 +00:00
"Tx/PTT indicator",
true,
true,
"Rig",
2024-01-11 03:18:34 +00:00
10,
2023-12-16 16:11:16 +00:00
),
new gridWidget(
grid_mycall,
2024-01-11 02:20:20 +00:00
{ x: 7, y: 0, w: 9, h: 13 },
2023-12-16 16:11:16 +00:00
"My callsign widget",
true,
true,
"Other",
2024-01-11 03:18:34 +00:00
11,
),
new gridWidget(
grid_mycall_small,
{ x: 8, y: 40, w: 4, h: 8 },
"My callsign widget (small)",
false,
true,
"Other",
17,
2023-12-16 16:11:16 +00:00
),
new gridWidget(
grid_CQ_btn,
2023-12-12 22:20:48 +00:00
{ x: 3, y: 27, w: 2, h: 8 },
2024-01-11 02:20:20 +00:00
"CQ button",
2023-12-12 22:20:48 +00:00
false,
true,
2023-12-16 16:11:16 +00:00
"Broadcasts",
2024-01-11 03:18:34 +00:00
12,
2023-12-16 16:11:16 +00:00
),
new gridWidget(
grid_ping,
{ x: 3, y: 27, w: 4, h: 9 },
2024-01-11 02:20:20 +00:00
"Ping widget",
2023-12-16 16:11:16 +00:00
false,
true,
"Broadcasts",
2024-01-11 03:18:34 +00:00
13,
2023-12-16 16:11:16 +00:00
),
2024-01-11 03:18:34 +00:00
2024-01-06 08:31:06 +00:00
new gridWidget(
grid_stop,
2024-01-11 02:20:20 +00:00
{ x: 0, y: 0, w: 2, h: 13 },
"Stop widget",
2024-01-06 08:31:06 +00:00
true,
true,
"Other",
2024-01-11 03:18:34 +00:00
15,
2024-01-06 08:31:06 +00:00
),
2024-01-12 21:52:56 +00:00
new gridWidget(
grid_tune,
2024-01-13 16:31:11 +00:00
{ x: 16, y: 8, w: 2, h: 8 },
2024-01-12 21:52:56 +00:00
"Tune widget",
true,
true,
"Audio",
18,
),
2024-01-14 09:15:24 +00:00
new gridWidget(
grid_scatter,
{ x: 0, y: 114, w: 6, h: 30 },
"Scatter graph",
false,
true,
"Stats",
19,
),
2024-01-11 03:18:34 +00:00
2024-01-14 09:15:24 +00:00
//New new widget ID should be 20
2023-11-26 06:02:16 +00:00
];
2023-12-16 16:11:16 +00:00
2023-12-16 16:11:16 +00:00
function updateFrequencyAndApply(frequency) {
state.new_frequency = frequency;
set_radio_parameter_frequency();
}
function set_radio_parameter_frequency(){
setRadioParametersFrequency(state.new_frequency)
2023-12-16 16:11:16 +00:00
}
function set_radio_parameter_mode(){
setRadioParametersMode(state.mode)
}
function set_radio_parameter_rflevel(){
setRadioParametersRFLevel(state.rf_level)
2023-12-16 16:11:16 +00:00
}
2024-01-11 03:18:34 +00:00
function savePreset()
{
settingsStore.local.grid_preset=settingsStore.local.grid_layout;
console.log("Saved grid preset")
}
function loadPreset()
{
2024-01-11 03:18:34 +00:00
clearAllItems();
settingsStore.local.grid_layout=settingsStore.local.grid_preset;
restoreGridLayoutFromConfig();
console.log("Restored grid preset")
}
onMounted(() => {
grid = GridStack.init({
2023-12-09 06:08:15 +00:00
// DO NOT use grid.value = GridStack.init(), see above
float: true,
2023-12-09 06:08:15 +00:00
cellHeight: "5px",
2023-11-26 06:02:16 +00:00
minRow: 50,
margin: 5,
2023-12-09 06:08:15 +00:00
column: 24,
2023-12-05 03:24:58 +00:00
draggable: {
scroll: true,
},
2023-12-03 18:02:40 +00:00
resizable: {
2023-12-04 03:49:02 +00:00
handles: "se,sw",
},
});
grid.on("dragstop", function (event, element) {
const node = element.gridstackNode;
console.info(
`Moved #${node.id} to ${node.x}.${node.y}. Dimensions: ${node.w}x${node.h}`,
);
});
grid.on("change", onChange);
gridWidgets.forEach((gw) => {
2023-12-09 10:21:47 +00:00
//Dynamically add widgets to widget menu
let dom = document.getElementById("otherBod");
switch (gw.category) {
case "Activity":
dom = document.getElementById("actBody");
break;
case "Stats":
2023-12-09 10:21:47 +00:00
dom = document.getElementById("statsBody");
break;
case "Audio":
2023-12-09 10:21:47 +00:00
dom = document.getElementById("audioBody");
break;
case "Rig":
2023-12-09 10:21:47 +00:00
dom = document.getElementById("rigBody");
break;
case "Broadcasts":
2023-12-09 10:21:47 +00:00
dom = document.getElementById("bcBody");
2023-12-16 16:11:16 +00:00
break;
case "Other":
2023-12-09 10:21:47 +00:00
break;
default:
2023-12-09 16:34:46 +00:00
console.error("Unknown widget category: " + gw.category);
2023-12-09 10:21:47 +00:00
break;
}
var index = gridWidgets.findIndex((w) => gw.text == w.text);
dom.insertAdjacentHTML("beforeend", `<div id="gridbtn-${index}""></div>`);
2023-12-09 10:21:47 +00:00
let dom2 = document.getElementById(`gridbtn-${index}`);
let vueComponent = h(grid_button,{btnText: gw.text,btnID:index});
render(vueComponent,dom2);
2023-12-17 15:58:19 +00:00
restoreGridLayoutFromConfig();
if ((items.value.length == 0))
{
2023-12-17 15:58:19 +00:00
//Pre-populate grid if there are no items
console.info("Grid config is empty; using default");
quickfill();
}
2023-12-09 10:21:47 +00:00
})
2023-12-09 16:34:46 +00:00
2023-12-09 10:21:47 +00:00
window.addEventListener(
"add-widget",
function (eventdata) {
2023-12-09 16:34:46 +00:00
let evt = <CustomEvent>eventdata;
2023-12-17 15:58:19 +00:00
addNewWidget2(gridWidgets[evt.detail],true);
2023-12-09 10:21:47 +00:00
},
false,
);
setGridEditState();
2023-12-09 10:21:47 +00:00
});
function onChange(event, changeItems) {
2024-02-21 10:01:24 +00:00
if (typeof changeItems !== "undefined"){
// update item position
changeItems.forEach((item) => {
var widget = items.value.find((w) => w.id == item.id);
if (!widget) {
console.error("Widget not found: " + item.id);
return;
}
widget.x = item.x;
widget.y = item.y;
widget.w = item.w;
widget.h = item.h;
});
saveGridLayout();
}
2023-12-17 15:58:19 +00:00
}
function restoreGridLayoutFromConfig(){
//Try to load grid from saved config
//On mounted seems to be called multiple times; so check to make sure items is empty first
2024-01-11 03:18:34 +00:00
//array format: 0 = x, 1 = y, 2 = w, 3 = h, 4 = gridwidget ID
2023-12-17 15:58:19 +00:00
if (items.value.length == 0){
let savedGrid = JSON.parse(settingsStore.local.grid_layout);
if (savedGrid.length > 0 ) console.info("Restoring " + savedGrid.length + " widget(s) from config");
for (let i=0; i < savedGrid.length;i++ ){
2024-01-11 03:18:34 +00:00
//Find widget by ID
var widgetIndex = gridWidgets.findIndex((gw) => gw.id == savedGrid[i][4])
2023-12-17 15:58:19 +00:00
//Refs are passed, so grab original settings for restoration
2024-01-11 03:18:34 +00:00
//let tempGW = gridWidgets[parseInt(savedGrid[i][4])];
2024-01-11 03:27:07 +00:00
let tempGW = gridWidgets[widgetIndex];
2023-12-17 15:58:19 +00:00
let backupGWsize = tempGW.size;
tempGW.autoPlace=false;
tempGW.size={x:savedGrid[i][0], y:savedGrid[i][1], w:savedGrid[i][2], h:savedGrid[i][3]}
addNewWidget2(tempGW, false);
tempGW.autoPlace=true;
tempGW.size = backupGWsize;
}
}
}
function saveGridLayout()
{
let cfg = [];
for (let i=0; items.value.length > i; i++) {
var widget = gridWidgets.findIndex((gw) => gw.component2.__name == items.value[i].component2.__name)
2024-01-11 03:18:34 +00:00
//Get the widget's id to store in config
var widgetid = gridWidgets[widget].id;
2024-01-13 18:58:54 +00:00
//Debug code to return index of widget based on id
//console.log(widgetid + "-" + widget);
2024-01-11 03:18:34 +00:00
cfg[i] = [items.value[i].x, items.value[i].y, items.value[i].w,items.value[i].h, widgetid ];
2023-12-17 15:58:19 +00:00
}
settingsStore.local.grid_layout=JSON.stringify(cfg);
2023-12-19 01:09:43 +00:00
saveLocalSettingsToConfig();
}
2023-12-09 06:08:15 +00:00
2023-12-17 15:58:19 +00:00
function addNewWidget2(componentToAdd :gridWidget,saveToConfig :boolean) {
2023-11-26 06:07:52 +00:00
const node = items[count.value] || { ...componentToAdd.size };
node.id = "w_" + count.value++;
2023-11-26 06:07:52 +00:00
node.component2 = shallowRef({ ...componentToAdd.component2 });
node.autoPlace = componentToAdd.autoPlace;
items.value.push(node);
nextTick(() => {
grid.makeWidget(node.id);
2023-12-17 15:58:19 +00:00
if (saveToConfig)
saveGridLayout();
});
}
function remove(widget) {
var index = items.value.findIndex((w) => w.id == widget.id);
items.value.splice(index, 1);
const selector = `#${widget.id}`;
grid.removeWidget(selector, false);
2023-12-17 15:58:19 +00:00
saveGridLayout();
}
function toggleGridEdit() {
//Toggle setting
settingsStore.local.grid_enabled = !settingsStore.local.grid_enabled
setGridEditState();
}
function setGridEditState()
{
//Apply grid state setting (allows/disallows moving, resizing, showing remove icon)
if (settingsStore.local.grid_enabled)
2024-01-15 00:24:03 +00:00
grid.enable();
else
grid.disable();
}
2023-12-09 06:08:15 +00:00
function clearAllItems() {
grid.removeAll(false);
count.value = 0;
items.value = [];
2023-12-17 15:58:19 +00:00
saveGridLayout();
}
function quickfill() {
2023-11-26 06:02:16 +00:00
gridWidgets.forEach(async (gw) => {
if (gw.quickFill === true) {
gw.autoPlace = false;
2023-12-17 15:58:19 +00:00
addNewWidget2(gw, false);
2023-12-09 06:08:15 +00:00
//Reset autoplace value
gw.autoPlace = true;
}
2023-11-26 06:07:52 +00:00
});
2023-12-17 15:58:19 +00:00
saveGridLayout();
}
</script>
<template>
<button
class="btn btn-secondary fixed-middle-right rounded-0 rounded-start-4 p-1 pt-4 pb-4"
type="button"
data-bs-toggle="offcanvas"
data-bs-target="#offcanvasGridItems"
aria-controls="offcanvasGridItems"
>
2023-12-09 06:08:15 +00:00
<i class="bi bi-grip-vertical h5"></i>
</button>
2023-12-08 21:20:11 +00:00
2024-02-22 12:02:23 +00:00
<div class="grid-container z-0" style="height: calc(100vh - 51px);">
<div class="grid-stack z-0">
2023-12-05 03:27:35 +00:00
<div
v-for="(w, indexs) in items"
class="grid-stack-item"
:gs-x="w.x"
:gs-y="w.y"
:gs-w="w.w"
:gs-h="w.h"
:gs-id="w.id"
:id="w.id"
:key="w.id"
:gs-auto-position="w.autoPlace"
2023-12-05 03:27:35 +00:00
>
<div class="grid-stack-item-content">
<button
@click="remove(w)"
class="btn-close grid-stack-floaty-btn"
:class="settingsStore.local.grid_enabled === true ? 'visible':'invisible'"
2023-12-05 03:27:35 +00:00
></button>
<component :is="w.component2" />
</div>
</div>
</div>
</div>
2023-12-08 21:20:11 +00:00
<div
class="offcanvas offcanvas-end"
data-bs-scroll="true"
data-bs-backdrop="true"
tabindex="-1"
id="offcanvasGridItems"
aria-labelledby="offcanvasGridItemsLabel"
>
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="offcanvasGridItemsLabel">
2024-01-15 00:24:03 +00:00
Manage grid widgets &nbsp;<button
class="btn btn-sm"
:class="settingsStore.local.grid_enabled == true ? 'btn-outline-success' : 'btn-outline-danger'"
2024-01-15 00:24:03 +00:00
type="button"
@click="toggleGridEdit"
2024-01-15 00:24:03 +00:00
title="Lock/unloack changes to grid"
>
<i class="bi" :class="settingsStore.local.grid_enabled == true ? 'bi-unlock-fill' : 'bi-lock-fill'"></i>
2024-01-15 00:24:03 +00:00
</button>&nbsp;
</h5>
<button
type="button"
class="btn-close"
data-bs-dismiss="offcanvas"
aria-label="Close"
></button>
2023-12-08 21:20:11 +00:00
</div>
<div class="offcanvas-body">
<p>
Grid widgets allow you to customize the display for your own usage. Here
you may add additional widgets to fit your needs. You can move and
resize the individual widgets!
2023-12-09 10:21:47 +00:00
</p>
2023-12-09 06:08:15 +00:00
<div>
<button
class="btn btn-sm btn-outline-primary"
type="button"
@click="quickfill"
>
2023-12-09 06:08:15 +00:00
Fill grid with common widgets
</button>
2023-12-09 06:08:15 +00:00
</div>
<hr />
2023-12-09 10:21:47 +00:00
<!-- Begin widget selector -->
<div class="accordion" id="accordionExample">
<!-- Heard Stations -->
<div class="accordion-item">
<h2 class="accordion-header" id="headingHeardStations">
<button
class="accordion-button"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapseHeardStations"
aria-expanded="true"
aria-controls="collapseHeardStations"
>
2023-12-09 10:21:47 +00:00
<strong>Activity</strong>
</button>
</h2>
<div
id="collapseHeardStations"
class="accordion-collapse collapse show"
aria-labelledby="headingHeardStations"
data-bs-parent="#accordionExample"
>
<div class="accordion-body" id="actBody"></div>
</div>
</div>
2023-12-08 21:20:11 +00:00
<!-- Activities -->
<div class="accordion-item">
<h2 class="accordion-header" id="headingActivities">
<button
class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapseActivities"
aria-expanded="false"
aria-controls="collapseActivities"
>
2023-12-09 10:21:47 +00:00
<strong>Audio</strong>
</button>
</h2>
<div
id="collapseActivities"
class="accordion-collapse collapse"
aria-labelledby="headingActivities"
data-bs-parent="#accordionExample"
>
<div class="accordion-body" id="audioBody"></div>
</div>
</div>
2023-12-09 10:21:47 +00:00
<!-- Broadcasts -->
<div class="accordion-item">
<h2 class="accordion-header" id="headingBroadcasts">
<button
class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapseBroadcasts"
aria-expanded="false"
aria-controls="collapseBroadcasts"
>
<strong>Broadcasts</strong>
</button>
</h2>
<div
id="collapseBroadcasts"
class="accordion-collapse collapse"
aria-labelledby="headingBroadcasts"
data-bs-parent="#accordionExample"
>
<div class="accordion-body" id="bcBody"></div>
2023-12-09 10:21:47 +00:00
</div>
</div>
<!-- Radio Control -->
<div class="accordion-item">
<h2 class="accordion-header" id="headingRadioControl">
<button
class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapseRadioControl"
aria-expanded="false"
aria-controls="collapseRadioControl"
>
2023-12-16 16:11:16 +00:00
<strong>Radio Control/Status</strong>
</button>
</h2>
<div
id="collapseRadioControl"
class="accordion-collapse collapse"
aria-labelledby="headingRadioControl"
data-bs-parent="#accordionExample"
>
<div class="accordion-body" id="rigBody"></div>
</div>
</div>
2023-12-08 21:20:11 +00:00
<!-- Audio Control -->
<div class="accordion-item">
<h2 class="accordion-header" id="headingAudioControl">
<button
class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapseAudioControl"
aria-expanded="false"
aria-controls="collapseAudioControl"
>
2023-12-09 10:21:47 +00:00
<strong>Statistics</strong>
</button>
</h2>
<div
id="collapseAudioControl"
class="accordion-collapse collapse"
aria-labelledby="headingAudioControl"
data-bs-parent="#accordionExample"
>
<div class="accordion-body" id="statsBody"></div>
</div>
</div>
2023-12-08 21:20:11 +00:00
<!-- Statistics -->
<div class="accordion-item">
<h2 class="accordion-header" id="headingStatistics">
<button
class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapseStatistics"
aria-expanded="false"
aria-controls="collapseStatistics"
>
2023-12-09 10:21:47 +00:00
<strong>Other</strong>
</button>
</h2>
<div
id="collapseStatistics"
class="accordion-collapse collapse"
aria-labelledby="headingStatistics"
data-bs-parent="#accordionExample"
>
<div class="accordion-body" id="otherBod"></div>
</div>
</div>
2023-12-08 21:20:11 +00:00
</div>
<hr />
2023-12-09 10:21:47 +00:00
<button
class="btn btn-sm btn-outline-warning"
type="button"
@click="clearAllItems"
2024-01-15 00:24:03 +00:00
title="Clear all items from the grid"
>
Clear grid
</button>
2024-01-15 00:24:03 +00:00
<hr/>
2024-01-11 03:18:34 +00:00
<button
class="btn btn-sm btn-outline-dark"
type="button"
2024-01-15 00:24:03 +00:00
@click="loadPreset"
title="Restore your saved grid preset (clears current grid)"
2024-01-11 03:18:34 +00:00
>
2024-01-15 00:24:03 +00:00
Restore preset
2024-01-11 03:18:34 +00:00
</button>&nbsp;
<button
class="btn btn-sm btn-outline-dark"
type="button"
2024-01-15 00:24:03 +00:00
@click="savePreset"
title="Save current grid layout as a preset that can be restored using restore preset button"
2024-01-11 03:18:34 +00:00
>
2024-01-15 00:24:03 +00:00
Save preset
2024-01-11 03:18:34 +00:00
</button>
2023-12-08 21:20:11 +00:00
</div>
</div>
2023-12-16 16:11:16 +00:00
<div class="offcanvas offcanvas-end text-start" data-bs-scroll="true"
data-bs-backdrop="false" tabindex="-1" id="offcanvasFrequency" aria-labelledby="offcanvasExampleLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="offcanvasExampleLabel">Frequency selection</h5>
<button
type="button"
class="btn-close"
data-bs-dismiss="offcanvas"
aria-label="Close"
></button>
2023-12-16 16:11:16 +00:00
</div>
<div class="offcanvas-body">
<p>
Commonly used frequencies are listed here, and are all USB.&nbsp; Simply click on a entry or manually enter a frequency in the textbox to tune your rig if rig control is enabled.
</p>
<ul
class="list-group"
aria-labelledby="dropdownMenuButton"
style="z-index: 50"
>
<li class="list-group-item">
<div class="input-group p-1">
<span class="input-group-text">frequency</span>
<input
v-model="state.new_frequency"
style="max-width: 8rem"
pattern="[0-9]*"
type="text"
class="form-control form-control-sm"
v-bind:class="{
disabled: state.hamlib_status === 'disconnected',
}"
placeholder="Type frequency..."
aria-label="Frequency"
/>
<button
class="btn btn-sm btn-outline-success"
type="button"
2024-01-12 21:12:43 +00:00
@click="updateFrequencyAndApply(state.new_frequency)"
2023-12-16 16:11:16 +00:00
v-bind:class="{
disabled: state.hamlib_status === 'disconnected',
}"
>
<i class="bi bi-check-square"></i>
</button>
</div>
</li>
<!-- Dropdown Divider -->
<li class="list-group-item"><hr class="dropdown-divider" /></li>
<!-- Dropdown Items -->
<a href="#" class="list-group-item list-group-item-action" @click="updateFrequencyAndApply(50616000)">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">50.616 MHz</h5>
<small>EU / US</small>
<h6>6m</h6>
</div>
</a>
<a href="#" class="list-group-item list-group-item-action" @click="updateFrequencyAndApply(50308000)">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">50.308 MHz</h5>
<small>US</small>
<h6>6m</h6>
</div>
</a>
<a href="#" class="list-group-item list-group-item-action" @click="updateFrequencyAndApply(28093000)">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">28.093 MHz</h5>
<small>EU / US</small>
<h6>10m</h6>
</div>
</a>
<a href="#" class="list-group-item list-group-item-action" @click="updateFrequencyAndApply(27265000)">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">27.265 MHz</h5>
<small>Ch 26</small>
<h6>11m</h6>
</div>
</a>
<a href="#" class="list-group-item list-group-item-action" @click="updateFrequencyAndApply(27245000)">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">27.245 MHz</h5>
<small>Ch 25</small>
<h6>11m</h6>
</div>
</a>
<a href="#" class="list-group-item list-group-item-action" @click="updateFrequencyAndApply(24908000)">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">24.908 MHz</h5>
<small>EU / US</small>
<h6>12m</h6>
</div>
</a>
<a href="#" class="list-group-item list-group-item-action" @click="updateFrequencyAndApply(21093000)">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">21.093 MHz</h5>
<small>EU / US</small>
<h6>15m</h6>
</div>
2024-01-07 05:27:43 +00:00
</a>
2024-03-13 00:40:35 +00:00
<a href="#" class="list-group-item list-group-item-action" @click="updateFrequencyAndApply(18106000)">
2024-01-07 05:27:43 +00:00
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">18.106 MHz</h5>
<small>EU / US</small>
<h6>17m</h6>
</div>
</a>
2023-12-16 16:11:16 +00:00
<a href="#" class="list-group-item list-group-item-action" @click="updateFrequencyAndApply(14093000)">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">14.093 MHz</h5>
<small>EU / US</small>
<h6>20m</h6>
</div>
</a>
<a href="#" class="list-group-item list-group-item-action" @click="updateFrequencyAndApply(7053000)">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">7.053 MHz</h5>
<small>EU / US</small>
<h6>40m</h6>
</div>
</a>
</ul>
</div>
</div>
</template>
2023-12-08 21:20:11 +00:00
<style>
2023-12-08 21:20:11 +00:00
.fixed-middle-right {
position: fixed; /* Fixed/sticky position */
top: 50%; /* Position at the middle of the viewport */
right: 0px; /* Place the button 20px from the right */
transform: translateY(-50%); /* Adjust for exact vertical centering */
z-index: 999; /* Ensure it's on top of other elements */
2023-12-08 21:20:11 +00:00
}
.grid-stack-item {
text-align: center;
overflow: auto;
z-index: 50;
}
.grid-stack-floaty-btn {
position: absolute;
2023-12-11 20:51:16 +00:00
right: 2px;
z-index: 1000;
float: right;
2023-12-11 20:51:16 +00:00
top: 4px;
}
2023-12-05 03:24:58 +00:00
.grid-container {
2023-12-05 03:27:35 +00:00
overflow-y: auto;
}
2023-12-09 06:08:15 +00:00
.gs-24 > .grid-stack-item {
width: 4.167%;
}
.gs-24 > .grid-stack-item[gs-x="1"] {
left: 4.167%;
}
.gs-24 > .grid-stack-item[gs-w="2"] {
width: 8.333%;
}
.gs-24 > .grid-stack-item[gs-x="2"] {
left: 8.333%;
}
.gs-24 > .grid-stack-item[gs-w="3"] {
width: 12.5%;
}
.gs-24 > .grid-stack-item[gs-x="3"] {
left: 12.5%;
}
.gs-24 > .grid-stack-item[gs-w="4"] {
width: 16.667%;
}
.gs-24 > .grid-stack-item[gs-x="4"] {
left: 16.667%;
}
.gs-24 > .grid-stack-item[gs-w="5"] {
width: 20.833%;
}
.gs-24 > .grid-stack-item[gs-x="5"] {
left: 20.833%;
}
.gs-24 > .grid-stack-item[gs-w="6"] {
width: 25%;
}
.gs-24 > .grid-stack-item[gs-x="6"] {
left: 25%;
}
.gs-24 > .grid-stack-item[gs-w="7"] {
width: 29.167%;
}
.gs-24 > .grid-stack-item[gs-x="7"] {
left: 29.167%;
}
.gs-24 > .grid-stack-item[gs-w="8"] {
width: 33.333%;
}
.gs-24 > .grid-stack-item[gs-x="8"] {
left: 33.333%;
}
.gs-24 > .grid-stack-item[gs-w="9"] {
width: 37.5%;
}
.gs-24 > .grid-stack-item[gs-x="9"] {
left: 37.5%;
}
.gs-24 > .grid-stack-item[gs-w="10"] {
width: 41.667%;
}
.gs-24 > .grid-stack-item[gs-x="10"] {
left: 41.667%;
}
.gs-24 > .grid-stack-item[gs-w="11"] {
width: 45.833%;
}
.gs-24 > .grid-stack-item[gs-x="11"] {
left: 45.833%;
}
.gs-24 > .grid-stack-item[gs-w="12"] {
width: 50%;
}
.gs-24 > .grid-stack-item[gs-x="12"] {
left: 50%;
}
.gs-24 > .grid-stack-item[gs-w="13"] {
width: 54.167%;
}
.gs-24 > .grid-stack-item[gs-x="13"] {
left: 54.167%;
}
.gs-24 > .grid-stack-item[gs-w="14"] {
width: 58.333%;
}
.gs-24 > .grid-stack-item[gs-x="14"] {
left: 58.333%;
}
.gs-24 > .grid-stack-item[gs-w="15"] {
width: 62.5%;
}
.gs-24 > .grid-stack-item[gs-x="15"] {
left: 62.5%;
}
.gs-24 > .grid-stack-item[gs-w="16"] {
width: 66.667%;
}
.gs-24 > .grid-stack-item[gs-x="16"] {
left: 66.667%;
}
.gs-24 > .grid-stack-item[gs-w="17"] {
width: 70.833%;
}
.gs-24 > .grid-stack-item[gs-x="17"] {
left: 70.833%;
}
.gs-24 > .grid-stack-item[gs-w="18"] {
width: 75%;
}
.gs-24 > .grid-stack-item[gs-x="18"] {
left: 75%;
}
.gs-24 > .grid-stack-item[gs-w="19"] {
width: 79.167%;
}
.gs-24 > .grid-stack-item[gs-x="19"] {
left: 79.167%;
}
.gs-24 > .grid-stack-item[gs-w="20"] {
width: 83.333%;
}
.gs-24 > .grid-stack-item[gs-x="20"] {
left: 83.333%;
}
.gs-24 > .grid-stack-item[gs-w="21"] {
width: 87.5%;
}
.gs-24 > .grid-stack-item[gs-x="21"] {
left: 87.5%;
}
.gs-24 > .grid-stack-item[gs-w="22"] {
width: 91.667%;
}
.gs-24 > .grid-stack-item[gs-x="22"] {
left: 91.667%;
}
.gs-24 > .grid-stack-item[gs-w="23"] {
width: 95.833%;
}
.gs-24 > .grid-stack-item[gs-x="23"] {
left: 95.833%;
}
.gs-24 > .grid-stack-item[gs-w="24"] {
width: 100%;
}
</style>