mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-15 17:14:05 -08:00
2d6e406e21
## Summary Title self explanatory before: <img width="1198" alt="image" src="https://github.com/n8n-io/n8n/assets/16496553/26c29cad-b376-40fd-9881-343f5e722d58"> now: <img width="1211" alt="image" src="https://github.com/n8n-io/n8n/assets/16496553/9f2d4020-6bf8-481a-b69f-eddb57cfaaae"> ## Related tickets and issues [ado-1645-fix-center-loading-preview-loading-state ](https://linear.app/n8n/issue/ADO-1645/fix-center-loading-preview-loading-state) ## Review / Merge checklist - [ x] PR title and summary are descriptive. **Remember, the title automatically goes into the changelog. Use `(no-changelog)` otherwise.** ([conventions](https://github.com/n8n-io/n8n/blob/master/.github/pull_request_title_conventions.md))
262 lines
5.7 KiB
Vue
262 lines
5.7 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
|
|
ref="iframeRef"
|
|
:class="{
|
|
[$style.workflow]: !nodeViewDetailsOpened,
|
|
[$style.executionPreview]: mode === 'execution',
|
|
[$style.openNDV]: nodeViewDetailsOpened,
|
|
[$style.show]: showPreview,
|
|
}"
|
|
:src="`${rootStore.baseUrl}workflows/demo`"
|
|
@mouseenter="onMouseEnter"
|
|
@mouseleave="onMouseLeave"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { onMounted, onBeforeUnmount, ref, computed, watch } from 'vue';
|
|
import { useI18n } from '@/composables/useI18n';
|
|
import { useToast } from '@/composables/useToast';
|
|
import type { IWorkflowDb } from '@/Interface';
|
|
import { useRootStore } from '@/stores/n8nRoot.store';
|
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
|
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
loading?: boolean;
|
|
mode?: 'workflow' | 'execution';
|
|
workflow?: IWorkflowDb;
|
|
executionId?: string;
|
|
executionMode?: string;
|
|
loaderType?: 'image' | 'spinner';
|
|
canOpenNDV?: boolean;
|
|
hideNodeIssues?: boolean;
|
|
}>(),
|
|
{
|
|
loading: false,
|
|
mode: 'workflow',
|
|
loaderType: 'image',
|
|
canOpenNDV: true,
|
|
hideNodeIssues: false,
|
|
},
|
|
);
|
|
|
|
const emit = defineEmits<{
|
|
(event: 'close'): void;
|
|
}>();
|
|
|
|
const i18n = useI18n();
|
|
const toast = useToast();
|
|
const rootStore = useRootStore();
|
|
const workflowsStore = useWorkflowsStore();
|
|
|
|
const iframeRef = ref<HTMLIFrameElement | null>(null);
|
|
const nodeViewDetailsOpened = ref(false);
|
|
const ready = ref(false);
|
|
const insideIframe = ref(false);
|
|
const scrollX = ref(0);
|
|
const scrollY = ref(0);
|
|
|
|
const showPreview = computed(() => {
|
|
return (
|
|
!props.loading &&
|
|
((props.mode === 'workflow' && props.workflow) ||
|
|
(props.mode === 'execution' && props.executionId)) &&
|
|
ready.value
|
|
);
|
|
});
|
|
|
|
const loadWorkflow = () => {
|
|
try {
|
|
if (!props.workflow) {
|
|
throw new Error(i18n.baseText('workflowPreview.showError.missingWorkflow'));
|
|
}
|
|
if (!props.workflow.nodes || !Array.isArray(props.workflow.nodes)) {
|
|
throw new Error(i18n.baseText('workflowPreview.showError.arrayEmpty'));
|
|
}
|
|
iframeRef.value?.contentWindow?.postMessage?.(
|
|
JSON.stringify({
|
|
command: 'openWorkflow',
|
|
workflow: props.workflow,
|
|
canOpenNDV: props.canOpenNDV,
|
|
hideNodeIssues: props.hideNodeIssues,
|
|
}),
|
|
'*',
|
|
);
|
|
} catch (error) {
|
|
toast.showError(
|
|
error,
|
|
i18n.baseText('workflowPreview.showError.previewError.title'),
|
|
i18n.baseText('workflowPreview.showError.previewError.message'),
|
|
);
|
|
}
|
|
};
|
|
|
|
const loadExecution = () => {
|
|
try {
|
|
if (!props.executionId) {
|
|
throw new Error(i18n.baseText('workflowPreview.showError.missingExecution'));
|
|
}
|
|
iframeRef.value?.contentWindow?.postMessage?.(
|
|
JSON.stringify({
|
|
command: 'openExecution',
|
|
executionId: props.executionId,
|
|
executionMode: props.executionMode || '',
|
|
canOpenNDV: props.canOpenNDV,
|
|
}),
|
|
'*',
|
|
);
|
|
|
|
if (workflowsStore.activeWorkflowExecution) {
|
|
iframeRef.value?.contentWindow?.postMessage?.(
|
|
JSON.stringify({
|
|
command: 'setActiveExecution',
|
|
execution: workflowsStore.activeWorkflowExecution,
|
|
}),
|
|
'*',
|
|
);
|
|
}
|
|
} catch (error) {
|
|
toast.showError(
|
|
error,
|
|
i18n.baseText('workflowPreview.showError.previewError.title'),
|
|
i18n.baseText('workflowPreview.executionMode.showError.previewError.message'),
|
|
);
|
|
}
|
|
};
|
|
|
|
const onMouseEnter = () => {
|
|
insideIframe.value = true;
|
|
scrollX.value = window.scrollX;
|
|
scrollY.value = window.scrollY;
|
|
};
|
|
const onMouseLeave = () => {
|
|
insideIframe.value = false;
|
|
};
|
|
|
|
const receiveMessage = ({ data }: MessageEvent) => {
|
|
if (!data?.includes?.('"command"')) {
|
|
return;
|
|
}
|
|
try {
|
|
const json = JSON.parse(data);
|
|
if (json.command === 'n8nReady') {
|
|
ready.value = true;
|
|
} else if (json.command === 'openNDV') {
|
|
nodeViewDetailsOpened.value = true;
|
|
} else if (json.command === 'closeNDV') {
|
|
nodeViewDetailsOpened.value = false;
|
|
} else if (json.command === 'error') {
|
|
emit('close');
|
|
}
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
};
|
|
const onDocumentScroll = () => {
|
|
if (insideIframe.value) {
|
|
window.scrollTo(scrollX.value, scrollY.value);
|
|
}
|
|
};
|
|
|
|
onMounted(() => {
|
|
window.addEventListener('message', receiveMessage);
|
|
document.addEventListener('scroll', onDocumentScroll);
|
|
});
|
|
|
|
onBeforeUnmount(() => {
|
|
window.removeEventListener('message', receiveMessage);
|
|
document.removeEventListener('scroll', onDocumentScroll);
|
|
});
|
|
|
|
watch(
|
|
() => showPreview.value,
|
|
() => {
|
|
if (showPreview.value) {
|
|
if (props.mode === 'workflow') {
|
|
loadWorkflow();
|
|
} else if (props.mode === 'execution') {
|
|
loadExecution();
|
|
}
|
|
}
|
|
},
|
|
);
|
|
|
|
watch(
|
|
() => props.executionId,
|
|
() => {
|
|
if (props.mode === 'execution' && props.executionId) {
|
|
loadExecution();
|
|
}
|
|
},
|
|
);
|
|
|
|
watch(
|
|
() => props.workflow,
|
|
() => {
|
|
if (props.mode === 'workflow' && props.workflow) {
|
|
loadWorkflow();
|
|
}
|
|
},
|
|
);
|
|
</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%;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
height: 100%;
|
|
}
|
|
|
|
.executionPreview {
|
|
height: 100%;
|
|
}
|
|
</style>
|