FreeDATA/gui/src/components/dynamic_components2.vue

878 lines
24 KiB
Vue
Raw 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 { setModemFrequency } 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";
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";
import { stateDispatcher } from "../js/eventHandler";
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;
constructor(component, size, text, quickfill, autoPlace, category) {
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;
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 = [
2023-11-26 06:07:52 +00:00
new gridWidget(
active_heard_stations,
2023-12-09 06:08:15 +00:00
{ x: 0, y: 0, w: 16, h: 40 },
2023-12-09 10:21:47 +00:00
"Detailed heard stations list",
true,
true,
"Activity",
2023-11-26 06:07:52 +00:00
),
new gridWidget(
active_stats,
2023-12-16 16:56:55 +00:00
{ x: 16, y: 16, w: 8, h: 80 },
"Stats (waterfall, etc)",
true,
true,
"Stats",
),
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",
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",
),
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",
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 },
2023-12-09 10:21:47 +00:00
"Mini heard stations list",
false,
true,
"Activity",
),
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",
),
new gridWidget(
dbfs_meter,
2023-12-16 16:11:16 +00:00
{ x: 20, y: 0, w: 4, h: 8 },
"Dbfs Meter",
2023-12-16 16:11:16 +00:00
true,
true,
"Audio",
),
new gridWidget(
grid_activities,
{ x: 0, y: 40, w: 6, h: 55 },
2023-12-09 10:21:47 +00:00
"Activities list",
true,
true,
2023-12-09 10:21:47 +00:00
"Activity",
),
2023-12-12 21:20:02 +00:00
new gridWidget(
active_broadcasts_vert,
2024-01-06 08:10:20 +00:00
{ x: 9, y: 55, w: 10, h: 40 },
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",
),
2023-12-12 22:20:48 +00:00
new gridWidget(
grid_ptt,
2024-01-05 20:27:11 +00:00
{ x: 17, y: 8, w: 5, h: 12 },
2023-12-16 16:11:16 +00:00
"Tx/PTT indicator",
true,
true,
"Rig",
),
new gridWidget(
grid_mycall,
2024-01-05 20:27:11 +00:00
{ x: 8, y: 40, w: 5, h: 15 },
2023-12-16 16:11:16 +00:00
"My callsign widget",
true,
true,
"Other",
),
new gridWidget(
grid_CQ_btn,
2023-12-12 22:20:48 +00:00
{ x: 3, y: 27, w: 2, h: 8 },
2023-12-16 16:11:16 +00:00
"CQ Button",
2023-12-12 22:20:48 +00:00
false,
true,
2023-12-16 16:11:16 +00:00
"Broadcasts",
),
new gridWidget(
grid_ping,
{ x: 3, y: 27, w: 4, h: 9 },
"Ping Widget",
false,
true,
"Broadcasts",
),
new gridWidget(
grid_freq,
2023-12-16 16:56:55 +00:00
{ x: 20, y: 8, w: 4, h: 8 },
2023-12-16 16:11:16 +00:00
"Frequency widget",
true,
true,
2023-12-12 22:20:48 +00:00
"Rig",
),
2024-01-06 08:31:06 +00:00
new gridWidget(
grid_stop,
{ x: 8, y: 40, w: 5, h: 15 },
"Stop Widget",
true,
true,
"Other",
),
2023-11-26 06:02:16 +00:00
];
2023-12-16 16:11:16 +00:00
function updateFrequencyAndApply(frequency) {
state.new_frequency = frequency;
setModemFrequency(state.new_frequency);
}
function set_hamlib_frequency_manually() {
setModemFrequency(state.new_frequency);
}
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,
);
});
function onChange(event, changeItems) {
// update item position
changeItems.forEach((item) => {
var widget = items.value.find((w) => w.id == item.id);
if (!widget) {
2023-12-09 06:08:15 +00:00
console.error("Widget not found: " + item.id);
return;
}
widget.x = item.x;
widget.y = item.y;
widget.w = item.w;
widget.h = item.h;
});
2023-12-17 15:58:19 +00:00
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 index
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++ ){
//Refs are passed, so grab original settings for restoration
let tempGW = gridWidgets[parseInt(savedGrid[i][4])];
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)
cfg[i] = [items.value[i].x, items.value[i].y, items.value[i].w,items.value[i].h, widget ];
}
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();
});
2023-12-17 15:58:19 +00:00
}
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();
}
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
2023-12-19 02:22:34 +00:00
<div class="grid-container" style="height: calc(100vh - 51px);">
2023-12-05 03:27:35 +00:00
<div class="grid-stack">
<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"
></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">
2023-12-09 06:08:15 +00:00
Manage grid widgets
</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"
>
Clear grid
</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>
</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"
@click="set_hamlib_frequency_manually"
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">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>