feat(smart scroll, ANSI clear, ANSI refresh)
This commit is contained in:
parent
15c1143b25
commit
51783612bc
|
@ -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
|
||||
|
|
|
@ -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, ' ');
|
||||
}
|
||||
|
||||
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: "["
|
||||
+ zeroPad(t.getHours(), 2) + ":"
|
||||
+ zeroPad(t.getMinutes(), 2) + ":"
|
||||
+ zeroPad(t.getSeconds(), 2) + ":"
|
||||
+ zeroPad(t.getMilliseconds(), 3)
|
||||
+ "]",
|
||||
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) {
|
||||
// frameBreakReady = true;
|
||||
// }
|
||||
// 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,
|
||||
|
|
|
@ -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) => {
|
||||
console.log("uart", msg);
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,13 @@
|
|||
<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>
|
||||
|
@ -105,7 +121,41 @@
|
|||
<div class="flex">
|
||||
<p v-show="store.showHexdump"
|
||||
class="text-nowrap"
|
||||
:style="{ 'background-color': item.isRX ? store.RxHexdumpColor : store.TxHexdumpColor }"
|
||||
: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>
|
||||
<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>
|
||||
|
@ -125,18 +175,21 @@
|
|||
</el-tag>
|
||||
</el-link>
|
||||
|
||||
<div class="flex align-center">
|
||||
<el-checkbox v-model="enableLoopSend" class="font-mono font-bold max-h-5" size="small" border>
|
||||
循环发送(ms)
|
||||
</el-checkbox>
|
||||
<el-input-number
|
||||
v-model="loopSendFreq"
|
||||
class="h-5"
|
||||
size="small"
|
||||
:step="10"
|
||||
>
|
||||
</el-input-number>
|
||||
</div>
|
||||
<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)
|
||||
</el-checkbox>
|
||||
<el-input-number
|
||||
v-model="loopSendFreq"
|
||||
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) {
|
||||
clearInterval(loopSendIntervalID);
|
||||
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 */
|
||||
clearInterval(loopSendIntervalID);
|
||||
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,12 +444,22 @@ function handleTextboxKeydown(ev: KeyboardEvent) {
|
|||
}
|
||||
|
||||
function onSendClick() {
|
||||
if (isSendTextFormat.value) {
|
||||
store.addString(uartInputTextBox.value, false, true);
|
||||
} else if (!isHexStringValid.value) {
|
||||
uartInputTextBox.value = formatHexInput(uartInputTextBox.value);
|
||||
if (store.acceptIncomingData) {
|
||||
if (isSendTextFormat.value) {
|
||||
store.addString(uartInputTextBox.value, false, true);
|
||||
} else if (!isHexStringValid.value) {
|
||||
uartInputTextBox.value = formatHexInput(uartInputTextBox.value);
|
||||
} else {
|
||||
store.addHexString(uartInputTextBox.value, false, true);
|
||||
}
|
||||
} else {
|
||||
store.addHexString(uartInputTextBox.value, false, true);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue