mirror of
https://github.com/DJ2LS/FreeDATA
synced 2024-05-14 10:04:33 +02:00
1028 lines
27 KiB
Vue
1028 lines
27 KiB
Vue
<script setup lang="ts">
|
|
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";
|
|
import { useStateStore } from "../store/stateStore.js";
|
|
const state = useStateStore(pinia);
|
|
import { setRadioParametersFrequency, setRadioParametersMode, setRadioParametersRFLevel } from "../js/api";
|
|
import { saveLocalSettingsToConfig, settingsStore } from "../store/settingsStore";
|
|
|
|
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";
|
|
import active_broadcasts from "./grid/grid_active_broadcasts.vue";
|
|
import active_broadcasts_vert from "./grid/grid_active_broadcasts_vert.vue";
|
|
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";
|
|
import grid_button from "./grid/button.vue";
|
|
import grid_ptt from "./grid/grid_ptt.vue";
|
|
import grid_mycall from "./grid/grid_mycall.vue";
|
|
import grid_stop from "./grid/grid_stop.vue";
|
|
import grid_tune from "./grid/grid_tune.vue";
|
|
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";
|
|
import grid_beacon from "./grid/grid_beacon.vue";
|
|
import grid_mycall_small from "./grid/grid_mycall small.vue";
|
|
import grid_scatter from "./grid/grid_scatter.vue";
|
|
import { stateDispatcher } from "../js/eventHandler";
|
|
import { Scatter } from "vue-chartjs";
|
|
|
|
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([]);
|
|
class gridWidget {
|
|
//Contains the vue component
|
|
component2;
|
|
//Initial size and location if autoplace is false
|
|
size;
|
|
//Text for button label in widget picker
|
|
text;
|
|
//if true add when quick fill button is clicked
|
|
quickFill;
|
|
//Auto place; true to add where ever it fits; false uses position information
|
|
autoPlace;
|
|
//Category to place widget in widget picker
|
|
category;
|
|
//Unique ID for widget
|
|
id;
|
|
constructor(component, size, text, quickfill, autoPlace, category, id) {
|
|
this.component2 = component;
|
|
this.size = size;
|
|
this.text = text;
|
|
this.quickFill = quickfill;
|
|
this.autoPlace = autoPlace;
|
|
this.category = category;
|
|
this.id = id;
|
|
}
|
|
}
|
|
//Array of grid widgets, do not change array order as it'll affect saved configs
|
|
const gridWidgets = [
|
|
new gridWidget(
|
|
grid_activities,
|
|
{ x: 0, y: 53, w: 6, h: 55 },
|
|
"Activities list",
|
|
true,
|
|
true,
|
|
"Activity",
|
|
8,
|
|
),
|
|
new gridWidget(
|
|
active_heard_stations,
|
|
{ x: 0, y: 13, w: 16, h: 40 },
|
|
"Heard stations list (detailed)",
|
|
true,
|
|
true,
|
|
"Activity",
|
|
0,
|
|
),
|
|
new gridWidget(
|
|
active_stats,
|
|
{ x: 16, y: 16, w: 8, h: 72 },
|
|
"Stats (waterfall, etc)",
|
|
true,
|
|
true,
|
|
"Stats",
|
|
1,
|
|
),
|
|
new gridWidget(
|
|
active_audio_level,
|
|
{ x: 16, y: 0, w: 8, h: 15 },
|
|
"Audio main",
|
|
false,
|
|
true,
|
|
"Audio",
|
|
2,
|
|
),
|
|
new gridWidget(
|
|
grid_freq,
|
|
{ x: 20, y: 8, w: 4, h: 8 },
|
|
"Frequency widget",
|
|
true,
|
|
true,
|
|
"Rig",
|
|
14,
|
|
),
|
|
new gridWidget(
|
|
active_rig_control,
|
|
{ x: 6, y: 40, w: 9, h: 15 },
|
|
"Rig control main",
|
|
false,
|
|
true,
|
|
"Rig",
|
|
3,
|
|
),
|
|
new gridWidget(
|
|
grid_beacon,
|
|
{ x: 3, y: 27, w: 3, h: 8 },
|
|
"Beacon button",
|
|
false,
|
|
true,
|
|
"Broadcasts",
|
|
16,
|
|
),
|
|
new gridWidget(
|
|
active_broadcasts,
|
|
{ x: 6, y: 70, w: 6, h: 15 },
|
|
"Broadcasts main (horizontal)",
|
|
false,
|
|
true,
|
|
"Broadcasts",
|
|
4,
|
|
),
|
|
new gridWidget(
|
|
mini_heard_stations,
|
|
{ x: 1, y: 1, w: 6, h: 54 },
|
|
"Heard stations list (small)",
|
|
false,
|
|
true,
|
|
"Activity",
|
|
5,
|
|
),
|
|
new gridWidget(
|
|
s_meter,
|
|
{ x: 16, y: 0, w: 4, h: 8 },
|
|
"S-Meter",
|
|
true,
|
|
true,
|
|
"Rig",
|
|
6,
|
|
),
|
|
new gridWidget(
|
|
dbfs_meter,
|
|
{ x: 20, y: 0, w: 4, h: 8 },
|
|
"Dbfs meter",
|
|
true,
|
|
true,
|
|
"Audio",
|
|
7,
|
|
),
|
|
|
|
new gridWidget(
|
|
active_broadcasts_vert,
|
|
{ x: 6, y: 53, w: 10, h: 35 },
|
|
"Broadcasts main (vertical)",
|
|
true,
|
|
true,
|
|
"Broadcasts",
|
|
9,
|
|
),
|
|
new gridWidget(
|
|
grid_ptt,
|
|
{ x: 2, y: 0, w: 5, h: 13 },
|
|
"Tx/PTT indicator",
|
|
true,
|
|
true,
|
|
"Rig",
|
|
10,
|
|
),
|
|
new gridWidget(
|
|
grid_mycall,
|
|
{ x: 7, y: 0, w: 9, h: 13 },
|
|
"My callsign widget",
|
|
true,
|
|
true,
|
|
"Other",
|
|
11,
|
|
),
|
|
new gridWidget(
|
|
grid_mycall_small,
|
|
{ x: 8, y: 40, w: 4, h: 8 },
|
|
"My callsign widget (small)",
|
|
false,
|
|
true,
|
|
"Other",
|
|
17,
|
|
),
|
|
new gridWidget(
|
|
grid_CQ_btn,
|
|
{ x: 3, y: 27, w: 2, h: 8 },
|
|
"CQ button",
|
|
false,
|
|
true,
|
|
"Broadcasts",
|
|
12,
|
|
),
|
|
new gridWidget(
|
|
grid_ping,
|
|
{ x: 3, y: 27, w: 4, h: 9 },
|
|
"Ping widget",
|
|
false,
|
|
true,
|
|
"Broadcasts",
|
|
13,
|
|
),
|
|
|
|
new gridWidget(
|
|
grid_stop,
|
|
{ x: 0, y: 0, w: 2, h: 13 },
|
|
"Stop widget",
|
|
true,
|
|
true,
|
|
"Other",
|
|
15,
|
|
),
|
|
new gridWidget(
|
|
grid_tune,
|
|
{ x: 16, y: 8, w: 2, h: 8 },
|
|
"Tune widget",
|
|
true,
|
|
true,
|
|
"Audio",
|
|
18,
|
|
),
|
|
new gridWidget(
|
|
grid_scatter,
|
|
{ x: 0, y: 114, w: 6, h: 30 },
|
|
"Scatter graph",
|
|
false,
|
|
true,
|
|
"Stats",
|
|
19,
|
|
),
|
|
|
|
//New new widget ID should be 20
|
|
];
|
|
|
|
|
|
function updateFrequencyAndApply(frequency) {
|
|
state.new_frequency = frequency;
|
|
set_radio_parameter_frequency();
|
|
}
|
|
|
|
function set_radio_parameter_frequency(){
|
|
setRadioParametersFrequency(state.new_frequency)
|
|
}
|
|
|
|
function set_radio_parameter_mode(){
|
|
setRadioParametersMode(state.mode)
|
|
}
|
|
|
|
function set_radio_parameter_rflevel(){
|
|
setRadioParametersRFLevel(state.rf_level)
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function savePreset()
|
|
{
|
|
settingsStore.local.grid_preset=settingsStore.local.grid_layout;
|
|
console.log("Saved grid preset")
|
|
}
|
|
function loadPreset()
|
|
{
|
|
|
|
clearAllItems();
|
|
settingsStore.local.grid_layout=settingsStore.local.grid_preset;
|
|
restoreGridLayoutFromConfig();
|
|
console.log("Restored grid preset")
|
|
}
|
|
onMounted(() => {
|
|
grid = GridStack.init({
|
|
// DO NOT use grid.value = GridStack.init(), see above
|
|
float: true,
|
|
cellHeight: "5px",
|
|
minRow: 50,
|
|
margin: 5,
|
|
column: 24,
|
|
draggable: {
|
|
scroll: true,
|
|
},
|
|
resizable: {
|
|
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) => {
|
|
//Dynamically add widgets to widget menu
|
|
let dom = document.getElementById("otherBod");
|
|
switch (gw.category) {
|
|
case "Activity":
|
|
dom = document.getElementById("actBody");
|
|
break;
|
|
case "Stats":
|
|
dom = document.getElementById("statsBody");
|
|
break;
|
|
case "Audio":
|
|
dom = document.getElementById("audioBody");
|
|
break;
|
|
case "Rig":
|
|
dom = document.getElementById("rigBody");
|
|
break;
|
|
case "Broadcasts":
|
|
dom = document.getElementById("bcBody");
|
|
break;
|
|
case "Other":
|
|
|
|
break;
|
|
default:
|
|
console.error("Unknown widget category: " + gw.category);
|
|
break;
|
|
}
|
|
var index = gridWidgets.findIndex((w) => gw.text == w.text);
|
|
dom.insertAdjacentHTML("beforeend", `<div id="gridbtn-${index}""></div>`);
|
|
let dom2 = document.getElementById(`gridbtn-${index}`);
|
|
let vueComponent = h(grid_button,{btnText: gw.text,btnID:index});
|
|
render(vueComponent,dom2);
|
|
|
|
restoreGridLayoutFromConfig();
|
|
|
|
if ((items.value.length == 0))
|
|
{
|
|
//Pre-populate grid if there are no items
|
|
console.info("Grid config is empty; using default");
|
|
quickfill();
|
|
}
|
|
})
|
|
|
|
window.addEventListener(
|
|
"add-widget",
|
|
function (eventdata) {
|
|
let evt = <CustomEvent>eventdata;
|
|
addNewWidget2(gridWidgets[evt.detail],true);
|
|
},
|
|
false,
|
|
);
|
|
setGridEditState();
|
|
});
|
|
function onChange(event, changeItems) {
|
|
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();
|
|
}
|
|
}
|
|
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
|
|
//array format: 0 = x, 1 = y, 2 = w, 3 = h, 4 = gridwidget ID
|
|
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++ ){
|
|
//Find widget by ID
|
|
var widgetIndex = gridWidgets.findIndex((gw) => gw.id == savedGrid[i][4])
|
|
|
|
//Refs are passed, so grab original settings for restoration
|
|
//let tempGW = gridWidgets[parseInt(savedGrid[i][4])];
|
|
let tempGW = gridWidgets[widgetIndex];
|
|
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)
|
|
//Get the widget's id to store in config
|
|
var widgetid = gridWidgets[widget].id;
|
|
//Debug code to return index of widget based on id
|
|
//console.log(widgetid + "-" + widget);
|
|
cfg[i] = [items.value[i].x, items.value[i].y, items.value[i].w,items.value[i].h, widgetid ];
|
|
}
|
|
settingsStore.local.grid_layout=JSON.stringify(cfg);
|
|
saveLocalSettingsToConfig();
|
|
}
|
|
|
|
function addNewWidget2(componentToAdd :gridWidget,saveToConfig :boolean) {
|
|
const node = items[count.value] || { ...componentToAdd.size };
|
|
node.id = "w_" + count.value++;
|
|
node.component2 = shallowRef({ ...componentToAdd.component2 });
|
|
node.autoPlace = componentToAdd.autoPlace;
|
|
items.value.push(node);
|
|
nextTick(() => {
|
|
grid.makeWidget(node.id);
|
|
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);
|
|
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)
|
|
grid.enable();
|
|
else
|
|
grid.disable();
|
|
}
|
|
function clearAllItems() {
|
|
grid.removeAll(false);
|
|
count.value = 0;
|
|
items.value = [];
|
|
saveGridLayout();
|
|
}
|
|
function quickfill() {
|
|
gridWidgets.forEach(async (gw) => {
|
|
if (gw.quickFill === true) {
|
|
gw.autoPlace = false;
|
|
addNewWidget2(gw, false);
|
|
//Reset autoplace value
|
|
gw.autoPlace = true;
|
|
}
|
|
});
|
|
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"
|
|
>
|
|
<i class="bi bi-grip-vertical h5"></i>
|
|
</button>
|
|
|
|
<div class="grid-container z-0" style="height: calc(100vh - 51px);">
|
|
<div class="grid-stack z-0">
|
|
<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"
|
|
>
|
|
<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'"
|
|
></button>
|
|
<component :is="w.component2" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<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">
|
|
Manage grid widgets <button
|
|
class="btn btn-sm"
|
|
:class="settingsStore.local.grid_enabled == true ? 'btn-outline-success' : 'btn-outline-danger'"
|
|
type="button"
|
|
@click="toggleGridEdit"
|
|
title="Lock/unloack changes to grid"
|
|
>
|
|
<i class="bi" :class="settingsStore.local.grid_enabled == true ? 'bi-unlock-fill' : 'bi-lock-fill'"></i>
|
|
</button>
|
|
</h5>
|
|
|
|
<button
|
|
type="button"
|
|
class="btn-close"
|
|
data-bs-dismiss="offcanvas"
|
|
aria-label="Close"
|
|
></button>
|
|
</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!
|
|
</p>
|
|
<div>
|
|
<button
|
|
class="btn btn-sm btn-outline-primary"
|
|
type="button"
|
|
@click="quickfill"
|
|
>
|
|
Fill grid with common widgets
|
|
</button>
|
|
</div>
|
|
<hr />
|
|
<!-- 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"
|
|
>
|
|
<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>
|
|
|
|
<!-- 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"
|
|
>
|
|
<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>
|
|
<!-- 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>
|
|
</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"
|
|
>
|
|
<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>
|
|
|
|
<!-- 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"
|
|
>
|
|
<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>
|
|
|
|
<!-- 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"
|
|
>
|
|
<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>
|
|
</div>
|
|
<hr />
|
|
<button
|
|
class="btn btn-sm btn-outline-warning"
|
|
type="button"
|
|
@click="clearAllItems"
|
|
title="Clear all items from the grid"
|
|
>
|
|
Clear grid
|
|
</button>
|
|
<hr/>
|
|
<button
|
|
class="btn btn-sm btn-outline-dark"
|
|
type="button"
|
|
@click="loadPreset"
|
|
title="Restore your saved grid preset (clears current grid)"
|
|
>
|
|
|
|
Restore preset
|
|
</button>
|
|
<button
|
|
class="btn btn-sm btn-outline-dark"
|
|
type="button"
|
|
@click="savePreset"
|
|
title="Save current grid layout as a preset that can be restored using restore preset button"
|
|
>
|
|
Save preset
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<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>
|
|
|
|
|
|
|
|
</div>
|
|
<div class="offcanvas-body">
|
|
<p>
|
|
Commonly used frequencies are listed here, and are all USB. 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"
|
|
@click="updateFrequencyAndApply(state.new_frequency)"
|
|
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>
|
|
</a>
|
|
<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">18.106 MHz</h5>
|
|
<small>EU / US</small>
|
|
<h6>17m</h6>
|
|
</div>
|
|
</a>
|
|
<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>
|
|
|
|
<style>
|
|
.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 */
|
|
}
|
|
|
|
.grid-stack-item {
|
|
text-align: center;
|
|
overflow: auto;
|
|
z-index: 50;
|
|
}
|
|
.grid-stack-floaty-btn {
|
|
position: absolute;
|
|
right: 2px;
|
|
z-index: 1000;
|
|
float: right;
|
|
top: 4px;
|
|
}
|
|
.grid-container {
|
|
overflow-y: auto;
|
|
}
|
|
.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>
|