Skip to content

Commit da8f9cc

Browse files
authored
Display saga messages body using CodeEditor (#2402)
1 parent d71928a commit da8f9cc

File tree

11 files changed

+384
-151
lines changed

11 files changed

+384
-151
lines changed
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
<script setup lang="ts">
2+
import { ref, onMounted, onBeforeUnmount } from "vue";
3+
import CodeEditor from "@/components/CodeEditor.vue";
4+
import DiffMaximizeIcon from "@/assets/diff-maximize.svg";
5+
import DiffCloseIcon from "@/assets/diff-close.svg";
6+
import { Extension } from "@codemirror/state";
7+
import { CodeLanguage } from "@/components/codeEditorTypes";
8+
9+
const modelValue = defineModel<string>({ required: true });
10+
11+
withDefaults(
12+
defineProps<{
13+
language?: CodeLanguage;
14+
readOnly?: boolean;
15+
showGutter?: boolean;
16+
ariaLabel?: string;
17+
extensions?: Extension[];
18+
modalTitle?: string;
19+
}>(),
20+
{
21+
readOnly: false,
22+
showGutter: false,
23+
extensions: () => [],
24+
modalTitle: "Code View",
25+
}
26+
);
27+
28+
// Component state for maximize functionality
29+
const showMaximizeModal = ref(false);
30+
const showMaximizeButton = ref(false);
31+
32+
// Handle maximize functionality
33+
const toggleMaximizeModal = () => {
34+
showMaximizeModal.value = !showMaximizeModal.value;
35+
};
36+
37+
// Handle mouse enter/leave for showing maximize button
38+
const onEditorMouseEnter = () => {
39+
showMaximizeButton.value = true;
40+
};
41+
42+
const onEditorMouseLeave = () => {
43+
showMaximizeButton.value = false;
44+
};
45+
46+
// Handle ESC key to close modal
47+
const handleKeyDown = (event: KeyboardEvent) => {
48+
if (event.key === "Escape" && showMaximizeModal.value) {
49+
showMaximizeModal.value = false;
50+
}
51+
};
52+
53+
// Setup keyboard events for maximize modal
54+
onMounted(() => {
55+
window.addEventListener("keydown", handleKeyDown);
56+
});
57+
58+
// Clean up event listeners when component is destroyed
59+
onBeforeUnmount(() => {
60+
window.removeEventListener("keydown", handleKeyDown);
61+
});
62+
</script>
63+
64+
<template>
65+
<div class="code-editor-wrapper" @mouseenter="onEditorMouseEnter" @mouseleave="onEditorMouseLeave">
66+
<!-- Regular CodeEditor -->
67+
<div class="editor-container">
68+
<CodeEditor class="maximazable-code-editor--inline-instance" v-model="modelValue" :language="language" :read-only="readOnly" :show-gutter="showGutter" :show-copy-to-clipboard="false" :aria-label="ariaLabel" :extensions="extensions">
69+
<template #toolbarLeft>
70+
<slot name="toolbarLeft"></slot>
71+
</template>
72+
<template #toolbarRight>
73+
<slot name="toolbarRight">
74+
<!-- Maximize Button (shown on hover) -->
75+
<button v-if="showMaximizeButton" @click="toggleMaximizeModal" class="maximize-button" v-tippy="`Maximize view`">
76+
<img :src="DiffMaximizeIcon" alt="Maximize" width="14" height="14" />
77+
</button>
78+
</slot>
79+
</template>
80+
</CodeEditor>
81+
</div>
82+
83+
<!-- Maximize modal for CodeEditor -->
84+
<div v-if="showMaximizeModal" class="maximize-modal">
85+
<div class="maximize-modal-content">
86+
<div class="maximize-modal-toolbar">
87+
<span class="maximize-modal-title">{{ modalTitle }}</span>
88+
<button @click="toggleMaximizeModal" class="maximize-modal-close" v-tippy="`Close`">
89+
<img :src="DiffCloseIcon" alt="Close" width="16" height="16" />
90+
</button>
91+
</div>
92+
<div class="maximize-modal-body">
93+
<CodeEditor class="maximazable-code-editor--pop-up-instance" v-model="modelValue" :language="language" :read-only="readOnly" :show-copy-to-clipboard="true" :show-gutter="true" :aria-label="ariaLabel" :extensions="[]" />
94+
</div>
95+
</div>
96+
</div>
97+
</div>
98+
</template>
99+
100+
<style scoped>
101+
.code-editor-wrapper {
102+
position: relative;
103+
width: 100%;
104+
}
105+
106+
.maximize-button {
107+
position: absolute;
108+
right: 0.375rem;
109+
top: 0.375rem;
110+
z-index: 10;
111+
background-color: rgba(255, 255, 255, 0.7);
112+
border: 1px solid #ddd;
113+
border-radius: 3px;
114+
padding: 0.25rem;
115+
cursor: pointer;
116+
opacity: 0.6;
117+
transition: opacity 0.2s ease;
118+
}
119+
120+
.maximize-button:hover {
121+
opacity: 1;
122+
}
123+
124+
.maximize-modal {
125+
position: fixed;
126+
top: 0;
127+
left: 0;
128+
right: 0;
129+
bottom: 0;
130+
background-color: rgba(0, 0, 0, 0.5);
131+
z-index: 1000;
132+
display: flex;
133+
justify-content: center;
134+
align-items: center;
135+
}
136+
137+
.maximize-modal-content {
138+
background-color: white;
139+
width: 95vw;
140+
height: 90vh;
141+
border-radius: 4px;
142+
overflow: hidden;
143+
display: flex;
144+
flex-direction: column;
145+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
146+
}
147+
148+
.maximize-modal-toolbar {
149+
display: flex;
150+
justify-content: space-between;
151+
align-items: center;
152+
padding: 0.625rem 0.9375rem;
153+
background-color: #f8f8f8;
154+
border-bottom: 1px solid #ddd;
155+
}
156+
157+
.maximize-modal-title {
158+
font-weight: bold;
159+
font-size: 1rem;
160+
}
161+
162+
.maximize-modal-close {
163+
background: none;
164+
border: none;
165+
cursor: pointer;
166+
padding: 0.3125rem;
167+
display: flex;
168+
align-items: center;
169+
justify-content: center;
170+
}
171+
172+
.maximize-modal-body {
173+
flex: 1;
174+
overflow: auto;
175+
padding: 0;
176+
}
177+
178+
/* Ensure the CodeEditor wrapper fills the modal body */
179+
.maximize-modal-body :deep(.wrapper) {
180+
height: 100%;
181+
border-radius: 0;
182+
}
183+
184+
.maximize-modal-body :deep(.cm-editor),
185+
.maximize-modal-body :deep(.cm-scroller) {
186+
height: 100%;
187+
}
188+
</style>

src/Frontend/src/components/messages2/DiffViewer.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,8 @@ onBeforeUnmount(() => {
166166
167167
.maximize-modal-content {
168168
background-color: white;
169-
width: calc(100% - 40px);
170-
height: calc(100% - 40px);
169+
width: 95vw;
170+
height: 90vh;
171171
border-radius: 4px;
172172
overflow: hidden;
173173
display: flex;
@@ -186,7 +186,7 @@ onBeforeUnmount(() => {
186186
187187
.maximize-modal-title {
188188
font-weight: bold;
189-
font-size: 16px;
189+
font-size: 1rem;
190190
}
191191
192192
.maximize-modal-close {

src/Frontend/src/components/messages2/SagaDiagram.spec.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,12 +257,16 @@ function rendercomponent({ initialState = {} }: { initialState?: { MessageStore?
257257
CodeEditor: true,
258258
CopyToClipboard: true,
259259
},
260+
directives: {
261+
// Add stub for tippy directive
262+
tippy: () => {},
263+
},
260264
},
261265
});
262266

263267
const dslAPI: componentDSL = {
264268
action1: () => {
265-
// Add actions here;dl;;lksd;lksd;lkdmdslm,.mc,.
269+
// Add actions here;
266270
},
267271
assert: {
268272
NoSagaDataAvailableMessageIsShownWithMessage(message: RegExp) {

src/Frontend/src/components/messages2/SagaDiagram/MessageDataBox.vue

Lines changed: 70 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,43 @@
11
<script setup lang="ts">
2-
import { SagaMessageDataItem, useSagaDiagramStore } from "@/stores/SagaDiagramStore";
2+
import { SagaMessageData, useSagaDiagramStore } from "@/stores/SagaDiagramStore";
33
import { storeToRefs } from "pinia";
44
import LoadingSpinner from "@/components/LoadingSpinner.vue";
5+
import MaximizableCodeEditor from "@/components/MaximizableCodeEditor.vue";
6+
import { computed } from "vue";
7+
import parseContentType from "@/composables/contentTypeParser";
58
6-
defineProps<{
7-
messageData: SagaMessageDataItem[];
9+
const props = defineProps<{
10+
messageData: SagaMessageData;
11+
maximizedTitle?: string;
812
}>();
913
14+
const modalTitle = computed(() => {
15+
return props.maximizedTitle ? `Message Data - ${props.maximizedTitle}` : "Message Data";
16+
});
17+
1018
const sagaDiagramStore = useSagaDiagramStore();
1119
const { messageDataLoading } = storeToRefs(sagaDiagramStore);
20+
21+
const contentType = computed(() => parseContentType(props.messageData.body.data.content_type));
22+
23+
const body = computed(() => props.messageData.body.data.value || "");
1224
</script>
1325

1426
<template>
1527
<div v-if="messageDataLoading" class="message-data-loading">
1628
<LoadingSpinner />
1729
</div>
18-
<div v-else-if="!messageDataLoading && messageData.length === 0" class="message-data-box">
30+
<div v-else-if="messageData.body.failed_to_load" class="message-data-box message-data-box-error">
31+
<span class="message-data-box-text--error">An error occurred while retrieving the message data</span>
32+
</div>
33+
<div v-else-if="!messageDataLoading && (!messageData.body.data.value || messageData.body.not_found)" class="message-data-box">
1934
<span class="message-data-box-text--empty">Empty</span>
2035
</div>
21-
<div v-else-if="messageData.length > 0" v-for="(item, index) in messageData" :key="index" class="message-data-box">
22-
<b class="message-data-box-text">{{ item.key }}</b>
23-
<span class="message-data-box-text">=</span>
24-
<span class="message-data-box-text--ellipsis" :title="item.value">{{ item.value }}</span>
36+
<div v-else-if="contentType.isSupported" class="message-data-box message-data-box-content">
37+
<MaximizableCodeEditor :model-value="body" :language="contentType.language" :readOnly="true" :showGutter="false" :modalTitle="modalTitle" />
38+
</div>
39+
<div v-else class="message-data-box message-data-box-error">
40+
<span class="message-data-box-text--unsupported">Message body cannot be displayed because content type "{{ messageData.body.data.content_type }}" is not supported.</span>
2541
</div>
2642
</template>
2743

@@ -30,6 +46,10 @@ const { messageDataLoading } = storeToRefs(sagaDiagramStore);
3046
display: flex;
3147
}
3248
49+
.message-data-box-content {
50+
display: block;
51+
}
52+
3353
.message-data-box-text {
3454
display: inline-block;
3555
margin-right: 0.25rem;
@@ -48,11 +68,53 @@ const { messageDataLoading } = storeToRefs(sagaDiagramStore);
4868
display: inline-block;
4969
width: 100%;
5070
text-align: center;
71+
color: #666;
72+
font-style: italic;
73+
}
74+
75+
.message-data-box-text--error {
76+
display: inline-block;
77+
width: 100%;
78+
text-align: center;
79+
color: #a94442;
80+
font-style: italic;
81+
}
82+
83+
.message-data-box-text--unsupported {
84+
display: inline-block;
85+
width: 100%;
86+
text-align: center;
87+
color: #8a6d3b;
88+
font-style: italic;
5189
}
5290
5391
.message-data-loading {
5492
display: flex;
5593
justify-content: center;
5694
align-items: center;
5795
}
96+
97+
.message-data-box-error {
98+
padding: 1rem;
99+
justify-content: center;
100+
}
101+
.message-data-box-content :deep(.wrapper.maximazable-code-editor--inline-instance) {
102+
border: none;
103+
border-radius: 0;
104+
margin-top: 0;
105+
font-size: 0.75rem;
106+
}
107+
108+
.message-data-box-content :deep(.wrapper.maximazable-code-editor--inline-instance .toolbar) {
109+
border: none;
110+
border-radius: 0;
111+
background-color: transparent;
112+
padding: 0;
113+
margin-bottom: 0;
114+
}
115+
116+
.message-data-box-content :deep(.wrapper.maximazable-code-editor--inline-instance .cm-editor) {
117+
/* Override any borders from the default theme */
118+
border: none;
119+
}
58120
</style>

0 commit comments

Comments
 (0)