Grid layout system proof of concept demo

This commit is contained in:
Mashintime 2023-11-25 01:17:45 -05:00
parent 395e505b84
commit 006dec976a
4 changed files with 285 additions and 0 deletions

View file

@ -49,6 +49,7 @@
"emoji-picker-element": "^1.18.3",
"emoji-picker-element-data": "^1.4.0",
"file-saver": "^2.0.5",
"gridstack": "^10.0.0",
"mime": "^3.0.0",
"pinia": "^2.1.6",
"pouchdb": "^8.0.1",

View file

@ -0,0 +1,108 @@
<script setup lang="ts">
import { ref, onMounted, reactive, nextTick } from "vue";
import { setActivePinia } from "pinia";
import pinia from "../store/index";
setActivePinia(pinia);
import "../../node_modules/gridstack/dist/gridstack.min.css"
import { GridStack } from "gridstack";
import { settingsStore as settings } from "../store/settingsStore.js";
import hsl from "./main_active_heard_stations.vue"
import stats from "./main_active_stats.vue"
import audio from "./main_active_audio_level.vue"
import rigctl from "./main_active_rig_control.vue"
import beacon from "./main_active_broadcasts.vue"
let count = ref(0);
let info = ref("");
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
const items = [
{ x: 0, y: 1, h: 2 },
{ x: 0, y: 2, w: 3 },
{ x: 0, y: 7 },
{ x: 3, y: 1, h: 2 },
{ x: 0, y: 6, w: 2, h: 2 },
];
let components = reactive({
yourRandomComponent1: {
name: hsl, props: {}, gridPos: { x: 0, y: 1, w: 8, h: 13 }
},
yourRandomComponent2: {
name: stats, props: {}, gridPos: { x: 9, y: 1, w: 4, h: 13 }
},
yourRandomComponent5: {
name: beacon, props: {}, gridPos: { x: 0, y: 15, w: 4, h: 5 },
},
yourRandomComponent4: {
name: rigctl, props: {}, gridPos: { x: 5, y: 15, w: 4, h: 5},
},
yourRandomComponent3: {
name: audio, props: {}, gridPos: { x: 14, y: 15, w: 4, h: 5},
},
});
onMounted(() => {
grid = GridStack.init({ // DO NOT use grid.value = GridStack.init(), see above
float: true,
cellHeight: "25px",
minRow: 24,
});
grid.on("dragstop", function (event, element) {
const node = element.gridstackNode;
info.value = `you just dragged node #${node.id} to ${node.x},${node.y} good job!`;
});
});
function addNewWidget() {
components.yourRandomComponent3= {
name: rigctl, props: {}, gridPos: {x: 14, y: 15, w: 4, h: 5 }
}
// we have to wait for vue to update v-for,
// until then the querySelector wont find the element
nextTick(() => {
console.log(grid);
let compEl = document.querySelector('[gs-id="yourRandomComponent3"]');
console.log(compEl);
grid.makeWidget(compEl);
});
//console.warn("i will only work once, fix my inputs to reuse me");
}
</script>
<template>
<!--
<button @click="addNewWidget()">Add Widget</button> {{ info }}
-->
<section class="grid-stack">
<div
v-for="(component, key, index) in components"
:key="'component'+index"
:gs-id="key"
class="grid-stack-item"
:gs-x="component.gridPos.x"
:gs-y="component.gridPos.y"
:gs-h="component.gridPos.h"
:gs-w="component.gridPos.w"
gs-auto-position="true"
>
<div class="grid-stack-item-content">
<component :is="component.name" v-bind="component.props" />
</div>
</div>
</section>
</template>
<style>
.grid-stack-item {
color: #2c3e50;
text-align: center;
overflow: auto;
z-index: 50;
}
</style>

View file

@ -0,0 +1,156 @@
<script setup lang="ts">
import { ref, onMounted, reactive, nextTick } 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 { settingsStore as settings } from "../store/settingsStore.js";
import hsl from "./main_active_heard_stations.vue"
import stats from "./main_active_stats.vue"
import audio from "./main_active_audio_level.vue"
import rigctl from "./main_active_rig_control.vue"
import beacon from "./main_active_broadcasts.vue"
import { stateDispatcher } from "../js/eventHandler";
let count = ref(0);
let info = ref("");
let gridFloat = ref(false);
let color = ref("black");
let gridInfo = ref("");
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([]);
onMounted(() => {
grid = GridStack.init({ // DO NOT user grid.value = GridStack.init(), see above
float: true,
cellHeight: "70px",
minRow: 12,
});
grid.on("dragstop", function (event, element) {
const node = element.gridstackNode;
info.value = `you just dragged node #${node.id} to ${node.x},${node.y} good job!`;
});
grid.on('change', onChange);
// gridFloat.value = grid.float();
});
function changeFloat() {
gridFloat.value = !gridFloat.value;
grid.float(gridFloat.value);
}
function onChange(event, changeItems) {
updateInfo();
// update item position
changeItems.forEach(item => {
var widget = items.value.find(w => w.id == item.id);
if (!widget) {
alert("Widget not found: " + item.id);
return;
}
widget.x = item.x;
widget.y = item.y;
widget.w = item.w;
widget.h = item.h;
});
}
function addNewWidget2(componentToAdd) {
const node = items[count.value] || { x: 0, y: 0, w: 3, h: 3 };
node.id = 'w_'+ (count.value++);
node.component2=componentToAdd;
items.value.push(node);
nextTick(()=>{
grid.makeWidget(node.id);
updateInfo();
});
}
function removeLastWidget() {
if (count.value == 0) return;
var id = `w_${count.value-1}`;
var index = items.value.findIndex(w => w.id == id);
if (index < 0) return;
var removed = items.value[index];
remove(removed);
}
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);
updateInfo();
}
function updateInfo() {
color.value = grid.engine.nodes.length == items.value.length ? "black" : "red";
gridInfo.value = `Grid engine: ${grid.engine.nodes.length}, widgets: ${items.value.length}`;
}
function showModal(){
new Modal("#tileModal", {}).show();
}
function quickfill() {
addNewWidget2(hsl);
addNewWidget2(stats);
addNewWidget2(audio);
addNewWidget2(rigctl);
addNewWidget2(beacon);
}
</script>
<template>
<button type="button" @click="showModal">Add Widget pos [0,0]</button>
<button type="button" @click="quickfill">Quickfill</button>
<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="true">
<div class="grid-stack-item-content">
<button @click="remove(w)" class="btn-close grid-stack-floaty-btn"><i style="font-size: x-small;" class="bi bi-trash3 h3"></i></button>
<component :is="w.component2" v-bind="w" />
</div>
</div>
</div>
<div class="modal fade" id="tileModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Add grid tile</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="btn-group" role="group" aria-label="Basic outlined example">
<button type="button" @click=addNewWidget2(hsl) class="btn btn-outline-secondary" data-bs-dismiss="modal">Heard station list</button>
<button type="button" @click=addNewWidget2(stats) class="btn btn-outline-secondary" data-bs-dismiss="modal">Stats (waterfall, etc)</button>
<button type="button" @click=addNewWidget2(audio) class="btn btn-outline-secondary" data-bs-dismiss="modal">Audio</button>
<button type="button" @click=addNewWidget2(beacon) class="btn btn-outline-secondary" data-bs-dismiss="modal">Broadcasts</button>
<button type="button" @click=addNewWidget2(rigctl) class="btn btn-outline-secondary" data-bs-dismiss="modal">Rig Control</button>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</div>
</template>
<style>
.grid-stack-item {
text-align: center;
overflow: auto;
z-index: 50;
}
.grid-stack-floaty-btn{
position: absolute;
right:0px;
z-index:1000;
float: right;
top: 3px;
opacity: 50%;
}
</style>

View file

@ -18,6 +18,7 @@ import main_active_audio_level from "./main_active_audio_level.vue";
import chat from "./chat.vue";
import infoScreen from "./infoScreen.vue";
import main_modem_healthcheck from "./main_modem_healthcheck.vue";
import Dynamic_components2 from "./dynamic_components2.vue";
function stopAllTransmissions() {
console.log("stopping transmissions");
@ -104,6 +105,16 @@ function stopAllTransmissions() {
aria-controls="list-logger"
><i class="bi bi-activity h3"></i
></a>
<a
class="list-group-item list-group-item-dark list-group-item-action border-0 rounded-3 mb-2"
id="list-grid-list"
data-bs-toggle="list"
href="#list-grid"
role="tab"
aria-controls="list-grid"
title="Grid test"
><i class="bi bi-grid h3"></i
></a>
<a
class="list-group-item list-group-item-dark list-group-item-action border-0 rounded-3 mb-2"
@ -319,6 +330,15 @@ function stopAllTransmissions() {
>
<infoScreen />
</div>
<div
class="tab-pane fade"
id="list-grid"
role="tabpanel"
aria-labelledby="list-grid-list"
>
<Dynamic_components2 />
</div>
<div
class="tab-pane fade"
id="list-chat"