feat(smart scroll, ANSI clear, ANSI refresh)

This commit is contained in:
kerms 2024-05-21 15:14:46 +08:00
parent 15c1143b25
commit 51783612bc
5 changed files with 380 additions and 169 deletions

View File

@ -58,6 +58,7 @@ class OneTimeWebsocket implements IWebsocket {
/* did not receive packet "heartBeatTimeout" times,
* connection may be lost: close the socket */
if (this.socket.readyState === this.socket.OPEN) {
console.log("No heart beat, break connection");
this.close();
this.clear();
}
@ -91,7 +92,10 @@ class OneTimeWebsocket implements IWebsocket {
this.msgCallback(msg);
}
this.socket.onclose = () => {
this.socket.onclose = (ev) => {
if (isDevMode()) {
console.log("ws closed", ev.reason, ev.code);
}
this.socket.onclose = null
this.socket.onopen = null
this.socket.onerror = null

View File

@ -1,5 +1,12 @@
import {defineStore} from "pinia";
import {computed, type Ref, ref, shallowReactive} from "vue";
import {
computed,
type Ref,
type ShallowReactive,
ref,
shallowReactive,
watch
} from "vue";
import {AnsiUp} from 'ansi_up'
import {debouncedWatch} from "@vueuse/core";
import {type IUartConfig, uart_send_msg} from "@/api/apiUart";
@ -13,6 +20,7 @@ interface IDataArchive {
export interface IDataBuf {
time: string;
isRX: boolean;
type: number;
data: Uint8Array;
str: string;
hex: string;
@ -67,6 +75,9 @@ const zeroPad = (num: number, places: number) => String(num).padStart(places, '0
const ansi_up = new AnsiUp();
ansi_up.escape_html = false;
const ANSI_REFRESH_WINDOW = new Uint8Array([0x1B, 0x5B, 0x37, 0x74]);
const ANSI_CLEAR_WINDOW = new Uint8Array([0x1B, 0x5B, 0x32, 0x4A]);
/* quick HEX lookup table */
const byteToHex: string[] = new Array(256);
for (let n = 0; n <= 0xff; ++n) {
@ -128,11 +139,12 @@ function strToHTML(str: string) {
.replace(/ /g, '&nbsp;');
}
function isArrayContained(subArray: Uint8Array, mainArray: Uint8Array) {
function isArrayContained(subArray: Uint8Array, mainArray: Uint8Array,
sub_start: number = 0, main_start: number = 0) {
if (subArray.length === 0) return -1;
outerLoop: for (let i = 0; i <= mainArray.length - subArray.length; i++) {
outerLoop: for (let i = main_start; i <= mainArray.length - subArray.length; i++) {
// Check if subArray is found starting at position i in mainArray
for (let j = 0; j < subArray.length; j++) {
for (let j = sub_start; j < subArray.length; j++) {
if (mainArray[i + j] !== subArray[j]) {
continue outerLoop; // Continue the outer loop if mismatch found
}
@ -207,8 +219,7 @@ export const useDataViewerStore = defineStore('text-viewer', () => {
const showHexdump = ref(false);
const enableLineWrap = ref(true);
const showTimestamp = ref(true);
const pauseAutoRefresh = ref(false);
const showVirtualScroll = ref(true);
const RxHexdumpColor = ref("#f0f9eb");
const RxTotalByteCount = ref(0);
@ -253,36 +264,40 @@ export const useDataViewerStore = defineStore('text-viewer', () => {
return encoder.encode(str);
})
debouncedWatch(() => frameBreakSequence.value, (newValue) => {
const unescapedStr = unescapeString(newValue);
const encoder = new TextEncoder();
frameBreakSequenceNormalized.value = encoder.encode(unescapedStr);
}, {debounce: 300, immediate: true});
// debouncedWatch(() => frameBreakSequence.value, (newValue) => {
// const unescapedStr = unescapeString(newValue);
// const encoder = new TextEncoder();
// frameBreakSequenceNormalized.value = encoder.encode(unescapedStr);
// }, {debounce: 300, immediate: true});
//
// debouncedWatch(() => frameBreakDelay.value, (newValue) => {
// if (newValue < 0) {
// frameBreakReady = false;
// clearTimeout(frameBreakTimeoutID);
// } else if (newValue === 0) {
// frameBreakReady = true;
// clearTimeout(frameBreakTimeoutID);
// } else {
// refreshTimeout();
// }
// }, {debounce: 300, immediate: true});
debouncedWatch(() => frameBreakDelay.value, (newValue) => {
if (newValue < 0) {
frameBreakReady = false;
clearTimeout(frameBreakTimeoutID);
} else if (newValue === 0) {
frameBreakReady = true;
clearTimeout(frameBreakTimeoutID);
} else {
refreshTimeout();
}
}, {debounce: 300, immediate: true});
// let frameBreakReady = false;
// let frameBreakTimeoutID = setTimeout(() => {
// }, 0);
const dataArchive: IDataArchive[] = [];
const dataBuf: IDataBuf[] = [];
const dataBufLength = ref(0);
/* actual data shown on screen */
const dataFiltered: IDataBuf[] = shallowReactive([]);
const dataFilteredLength = ref(0);
const dataFiltered: ShallowReactive<IDataBuf[]> = shallowReactive([]);
const dataFilterAutoUpdate = ref(true);
const acceptIncomingData = ref(false);
let frameBreakReady = false;
let frameBreakTimeoutID = setTimeout(() => {
}, 0);
// let frameBreakReady = false;
// let frameBreakTimeoutID = setTimeout(() => {
// }, 0);
debouncedWatch(computedFilterValue, () => {
dataFiltered.length = 0; // Clear the array efficiently
@ -302,24 +317,93 @@ export const useDataViewerStore = defineStore('text-viewer', () => {
}
}, {debounce: 300});
function addString(item: string, isRX: boolean = false, doSend: boolean = false) {
let batchDataUpdateIntervalID: number = -1;
const batchUpdateTime = ref(80); /* ms */
let batchStartIndex: number = 0;
watch(batchUpdateTime, value => {
if (batchDataUpdateIntervalID >= 0) {
clearInterval(batchDataUpdateIntervalID);
batchDataUpdateIntervalID = -1;
}
batchUpdate();
if (dataFilterAutoUpdate.value && batchDataUpdateIntervalID < 0) {
batchDataUpdateIntervalID = setInterval(batchUpdate, batchUpdateTime.value);
}
}, {immediate: true});
setInterval(() => {
dataBufLength.value = dataBuf.length;
}, 500);
function addString(item: string, isRX: boolean = false, doSend: boolean = false, type: number = 0) {
const encoder = new TextEncoder();
item = unescapeString(item);
const encodedStr = encoder.encode(item);
return addItem(encodedStr, isRX, doSend);
return addItem(encodedStr, isRX, doSend, type);
}
function addHexString(item: string, isRX: boolean = false, doSend: boolean = false){
function addHexString(item: string, isRX: boolean = false, doSend: boolean = false, type: number = 0){
if (item === "") {
return addItem(new Uint8Array(0), isRX);
}
const hexArray = item.split(' ');
// Map each hex value to a decimal (integer) and create a Uint8Array from these integers
const uint8Array = new Uint8Array(hexArray.map(hex => parseInt(hex, 16)));
return addItem(uint8Array, isRX, doSend);
return addItem(uint8Array, isRX, doSend, type);
}
function addItem(item: Uint8Array, isRX: boolean, doSend: boolean = false){
function batchUpdate() {
if (batchStartIndex >= dataBuf.length) {
return;
}
/* handle data buf array */
if (dataBuf.length >= 30000) {
/* make array size to 15000 */
const deleteCount = dataBuf.length - 30000 + 5000;
batchStartIndex -= deleteCount;
dataBuf.splice(0, deleteCount);
}
if (!dataFilterAutoUpdate.value) {
return;
}
softRefreshFilterBuf();
}
function softRefreshFilterBuf(delayTime: number = 0) {
/* handle filtered buf array */
const totalBufLength = dataBuf.length - batchStartIndex + dataFiltered.length;
if (batchStartIndex < 0) {
dataFiltered.length = 1;
dataFiltered.pop();
} else if (totalBufLength >= 30000) {
dataFiltered.splice(0, totalBufLength - 30000 + 5000);
}
if (!enableFilter.value || computedFilterValue.value.length === 0) {
/* no filter, do normal push */
if (batchStartIndex < 0) {
dataFiltered.push(...dataBuf);
} else {
dataFiltered.push(...dataBuf.slice(batchStartIndex));
}
batchStartIndex = dataBuf.length;
} else if (enableFilter.value && computedFilterValue.value.length !== 0) {
for (let i = batchStartIndex; i < dataBuf.length; i++) {
if (isArrayContained(computedFilterValue.value, dataBuf[i].data) >= 0) {
dataFiltered.push(dataBuf[i]);
}
}
batchStartIndex = dataBuf.length;
}
}
function addItem(item: Uint8Array, isRX: boolean, doSend: boolean = false, type: number = 0) {
const t = new Date();
// dataArchive.push({
@ -349,119 +433,107 @@ export const useDataViewerStore = defineStore('text-viewer', () => {
TxByteCount.value = item.length;
}
let str = decodeUtf8(item);
let str = ""
str = decodeUtf8(item);
str = escapeHTML(str);
str = strToHTML(str);
/* unescape data \n */
if (enableAnsiDecode.value) {
if (isArrayContained(ANSI_CLEAR_WINDOW, item) >= 0) {
clearFilteredBuff();
}
if (isArrayContained(ANSI_REFRESH_WINDOW, item) >= 0) {
batchUpdate()
softRefreshFilterBuf();
}
/* ansi_to_html will escape HTML sequence */
str = ansi_up.ansi_to_html("\x1b[0m" + str);
}
dataBuf.push({
time: "["
time:
"["
+ zeroPad(t.getHours(), 2) + ":"
+ zeroPad(t.getMinutes(), 2) + ":"
+ zeroPad(t.getSeconds(), 2) + ":"
+ zeroPad(t.getMilliseconds(), 3)
+ "]",
type: type,
data: item,
isRX: isRX,
str: str,
hex: u8toHexString(item),
hexdump: u8toHexdump(item),
});
if (dataBuf.length >= 20000) {
dataBuf.splice(0, 5000);
}
if (!enableFilter.value || computedFilterValue.value.length === 0) {
dataFiltered.push(dataBuf[dataBuf.length - 1]);
if (dataFiltered.length >= 20000) {
dataFiltered.splice(0, 5000);
}
} else if (enableFilter.value && isArrayContained(computedFilterValue.value, dataBuf[dataBuf.length - 1].data) >= 0) {
dataFiltered.push(dataBuf[dataBuf.length - 1]);
if (dataFiltered.length >= 20000) {
dataFiltered.splice(0, 5000);
}
}
dataBufLength.value = dataBuf.length;
}
function popItem() {
dataBuf.pop();
dataFiltered.pop();
}
function doFrameBreak() {
frameBreakReady = true;
}
function refreshTimeout() {
/* always break */
// if (frameBreakDelay.value === 0) {
// function doFrameBreak() {
// frameBreakReady = true;
// }
if (!frameBreakReady && frameBreakDelay.value > 0) {
clearTimeout(frameBreakTimeoutID);
frameBreakTimeoutID = setTimeout(doFrameBreak, frameBreakDelay.value);
}
}
// function refreshTimeout() {
// /* always break */
// // if (frameBreakDelay.value === 0) {
// // frameBreakReady = true;
// // }
//
//
// if (!frameBreakReady && frameBreakDelay.value > 0) {
// clearTimeout(frameBreakTimeoutID);
// frameBreakTimeoutID = setTimeout(doFrameBreak, frameBreakDelay.value);
// }
// }
function addChunk(item: Uint8Array, isRX: boolean) {
let newArray: Uint8Array;
if (frameBreakSequence.value === "") {
if (frameBreakReady || dataBuf.length === 0 || dataBuf[dataBuf.length - 1].isRX != isRX) {
addItem(item, isRX);
frameBreakReady = false;
} else {
/* TODO: append item to last */
newArray = new Uint8Array(dataBuf[dataBuf.length - 1].data.length + item.length + 1);
newArray.set(dataBuf[dataBuf.length - 1].data);
newArray.set(item, dataBuf[dataBuf.length - 1].data.length);
popItem();
addItem(newArray, isRX);
}
refreshTimeout();
return;
}
if (frameBreakReady) {
newArray = item;
} else {
if (dataBuf.length) {
newArray = new Uint8Array(dataBuf[dataBuf.length - 1].data.length + item.length + 1);
newArray.set(dataBuf[dataBuf.length - 1].data);
newArray.set(item, dataBuf[dataBuf.length - 1].data.length);
popItem();
} else {
newArray = item;
}
}
console.log(newArray)
console.log(frameBreakSequenceNormalized.value)
/* break frame at sequence match */
let matchIndex = isArrayContained(frameBreakSequenceNormalized.value, newArray);
while (matchIndex < 0) {
console.log(matchIndex)
/* update last buf item */
addItem(newArray.slice(0, matchIndex + frameBreakSequenceNormalized.value.length), isRX);
newArray = newArray.slice(matchIndex + frameBreakSequenceNormalized.value.length);
matchIndex = isArrayContained(frameBreakSequenceNormalized.value, newArray);
}
addItem(newArray.slice(0, matchIndex + frameBreakSequenceNormalized.value.length), isRX);
}
// function addChunk(item: Uint8Array, isRX: boolean) {
// let newArray: Uint8Array;
//
// if (frameBreakSequence.value === "") {
// if (frameBreakReady || dataBuf.length === 0 || dataBuf[dataBuf.length - 1].isRX != isRX) {
// addItem(item, isRX);
// frameBreakReady = false;
// } else {
// /* TODO: append item to last */
// newArray = new Uint8Array(dataBuf[dataBuf.length - 1].data.length + item.length + 1);
// newArray.set(dataBuf[dataBuf.length - 1].data);
// newArray.set(item, dataBuf[dataBuf.length - 1].data.length);
// popItem();
// addItem(newArray, isRX);
// }
// refreshTimeout();
// return;
// }
//
//
// if (frameBreakReady) {
// newArray = item;
// } else {
// if (dataBuf.length) {
// newArray = new Uint8Array(dataBuf[dataBuf.length - 1].data.length + item.length + 1);
// newArray.set(dataBuf[dataBuf.length - 1].data);
// newArray.set(item, dataBuf[dataBuf.length - 1].data.length);
// popItem();
// } else {
// newArray = item;
// }
// }
//
// console.log(newArray)
// console.log(frameBreakSequenceNormalized.value)
//
// /* break frame at sequence match */
// let matchIndex = isArrayContained(frameBreakSequenceNormalized.value, newArray);
// while (matchIndex < 0) {
// console.log(matchIndex)
// /* update last buf item */
// addItem(newArray.slice(0, matchIndex + frameBreakSequenceNormalized.value.length), isRX);
// newArray = newArray.slice(matchIndex + frameBreakSequenceNormalized.value.length);
// matchIndex = isArrayContained(frameBreakSequenceNormalized.value, newArray);
// }
// addItem(newArray.slice(0, matchIndex + frameBreakSequenceNormalized.value.length), isRX);
// }
function clearByteCount(isRX: boolean) {
if (isRX) {
@ -472,9 +544,10 @@ export const useDataViewerStore = defineStore('text-viewer', () => {
}
function clearDataBuff() {
clearFilteredBuff();
dataBuf.length = 0;
dataFiltered.length = 0;
dataBufLength.value = 0;
batchStartIndex = 0;
RxByteCount.value = 0;
RxTotalByteCount.value = 0;
@ -484,6 +557,7 @@ export const useDataViewerStore = defineStore('text-viewer', () => {
}
function clearFilteredBuff() {
showVirtualScroll.value = !showVirtualScroll.value;
dataFiltered.length = 0;
}
@ -508,22 +582,27 @@ export const useDataViewerStore = defineStore('text-viewer', () => {
return {
addItem,
addChunk,
addString,
addHexString,
clearFilteredBuff,
clearDataBuff,
refreshFilteredBuff,
softRefreshFilterBuf,
textSuffixValue,
textPrefixValue,
clearByteCount,
dataBufLength,
configPanelTab,
configPanelShow,
pauseAutoRefresh,
quickAccessPanelShow,
dataFiltered,
dataFilterAutoUpdate,
filterValue,
batchUpdateTime,
acceptIncomingData,
showVirtualScroll,
enableAnsiDecode,
showHex,
showHexdump,

View File

@ -368,8 +368,9 @@ const onUartJsonMsg = (msg: api.ApiJsonMsg) => {
case WtUartCmd.GET_CONFIG:
case WtUartCmd.SET_CONFIG:{
const uartMsg = msg as IUartMsgConfig;
store.uartConfig.data_bits = uartMsg.data_bits;
store.uartConfig.stop_bits = uartMsg.stop_bits;
store.uartConfig.parity = uartMsg.parity;
break;
}
default:
@ -381,7 +382,9 @@ const onUartJsonMsg = (msg: api.ApiJsonMsg) => {
};
const onUartBinaryMsg = (msg: ApiBinaryMsg) => {
if (isDevMode()) {
console.log("uart", msg);
}
if (msg.sub_mod !== 1) {
/* ignore other num for the moment */
@ -410,9 +413,10 @@ const onClientCtrl = (msg: api.ControlMsg) => {
}
if (msg.data === ControlEvent.DISCONNECTED) {
store.acceptIncomingData = false;
} else if (msg.data === ControlEvent.CONNECTED) {
updateUartData();
store.acceptIncomingData = true;
}
};

View File

@ -85,7 +85,10 @@
<el-form-item label=" ">
<div class="flex">
<el-button type="primary">连接</el-button>
<el-button :type="store.acceptIncomingData ? 'danger': 'success'"
:disabled="wsStore.state !== ControlEvent.CONNECTED">
{{ store.acceptIncomingData ? "停止数据收发" : "开始数据收发" }}
</el-button>
</div>
</el-form-item>
</el-form>
@ -161,6 +164,34 @@
过滤
</template>
</el-input>
<div class="border rounded flex flex-col">
<el-tooltip
class="box-item"
effect="light"
placement="right-start"
>
<template #content>
</template>
<el-checkbox border v-model="store.dataFilterAutoUpdate">新数据自动刷新</el-checkbox>
</el-tooltip>
<el-tooltip content="提高间隔可减少CPU资源的使用" placement="right" effect="light" :show-after="500">
<div class="flex gap-4 p-2">
<el-text>数据显示刷新间隔(ms)</el-text>
<el-input-number
:step="10"
:min="10"
size="small"
v-model="store.batchUpdateTime"
>
</el-input-number>
</div>
</el-tooltip>
</div>
</div>
</template>
@ -224,9 +255,12 @@
<script setup lang="ts">
import {ref} from "vue";
import {useDataViewerStore} from "@/stores/dataViewerStore";
import {useWsStore} from "@/stores/websocket";
import {globalNotify} from "@/composables/notification";
import {ControlEvent} from "@/api";
const store = useDataViewerStore()
const wsStore = useWsStore()
const collapseActiveName = ref(["1", "2"])
const uartCustomBaud = ref(9600)

View File

@ -39,14 +39,24 @@
placement="top"
>
<template #content>
<p>与缓存同步</p>
<p>与缓存同步+过滤</p>
</template>
<el-button size="small" @click="store.refreshFilteredBuff">
刷新
</el-button>
</el-tooltip>
<!-- <el-checkbox class="hover:bg-blue-200" size="small" v-model="store.pauseAutoRefresh" label="暂停数据刷新" border/>-->
<el-tooltip
class="box-item"
effect="light"
placement="top"
>
<template #content>
<p>仅停止刷新显示区后台继续接收数据</p>
</template>
<el-checkbox size="small" border v-model="store.dataFilterAutoUpdate">
自动刷新
</el-checkbox>
</el-tooltip>
</div>
@ -85,6 +95,7 @@
<div class="flex flex-grow overflow-hidden border-2 scroll-m-2">
<v-virtual-scroll
v-if="store.showVirtualScroll"
:items="store.dataFiltered"
id="myScrollerID"
ref="vuetifyVirtualScrollRef"
@ -94,8 +105,47 @@
<template v-slot:default="{ item, }">
<div>
<div class="flex">
<p class="text-nowrap text-sm text-lime-500" v-if="item.isRX" type="success" v-show="store.showTimestamp"><span>{{ item.time }}</span>-RX|</p>
<p class="text-nowrap text-sm text-sky-500" v-else type="primary" v-show="store.showTimestamp"><span>{{ item.time }}</span>TX-|</p>
<p class="text-nowrap text-sm text-lime-500" v-if="item.isRX" type="success" v-show="store.showTimestamp">
<span>{{ item.time }}</span>-RX|</p>
<p class="text-nowrap text-sm text-sky-500" v-else-if="item.type === 0" type="primary" v-show="store.showTimestamp">
<span>{{ item.time }}</span>TX-|</p>
<p class="text-nowrap text-sm text-amber-800" v-else type="primary" v-show="store.showTimestamp">
<span>{{ item.time }}</span>未发送|</p>
<p v-show="store.showText"
v-html="item.str"></p>
</div>
<div class="flex text-wrap">
<p v-show="store.showHex" class="">{{ item.hex }}</p>
</div>
<div class="flex">
<p v-show="store.showHexdump"
class="text-nowrap"
:style="{ 'background-color': item.isRX ? store.RxHexdumpColor : store.TxHexdumpColor }"
v-html="item.hexdump"
></p>
</div>
</div>
</template>
</v-virtual-scroll>
<v-virtual-scroll
v-else
:items="store.dataFiltered"
id="myScrollerID"
ref="vuetifyVirtualScrollRef2"
class="font-mono break-all text-sm"
:class="[store.enableLineWrap ? 'break-all' : 'text-nowrap']"
>
<template v-slot:default="{ item, }">
<div>
<div class="flex">
<p class="text-nowrap text-sm text-lime-500" v-if="item.isRX" type="success" v-show="store.showTimestamp">
<span>{{ item.time }}</span>-RX|</p>
<p class="text-nowrap text-sm text-sky-500" v-else-if="item.type === 0" type="primary" v-show="store.showTimestamp">
<span>{{ item.time }}</span>TX-|</p>
<p class="text-nowrap text-sm text-amber-800" v-else type="primary" v-show="store.showTimestamp">
<span>{{ item.time }}</span>未发送|</p>
<p v-show="store.showText"
v-html="item.str"></p>
</div>
@ -125,6 +175,7 @@
</el-tag>
</el-link>
<el-tooltip content="实际频率受界面刷新率影响,如需要更精确,可以尝试关闭‘自动刷新’" placement="right" effect="light" :show-after="1000">
<div class="flex align-center">
<el-checkbox v-model="enableLoopSend" class="font-mono font-bold max-h-5" size="small" border>
循环发送(ms)
@ -134,9 +185,11 @@
class="h-5"
size="small"
:step="10"
:min="1"
>
</el-input-number>
</div>
</el-tooltip>
<el-link @click="isSendTextFormat = !isSendTextFormat">
<el-tag class="font-mono font-bold" size="small">发送格式{{ isSendTextFormat ? "文本" : "HEX" }}</el-tag>
@ -145,12 +198,12 @@
<div class="flex gap-2">
<el-link @click="showTxTotalByte = !showTxTotalByte">
<el-tag class="font-mono font-bold" size="small">
{{ showTxTotalByte ? `TX统计:${store.TxTotalByteCount}B`: `上个TX帧:${store.TxByteCount}B` }}
{{ showTxTotalByte ? `TX统计:${store.TxTotalByteCount}B` : `上个TX帧:${store.TxByteCount}B` }}
</el-tag>
</el-link>
<el-link type="success" @click="showRxTotalByte = !showRxTotalByte">
<el-tag class="font-mono font-bold" size="small" type="success">
{{ showRxTotalByte ? `RX统计:${store.RxTotalByteCount}B`: `上个RX帧:${store.RxByteCount}B` }}
{{ showRxTotalByte ? `RX统计:${store.RxTotalByteCount}B` : `上个RX帧:${store.RxByteCount}B` }}
</el-tag>
</el-link>
<div class="flex align-center">
@ -184,30 +237,33 @@ import {nextTick, onMounted, onUnmounted, ref, watch} from "vue";
import {useDataViewerStore} from "@/stores/dataViewerStore";
import InlineSvg from "@/components/InlineSvg.vue";
import TextDataConfig from "@/views/text-data-viewer/textDataConfig.vue";
import {debouncedWatch} from "@vueuse/core";
const count = ref(0);
const showTxTotalByte = ref(false);
const showRxTotalByte = ref(false);
const vuetifyVirtualScrollRef = ref(document.body);
const vuetifyVirtualScrollBarRef = ref(document.body);
const vuetifyVirtualScrollContainerRef = ref(document.body);
const enableLoopSend = ref(false);
const loopSendFreq = ref(1000);
let loopSendIntervalID: number;
let loopSendIntervalID: number = -1;
const isSendTextFormat = ref(true)
const isHexStringValid = ref(false);
const uartInputTextBox = ref("")
const store = useDataViewerStore();
let lastScrollHeight = 0;
const mutationObserver = new MutationObserver(() => {
if (store.forceToBottom) {
scrollToBottom();
}
});
onMounted(() => {
function attachScroll() {
const parent = document.getElementById('myScrollerID') || document.body;
// used to scroll to bottom
@ -219,15 +275,25 @@ onMounted(() => {
vuetifyVirtualScrollBarRef.value.onscroll = handleScroll;
if (vuetifyVirtualScrollContainerRef.value) {
const config = { childList: true, subtree: true, attributes: true };
const config = {childList: true, subtree: true, attributes: true};
mutationObserver.observe(vuetifyVirtualScrollBarRef.value, config)
}
}
onMounted(() => {
attachScroll();
})
onUnmounted(() => {
mutationObserver.disconnect();
});
debouncedWatch(() => store.showVirtualScroll, () => {
lastScrollHeight = 0;
mutationObserver.disconnect();
attachScroll();
}, {debounce: 80});
function addItem(nr: number) {
let rawText = "";
@ -253,7 +319,7 @@ function addItem(nr: number) {
rawText = count.value + "<p class=\"border-4\"> 666666666b\n6666 666\x1b[33m6666666666666666666666666</p>b\n"
const encoder = new TextEncoder();
const arr = encoder.encode(rawText);
store.addItem(arr, true);
store.addItem(arr, false, false, 1);
}
}
@ -283,7 +349,7 @@ function formatHexInput(input: string) {
str = str.replace(/[^0-9A-F]/gi, ' ');
let segments = str.split(/\s+/);
let output:string[] = [];
let output: string[] = [];
segments.forEach(segment => {
// Check if segment length is odd and needs padding
@ -315,7 +381,7 @@ watch(isSendTextFormat, (value) => {
}
});
watch(() => uartInputTextBox.value, (newValue) => {
watch(() => uartInputTextBox.value, () => {
if (!isSendTextFormat.value) {
checkHexTextValid()
}
@ -323,17 +389,22 @@ watch(() => uartInputTextBox.value, (newValue) => {
watch(enableLoopSend, (newValue) => {
if (newValue) {
if (loopSendIntervalID !== -1) {
clearInterval(loopSendIntervalID);
}
loopSendIntervalID = setInterval(onSendClick, loopSendFreq.value);
} else {
clearInterval(loopSendIntervalID);
loopSendIntervalID = -1;
}
});
watch(loopSendFreq, (value) => {
if (enableLoopSend.value && value) {
/* update interval with new value */
if (loopSendIntervalID !== -1) {
clearInterval(loopSendIntervalID);
}
loopSendIntervalID = setInterval(onSendClick, loopSendFreq.value);
}
})
@ -341,17 +412,26 @@ watch(loopSendFreq, (value) => {
/* patch scroll container does not update clear filter */
watch(() => store.filterChanged, (value) => {
if (value) {
scrollToBottom();
scrollToTop()
scrollToBottom();
store.filterChanged = false;
}
})
const handleScroll = (ev: Event) => {
if (store.forceToBottom) {
if (vuetifyVirtualScrollBarRef.value.scrollTop - lastScrollHeight < 0) {
store.forceToBottom = false;
}
}
lastScrollHeight = vuetifyVirtualScrollBarRef.value.scrollTop;
};
watch(() => store.forceToBottom, value => {
if (value) {
setTimeout(scrollToBottom, 0);
}
};
});
function clearSendInput() {
uartInputTextBox.value = ""
@ -364,6 +444,7 @@ function handleTextboxKeydown(ev: KeyboardEvent) {
}
function onSendClick() {
if (store.acceptIncomingData) {
if (isSendTextFormat.value) {
store.addString(uartInputTextBox.value, false, true);
} else if (!isHexStringValid.value) {
@ -371,6 +452,15 @@ function onSendClick() {
} else {
store.addHexString(uartInputTextBox.value, false, true);
}
} else {
if (isSendTextFormat.value) {
store.addString(uartInputTextBox.value, false, true, 1);
} else if (!isHexStringValid.value) {
uartInputTextBox.value = formatHexInput(uartInputTextBox.value);
} else {
store.addHexString(uartInputTextBox.value, false, true, 1);
}
}
}
</script>