Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(desktop): handle the large data on the messages #1685

Merged
merged 1 commit into from
Jun 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/components/ExportData.vue
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,8 @@ export default class ExportData extends Vue {
content = content.replace(/<([0-9]*)>/g, '<oneConnection>').replace(/<(\/[0-9]*)>/g, '</oneConnection>')
this.exportDiffFormatData(content, 'XML')
} catch (err) {
this.$message.error(err.toString())
const error = err as Error
this.$message.error(error.toString())
}
}
this.confirmLoading = true
Expand Down Expand Up @@ -256,7 +257,8 @@ export default class ExportData extends Vue {
content = CSVConvert(JSON.parse(content)).replace(/"(\d+\.(\d+)?0)"/g, '="$1"')
this.exportDiffFormatData(content, 'CSV')
} catch (err) {
this.$message.error(err.toString())
const error = err as Error
this.$message.error(error.toString())
}
}
this.confirmLoading = true
Expand Down
138 changes: 138 additions & 0 deletions src/components/FullMsgDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
<template>
<my-dialog
:title="$t('common.viewData')"
:visible.sync="showDialog"
class="full-msg-dialog"
width="600px"
:confirmBtnText="$t('common.saveToLocal')"
@confirm="saveDataToLocal"
@close="resetData"
>
<el-skeleton class="loading-full-msg" v-if="loadingMsg" :rows="6" animated />
<template v-else>
<div class="full-msg-size">
<span>
{{ $t('connections.payloadSize') }}: <strong>{{ calculateAndFormatTextSize(fullMsg) }}</strong>
</span>
<span>
{{ $t('common.msgType') }}: <strong>{{ msgType }}</strong>
</span>
</div>
<div class="editor-container full-msg-editor">
<Editor
:key="msgId"
ref="fullMsgEditorRef"
id="full-msg-editor"
:lang="lang"
lineNumbers="on"
wordWrap="on"
v-model="fullMsg"
disabled
/>
</div>
</template>
</my-dialog>
</template>

<script lang="ts">
import { Component, Vue, Prop, Watch } from 'vue-property-decorator'
import useServices from '@/database/useServices'
import MyDialog from './MyDialog.vue'
import Editor from './Editor.vue'
import { ipcRenderer } from 'electron'
import { calculateTextSize } from '@/utils/data'

@Component({
components: {
MyDialog,
Editor,
},
})
export default class FullMsgDialog extends Vue {
@Prop({ default: false }) public visible!: boolean
@Prop({ required: true }) public msgId!: string
@Prop({ default: '' }) public msgType!: string

private showDialog: boolean = this.visible

@Watch('visible')
private onVisibleChanged(val: boolean) {
this.showDialog = val
}

private getFormatOrLang(type: string) {
if (['JSON', 'CBOR'].includes(this.msgType)) {
return type === 'lang' ? 'json' : 'json'
}
return type === 'lang' ? 'plaintext' : 'txt'
}

get lang() {
return this.getFormatOrLang('lang')
}

get format() {
return this.getFormatOrLang('format')
}

private fullMsg = ''
private filename = 'payload'
private loadingMsg = false

private async loadMsg() {
this.loadingMsg = true
const { messageService } = useServices()
const message = await messageService.getOne(this.msgId)
this.fullMsg = message?.payload || ''
this.loadingMsg = false
}

private async saveDataToLocal() {
ipcRenderer.send('exportData', this.filename, this.fullMsg, this.format)
ipcRenderer.on('saved', () => {
this.$message.success(`${this.filename}.${this.format} ${this.$t('common.saveSuccess')}`)
this.resetData()
})
}

private resetData() {
ipcRenderer.removeAllListeners('saved')
this.fullMsg = ''
this.showDialog = false
this.$emit('update:visible', false)
}

private calculateAndFormatTextSize(text: string, decimals = 2) {
return calculateTextSize(text)
}

private mounted() {
this.loadMsg()
}
}
</script>

<style lang="scss">
.full-msg-dialog {
.el-dialog__body {
padding-top: 24px;
padding-bottom: 0px;
}
.loading-full-msg {
height: 400px;
}
.full-msg-size {
color: var(--color-text-default);
margin-bottom: 12px;
display: flex;
gap: 12px;
}
.editor-container.full-msg-editor {
height: 400px;
background: var(--color-bg-normal);
border: 1px solid var(--color-border-default);
padding: 10px 1px 1px 1px;
border-radius: 4px;
}
}
</style>
8 changes: 7 additions & 1 deletion src/components/MessageList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@
<span v-show="showBeforeLoadingIcon" class="loading-icon"><i class="el-icon-loading"></i></span>
<div v-if="showMessages.length" class="scroller">
<template v-for="item in showMessages">
<MsgLeftItem v-if="!item.out" :key="item.id" v-bind="item" @showmenu="handleShowContextMenu(arguments, item)" />
<MsgLeftItem
v-if="!item.out"
:key="item.id"
:msgId="item.id"
v-bind="item"
@showmenu="handleShowContextMenu(arguments, item)"
/>
<MsgRightItem v-else :key="item.id" v-bind="item" @showmenu="handleShowContextMenu(arguments, item)" />
</template>
</div>
Expand Down
53 changes: 51 additions & 2 deletions src/components/MsgLeftItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -65,38 +65,63 @@
/>
</p>
</div>
<pre v-if="!hightlight">{{ payload }}</pre>
<pre v-else><code class="language-js" >{{ payload }}</code></pre>
<template v-if="isLargeMsg">
<div class="large-message { .el-button { margin-top: 12px;}}">
<pre>{{ payload.substr(0, showMaxLen) }}<span><i class="iconfont icon-more"></i></span></pre>
<el-tooltip
placement="bottom"
:effect="theme !== 'light' ? 'light' : 'dark'"
:open-delay="500"
:content="$t('connections.messageTooLargeToHide')"
>
<el-button type="info" plain size="mini" class="see-more-btn" @click="handleShowFullMsg()">{{
$t('common.seeMore')
}}</el-button>
</el-tooltip>
</div>
</template>
<template v-else>
<pre v-if="!hightlight">{{ payload }}</pre>
<pre v-else><code class="language-js" >{{ payload }}</code></pre>
</template>
</div>
<p class="left-time time">{{ createAt }}</p>
<FullMsgDialog v-if="showFullMsg" :visible.sync="showFullMsg" :msgId="msgId" :msgType="msgType" />
</div>
</template>

<script lang="ts">
import { Component, Vue, Prop } from 'vue-property-decorator'
import KeyValueEditor from './KeyValueEditor.vue'
import FullMsgDialog from './FullMsgDialog.vue'
import Prism from 'prismjs'
import { Getter } from 'vuex-class'
import { SHOW_MAX_LENGTH } from '@/utils/data'

@Component({
components: {
KeyValueEditor,
FullMsgDialog,
},
})
export default class MsgLeftItem extends Vue {
@Prop({ required: true }) public topic!: string
@Prop({ required: true }) public qos!: number
@Prop({ required: true }) public payload!: string
@Prop({ required: true }) public createAt!: string
@Prop({ required: true }) public msgId!: string
@Prop({ required: false }) public meta?: string
@Prop({ required: false, default: false }) public retain!: boolean
@Prop({ required: false, default: () => ({}) }) public properties!: PushPropertiesModel
@Prop({ required: false, default: '' }) public color!: string

@Getter('jsonHighlight') private jsonHighlight!: boolean
@Getter('currentTheme') private theme!: Theme

public hightlight: boolean = false

private showFullMsg: boolean = false

public customMenu(event: MouseEvent) {
this.$emit('showmenu', this.payload, event)
}
Expand Down Expand Up @@ -128,6 +153,14 @@ export default class MsgLeftItem extends Vue {
return this.meta ? JSON.parse(this.meta).msgError : null
}

get isLargeMsg() {
return this.meta ? JSON.parse(this.meta).isLargeData : false
}

get showMaxLen() {
return SHOW_MAX_LENGTH
}

private hightlightJSON() {
if (this.jsonHighlight === false) {
return
Expand All @@ -144,6 +177,10 @@ export default class MsgLeftItem extends Vue {
}
}

private handleShowFullMsg() {
this.showFullMsg = true
}

private mounted() {
this.hightlightJSON()
}
Expand Down Expand Up @@ -193,5 +230,17 @@ body.night {
border: 1px solid var(--color-border-left_metainfo) !important;
}
}
.large-message {
.el-button.see-more-btn {
margin-top: 6px;
background: transparent;
color: var(--color-text-left_info);
border: 1px solid var(--color-text-left_info);
&:hover {
background: transparent;
color: var(--color-text-default);
}
}
}
}
</style>
4 changes: 3 additions & 1 deletion src/components/MyDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
:loading="confirmLoading"
:disabled="btnDisabled"
@click="confirmClick"
>{{ $t('common.confirm') }}
>{{ confirmBtnText === '' ? $t('common.confirm') : confirmBtnText }}
</el-button>
</div>
</el-dialog>
Expand All @@ -37,6 +37,8 @@ export default class MyDialog extends Vue {
@Prop({ default: false }) public visible!: boolean
// Confirm button loading
@Prop({ default: false }) public confirmLoading!: boolean
// Dialog confirm button text
@Prop({ default: '' }) public confirmBtnText!: string
// Confirm button disabled status
@Prop({ default: false }) public btnDisabled!: boolean
// Dialog margin-top value
Expand Down
5 changes: 5 additions & 0 deletions src/database/services/MessageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,4 +190,9 @@ export default class MessageService {
public async cleanAll(): Promise<void> {
await this.messageRepository.clear()
}

public async getOne(id: string): Promise<MessageModel | undefined> {
const res = await this.messageRepository.findOne(id)
return res ? MessageService.entityToModel(res) : undefined
}
}
28 changes: 28 additions & 0 deletions src/lang/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -594,4 +594,32 @@ export default {
ja: 'Webアプリ',
hu: 'Web alkalmazások',
},
seeMore: {
zh: '查看更多',
en: 'See More',
tr: 'Daha Fazla',
ja: 'もっと見る',
hu: 'Továbbiak',
},
viewData: {
zh: '查看数据',
en: 'View Data',
tr: 'Veriyi Görüntüle',
ja: 'データを表示',
hu: 'Adatok meg',
},
saveToLocal: {
zh: '保存到本地',
en: 'Save to Local',
tr: 'Yerel olarak kaydet',
ja: 'ローカルに保存',
hu: 'Mentés helyi',
},
msgType: {
zh: '消息类型',
en: 'Message Type',
tr: 'Mesaj Türü',
ja: 'メッセージタイプ',
hu: 'Üzenet típus',
},
}
21 changes: 21 additions & 0 deletions src/lang/connections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -894,4 +894,25 @@ export default {
ja: '接続リストを表示',
hu: 'Kapcsolatok mutatása',
},
messageTooLargeToHide: {
zh: '消息内容过大,已隐藏,点击查看完整内容',
en: 'Message content is too large, hidden, click to view full content',
tr: 'Mesaj içeriği çok büyük, gizlendi, tam içeriği görüntülemek için tıklayın',
ja: 'メッセージ内容が大きすぎて非表示になりました。クリックして完全な内容を表示します',
hu: 'Az üzenet tartalma túl nagy, elrejtve, kattintson a teljes tartalom megtekintéséhez',
},
showFullMsg: {
zh: '显示完整消息',
en: 'Show Full Message',
tr: 'Tam Mesajı Göster',
ja: '完全なメッセージを表示',
hu: 'Teljes üzenet megjelenítése',
},
payloadSize: {
zh: '消息大小',
en: 'Payload Size',
tr: 'Yük Boyutu',
ja: 'ペイロードサイズ',
hu: 'A hasznos teher mérete',
},
}
24 changes: 24 additions & 0 deletions src/utils/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export const LARGE_DATA_THRESHOLD = 524288 // 512KB

export const SHOW_MAX_LENGTH = 100

export function isLargeData(message: string): boolean {
return message.length > LARGE_DATA_THRESHOLD
}

export function calculateTextSize(text: string, decimals = 2) {
const blob = new Blob([text], { type: 'text/plain' })
const bytes = blob.size

if (bytes === 0) return '0 Bytes'

const k = 1024
const dm = decimals < 0 ? 0 : decimals
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']

const i = Math.floor(Math.log(bytes) / Math.log(k))

return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]
}

export default {}
Loading
Loading