Skip to content

Commit d3db6b2

Browse files
committed
feat(web): highlight topic input on validation errors
Add visual feedback when topic validation fails during message publish. The topic input now shows a red border highlight when: - Topic is empty and required - Topic contains invalid characters (+ or #) The highlight clears on focus or blur to provide a clear user interaction.
1 parent 8b3f3ce commit d3db6b2

2 files changed

Lines changed: 94 additions & 31 deletions

File tree

web/src/components/MsgPublish.vue

Lines changed: 86 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -113,34 +113,40 @@
113113
</el-badge>
114114
</el-tooltip>
115115
</div>
116-
<div class="topic-input-container">
117-
<el-input class="publish-topic-input" placeholder="Topic" v-model="msgRecord.topic" @focus="handleInputFoucs">
116+
<div :class="['topic-input-container', topicRequired ? 'required' : '', topicInvalid ? 'invalid' : '']">
117+
<el-input
118+
class="publish-topic-input"
119+
placeholder="Topic"
120+
v-model="msgRecord.topic"
121+
@focus="handleInputFoucs"
122+
@blur="handleInputBlur"
123+
>
118124
</el-input>
119-
</div>
120-
<el-select
121-
class="header-select"
122-
popper-class="header-select--popper"
123-
v-model="headerValue"
124-
placeholder=""
125-
size="mini"
126-
@change="handleHeaderChange"
127-
>
128-
<el-option
129-
class="header-option"
130-
v-for="item in headersHistory"
131-
:key="item.id"
132-
:label="item.label"
133-
:value="item"
125+
<el-select
126+
class="header-select"
127+
popper-class="header-select--popper"
128+
v-model="headerValue"
129+
placeholder=""
130+
size="mini"
131+
@change="handleHeaderChange"
134132
>
135-
<span style="float: left; width: 160px; overflow: hidden; text-overflow: ellipsis" :title="item.topic">{{
136-
item.topic
137-
}}</span>
138-
<span style="color: #8492a6; font-size: 12px; margin-left: 4px">QoS:{{ item.qos }}</span>
139-
<span style="float: right; color: #8492a6; font-size: 13px; margin-left: 4px">
140-
retain:{{ item.retain ? '1' : '0' }}
141-
</span>
142-
</el-option>
143-
</el-select>
133+
<el-option
134+
class="header-option"
135+
v-for="item in headersHistory"
136+
:key="item.id"
137+
:label="item.label"
138+
:value="item"
139+
>
140+
<span style="float: left; width: 160px; overflow: hidden; text-overflow: ellipsis" :title="item.topic">{{
141+
item.topic
142+
}}</span>
143+
<span style="color: #8492a6; font-size: 12px; margin-left: 4px">QoS:{{ item.qos }}</span>
144+
<span style="float: right; color: #8492a6; font-size: 13px; margin-left: 4px">
145+
retain:{{ item.retain ? '1' : '0' }}
146+
</span>
147+
</el-option>
148+
</el-select>
149+
</div>
144150
</div>
145151
<div class="editor-container">
146152
<div
@@ -231,6 +237,9 @@ export default class MsgPublish extends Vue {
231237
232238
private saveMetaLoading = false
233239
240+
private topicRequired = false
241+
private topicInvalid = false
242+
234243
private getHasMqtt5PropState() {
235244
return (
236245
Object.entries(this.MQTT5PropsForm).filter(([_, v]) => v !== null && v !== undefined && v !== false).length > 0
@@ -417,10 +426,33 @@ export default class MsgPublish extends Vue {
417426
}
418427
}
419428
429+
public setTopicRequired(isRequired = true) {
430+
this.topicRequired = isRequired
431+
}
432+
433+
public setTopicInvalid(isInvalid = true) {
434+
this.topicInvalid = isInvalid
435+
}
436+
420437
private handleInputFoucs() {
438+
if (this.topicRequired) {
439+
this.topicRequired = false
440+
}
441+
if (this.topicInvalid) {
442+
this.topicInvalid = false
443+
}
421444
this.$emit('foucs')
422445
}
423446
447+
private handleInputBlur() {
448+
if (this.topicRequired) {
449+
this.topicRequired = false
450+
}
451+
if (this.topicInvalid) {
452+
this.topicInvalid = false
453+
}
454+
}
455+
424456
private handleLayout() {
425457
const editorRef: EditorRef = this.$refs.payloadEditor as EditorRef
426458
editorRef.editorLayout()
@@ -505,19 +537,42 @@ export default class MsgPublish extends Vue {
505537
}
506538
}
507539
}
540+
.topic-input-container {
541+
position: relative;
542+
display: flex;
543+
flex-wrap: nowrap;
544+
align-items: center;
545+
&.required,
546+
&.invalid {
547+
.el-input.publish-topic-input {
548+
.el-input__inner {
549+
border-right: none !important;
550+
}
551+
}
552+
.el-select {
553+
.el-input__inner {
554+
border-left: none !important;
555+
}
556+
}
557+
.el-input__inner {
558+
border: 1px solid var(--color-minor-red) !important;
559+
}
560+
}
561+
}
508562
.publish-topic-input.el-input {
509-
width: calc(100% - 20px);
510-
vertical-align: top;
511-
display: inline-block;
563+
flex: 1 1 0;
564+
min-width: 0;
565+
width: auto;
566+
display: block;
512567
@include topic-input__inner;
513568
.el-input__inner {
514569
padding: 0px 16px;
515570
}
516571
}
517572
.header-select.el-select {
518-
vertical-align: top;
573+
flex: 0 0 20px;
519574
width: 20px;
520-
display: inline-block;
575+
display: block;
521576
.el-input {
522577
@include topic-input__inner;
523578
&.is-focus {

web/src/views/connections/ConnectionsDetail.vue

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -888,11 +888,19 @@ export default class ConnectionsDetail extends Vue {
888888
const { id, topic, qos, payload, retain, properties } = message
889889
890890
if (!topic && !properties?.topicAlias) {
891+
const msgPublish = this.$refs.msgPublish as MsgPublish
892+
if (msgPublish) {
893+
msgPublish.setTopicRequired()
894+
}
891895
this.$message.warning(this.$tc('connections.topicRequired'))
892896
return false
893897
}
894898
895899
if (topic && (topic.includes('+') || topic.includes('#'))) {
900+
const msgPublish = this.$refs.msgPublish as MsgPublish
901+
if (msgPublish) {
902+
msgPublish.setTopicInvalid()
903+
}
896904
this.$message.warning(this.$tc('connections.topicCannotContain'))
897905
return false
898906
}

0 commit comments

Comments
 (0)