Skip to content
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
188 changes: 188 additions & 0 deletions src/Frontend/src/components/MaximizableCodeEditor.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from "vue";
import CodeEditor from "@/components/CodeEditor.vue";
import DiffMaximizeIcon from "@/assets/diff-maximize.svg";
import DiffCloseIcon from "@/assets/diff-close.svg";
Comment on lines +4 to +5
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should try to use fontawesome icons if available

import { Extension } from "@codemirror/state";
import { CodeLanguage } from "@/components/codeEditorTypes";

const modelValue = defineModel<string>({ required: true });

withDefaults(
defineProps<{
language?: CodeLanguage;
readOnly?: boolean;
showGutter?: boolean;
ariaLabel?: string;
extensions?: Extension[];
modalTitle?: string;
}>(),
{
readOnly: false,
showGutter: false,
extensions: () => [],
modalTitle: "Code View",
}
);

// Component state for maximize functionality
const showMaximizeModal = ref(false);
const showMaximizeButton = ref(false);

// Handle maximize functionality
const toggleMaximizeModal = () => {
showMaximizeModal.value = !showMaximizeModal.value;
};

// Handle mouse enter/leave for showing maximize button
const onEditorMouseEnter = () => {
showMaximizeButton.value = true;
};

const onEditorMouseLeave = () => {
showMaximizeButton.value = false;
};

// Handle ESC key to close modal
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === "Escape" && showMaximizeModal.value) {
showMaximizeModal.value = false;
}
};

// Setup keyboard events for maximize modal
onMounted(() => {
window.addEventListener("keydown", handleKeyDown);
});

// Clean up event listeners when component is destroyed
onBeforeUnmount(() => {
window.removeEventListener("keydown", handleKeyDown);
});
</script>

<template>
<div class="code-editor-wrapper" @mouseenter="onEditorMouseEnter" @mouseleave="onEditorMouseLeave">
<!-- Regular CodeEditor -->
<div class="editor-container">
<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">
<template #toolbarLeft>
<slot name="toolbarLeft"></slot>
</template>
<template #toolbarRight>
<slot name="toolbarRight">
<!-- Maximize Button (shown on hover) -->
<button v-if="showMaximizeButton" @click="toggleMaximizeModal" class="maximize-button" v-tippy="`Maximize view`">
<img :src="DiffMaximizeIcon" alt="Maximize" width="14" height="14" />
</button>
</slot>
</template>
</CodeEditor>
</div>

<!-- Maximize modal for CodeEditor -->
<div v-if="showMaximizeModal" class="maximize-modal">
<div class="maximize-modal-content">
<div class="maximize-modal-toolbar">
<span class="maximize-modal-title">{{ modalTitle }}</span>
<button @click="toggleMaximizeModal" class="maximize-modal-close" v-tippy="`Close`">
<img :src="DiffCloseIcon" alt="Close" width="16" height="16" />
</button>
</div>
<div class="maximize-modal-body">
<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="[]" />
</div>
</div>
</div>
</div>
</template>

<style scoped>
.code-editor-wrapper {
position: relative;
width: 100%;
}

.maximize-button {
position: absolute;
right: 0.375rem;
top: 0.375rem;
z-index: 10;
background-color: rgba(255, 255, 255, 0.7);
border: 1px solid #ddd;
border-radius: 3px;
padding: 0.25rem;
cursor: pointer;
opacity: 0.6;
transition: opacity 0.2s ease;
}

.maximize-button:hover {
opacity: 1;
}

.maximize-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
display: flex;
justify-content: center;
align-items: center;
}

.maximize-modal-content {
background-color: white;
width: 95vw;
height: 90vh;
border-radius: 4px;
overflow: hidden;
display: flex;
flex-direction: column;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}

.maximize-modal-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.625rem 0.9375rem;
background-color: #f8f8f8;
border-bottom: 1px solid #ddd;
}

.maximize-modal-title {
font-weight: bold;
font-size: 1rem;
}

.maximize-modal-close {
background: none;
border: none;
cursor: pointer;
padding: 0.3125rem;
display: flex;
align-items: center;
justify-content: center;
}

.maximize-modal-body {
flex: 1;
overflow: auto;
padding: 0;
}

/* Ensure the CodeEditor wrapper fills the modal body */
.maximize-modal-body :deep(.wrapper) {
height: 100%;
border-radius: 0;
}

.maximize-modal-body :deep(.cm-editor),
.maximize-modal-body :deep(.cm-scroller) {
height: 100%;
}
</style>
6 changes: 3 additions & 3 deletions src/Frontend/src/components/messages2/DiffViewer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,8 @@ onBeforeUnmount(() => {

.maximize-modal-content {
background-color: white;
width: calc(100% - 40px);
height: calc(100% - 40px);
width: 95vw;
height: 90vh;
border-radius: 4px;
overflow: hidden;
display: flex;
Expand All @@ -186,7 +186,7 @@ onBeforeUnmount(() => {

.maximize-modal-title {
font-weight: bold;
font-size: 16px;
font-size: 1rem;
}

.maximize-modal-close {
Expand Down
6 changes: 5 additions & 1 deletion src/Frontend/src/components/messages2/SagaDiagram.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,12 +257,16 @@ function rendercomponent({ initialState = {} }: { initialState?: { MessageStore?
CodeEditor: true,
CopyToClipboard: true,
},
directives: {
// Add stub for tippy directive
tippy: () => {},
},
},
});

const dslAPI: componentDSL = {
action1: () => {
// Add actions here;dl;;lksd;lksd;lkdmdslm,.mc,.
// Add actions here;
},
assert: {
NoSagaDataAvailableMessageIsShownWithMessage(message: RegExp) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,43 @@
<script setup lang="ts">
import { SagaMessageDataItem, useSagaDiagramStore } from "@/stores/SagaDiagramStore";
import { SagaMessageData, useSagaDiagramStore } from "@/stores/SagaDiagramStore";
import { storeToRefs } from "pinia";
import LoadingSpinner from "@/components/LoadingSpinner.vue";
import MaximizableCodeEditor from "@/components/MaximizableCodeEditor.vue";
import { computed } from "vue";
import parseContentType from "@/composables/contentTypeParser";

defineProps<{
messageData: SagaMessageDataItem[];
const props = defineProps<{
messageData: SagaMessageData;
maximizedTitle?: string;
}>();

const modalTitle = computed(() => {
return props.maximizedTitle ? `Message Data - ${props.maximizedTitle}` : "Message Data";
});

const sagaDiagramStore = useSagaDiagramStore();
const { messageDataLoading } = storeToRefs(sagaDiagramStore);

const contentType = computed(() => parseContentType(props.messageData.body.data.content_type));

const body = computed(() => props.messageData.body.data.value || "");
</script>

<template>
<div v-if="messageDataLoading" class="message-data-loading">
<LoadingSpinner />
</div>
<div v-else-if="!messageDataLoading && messageData.length === 0" class="message-data-box">
<div v-else-if="messageData.body.failed_to_load" class="message-data-box message-data-box-error">
<span class="message-data-box-text--error">An error occurred while retrieving the message data</span>
</div>
<div v-else-if="!messageDataLoading && (!messageData.body.data.value || messageData.body.not_found)" class="message-data-box">
<span class="message-data-box-text--empty">Empty</span>
</div>
<div v-else-if="messageData.length > 0" v-for="(item, index) in messageData" :key="index" class="message-data-box">
<b class="message-data-box-text">{{ item.key }}</b>
<span class="message-data-box-text">=</span>
<span class="message-data-box-text--ellipsis" :title="item.value">{{ item.value }}</span>
<div v-else-if="contentType.isSupported" class="message-data-box message-data-box-content">
<MaximizableCodeEditor :model-value="body" :language="contentType.language" :readOnly="true" :showGutter="false" :modalTitle="modalTitle" />
</div>
<div v-else class="message-data-box message-data-box-error">
<span class="message-data-box-text--unsupported">Message body cannot be displayed because content type "{{ messageData.body.data.content_type }}" is not supported.</span>
</div>
</template>

Expand All @@ -30,6 +46,10 @@ const { messageDataLoading } = storeToRefs(sagaDiagramStore);
display: flex;
}

.message-data-box-content {
display: block;
}

.message-data-box-text {
display: inline-block;
margin-right: 0.25rem;
Expand All @@ -48,11 +68,53 @@ const { messageDataLoading } = storeToRefs(sagaDiagramStore);
display: inline-block;
width: 100%;
text-align: center;
color: #666;
font-style: italic;
}

.message-data-box-text--error {
display: inline-block;
width: 100%;
text-align: center;
color: #a94442;
font-style: italic;
}

.message-data-box-text--unsupported {
display: inline-block;
width: 100%;
text-align: center;
color: #8a6d3b;
font-style: italic;
}

.message-data-loading {
display: flex;
justify-content: center;
align-items: center;
}

.message-data-box-error {
padding: 1rem;
justify-content: center;
}
.message-data-box-content :deep(.wrapper.maximazable-code-editor--inline-instance) {
border: none;
border-radius: 0;
margin-top: 0;
font-size: 0.75rem;
}

.message-data-box-content :deep(.wrapper.maximazable-code-editor--inline-instance .toolbar) {
border: none;
border-radius: 0;
background-color: transparent;
padding: 0;
margin-bottom: 0;
}

.message-data-box-content :deep(.wrapper.maximazable-code-editor--inline-instance .cm-editor) {
/* Override any borders from the default theme */
border: none;
}
</style>
Loading