Files
n8n-enterprise-unlocked/packages/editor-ui/src/components/WorkflowPreview.vue
Csaba Tuncsik b95fcd7323 refactor(editor): Turn showMessage mixin to composable (#6081)
* refactor(editor): move $getExecutionError from showMessages mixin to pushConnection (it is used there only)

* refactor(editor): resolve showMessage mixin methods

* fix(editor): use composable instead of mixin

* fix(editor): resolve conflicts

* fix(editor): replace clearAllStickyNotifications

* fix(editor): replace confirmMessage

* fix(editor): replace confirmMessage

* fix(editor): replace confirmMessage

* fix(editor): remove last confirmMessage usage

* fix(editor): remove $prompt usage

* fix(editor): remove $show methods

* fix(editor): lint fix

* fix(editor): lint fix

* fix(editor): fixes after review
2023-05-12 10:13:42 +02:00

239 lines
5.4 KiB
Vue

<template>
<div :class="$style.container">
<div v-if="loaderType === 'image' && !showPreview" :class="$style.imageLoader">
<n8n-loading :loading="!showPreview" :rows="1" variant="image" />
</div>
<div v-else-if="loaderType === 'spinner' && !showPreview" :class="$style.spinner">
<n8n-spinner type="dots" />
</div>
<iframe
:class="{
[$style.workflow]: !this.nodeViewDetailsOpened,
[$style.executionPreview]: mode === 'execution',
[$style.openNDV]: this.nodeViewDetailsOpened,
[$style.show]: this.showPreview,
}"
ref="preview_iframe"
:src="`${rootStore.baseUrl}workflows/demo`"
@mouseenter="onMouseEnter"
@mouseleave="onMouseLeave"
></iframe>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { useToast } from '@/composables';
import type { IWorkflowDb } from '@/Interface';
import { mapStores } from 'pinia';
import { useRootStore } from '@/stores/n8nRoot.store';
export default defineComponent({
name: 'WorkflowPreview',
props: {
loading: {
type: Boolean,
default: false,
},
mode: {
type: String,
default: 'workflow',
validator: (value: string): boolean => ['workflow', 'execution'].includes(value),
},
workflow: {
type: Object as () => IWorkflowDb,
required: false,
},
executionId: {
type: String,
required: false,
},
executionMode: {
type: String,
required: false,
},
loaderType: {
type: String,
default: 'image',
validator: (value: string): boolean => ['image', 'spinner'].includes(value),
},
},
setup() {
return {
...useToast(),
};
},
data() {
return {
nodeViewDetailsOpened: false,
ready: false,
insideIframe: false,
scrollX: 0,
scrollY: 0,
};
},
computed: {
...mapStores(useRootStore),
showPreview(): boolean {
return (
!this.loading &&
((this.mode === 'workflow' && !!this.workflow) ||
(this.mode === 'execution' && !!this.executionId)) &&
this.ready
);
},
},
methods: {
onMouseEnter() {
this.insideIframe = true;
this.scrollX = window.scrollX;
this.scrollY = window.scrollY;
},
onMouseLeave() {
this.insideIframe = false;
},
loadWorkflow() {
try {
if (!this.workflow) {
throw new Error(this.$locale.baseText('workflowPreview.showError.missingWorkflow'));
}
if (!this.workflow.nodes || !Array.isArray(this.workflow.nodes)) {
throw new Error(this.$locale.baseText('workflowPreview.showError.arrayEmpty'));
}
const iframeRef = this.$refs.preview_iframe as HTMLIFrameElement | undefined;
if (iframeRef?.contentWindow) {
iframeRef.contentWindow.postMessage(
JSON.stringify({
command: 'openWorkflow',
workflow: this.workflow,
}),
'*',
);
}
} catch (error) {
this.showError(
error,
this.$locale.baseText('workflowPreview.showError.previewError.title'),
this.$locale.baseText('workflowPreview.showError.previewError.message'),
);
}
},
loadExecution() {
try {
if (!this.executionId) {
throw new Error(this.$locale.baseText('workflowPreview.showError.missingExecution'));
}
const iframeRef = this.$refs.preview_iframe as HTMLIFrameElement | undefined;
if (iframeRef?.contentWindow) {
iframeRef.contentWindow.postMessage(
JSON.stringify({
command: 'openExecution',
executionId: this.executionId,
executionMode: this.executionMode || '',
}),
'*',
);
}
} catch (error) {
this.showError(
error,
this.$locale.baseText('workflowPreview.showError.previewError.title'),
this.$locale.baseText('workflowPreview.executionMode.showError.previewError.message'),
);
}
},
receiveMessage({ data }: MessageEvent) {
try {
const json = JSON.parse(data);
if (json.command === 'n8nReady') {
this.ready = true;
} else if (json.command === 'openNDV') {
this.nodeViewDetailsOpened = true;
} else if (json.command === 'closeNDV') {
this.nodeViewDetailsOpened = false;
} else if (json.command === 'error') {
this.$emit('close');
}
} catch (e) {}
},
onDocumentScroll() {
if (this.insideIframe) {
window.scrollTo(this.scrollX, this.scrollY);
}
},
},
watch: {
showPreview(show) {
if (show) {
if (this.mode === 'workflow') {
this.loadWorkflow();
} else if (this.mode === 'execution') {
this.loadExecution();
}
}
},
executionId(value) {
if (this.mode === 'execution' && this.executionId) {
this.loadExecution();
}
},
},
mounted() {
window.addEventListener('message', this.receiveMessage);
document.addEventListener('scroll', this.onDocumentScroll);
},
beforeDestroy() {
window.removeEventListener('message', this.receiveMessage);
document.removeEventListener('scroll', this.onDocumentScroll);
},
});
</script>
<style lang="scss" module>
.container {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
}
.workflow {
// firefox bug requires loading iframe as such
visibility: hidden;
height: 0;
width: 0;
}
.show {
visibility: visible;
height: 100%;
width: 100%;
}
.openNDV {
position: fixed;
top: 0;
left: 0;
height: 100%;
width: 100%;
z-index: 9999999;
}
.spinner {
color: var(--color-primary);
position: absolute;
top: 50% !important;
-ms-transform: translateY(-50%);
transform: translateY(-50%);
}
.imageLoader {
width: 100%;
}
.executionPreview {
height: 100%;
}
</style>