mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-14 16:44:07 -08:00
172 lines
4.5 KiB
Vue
172 lines
4.5 KiB
Vue
<script setup lang="ts">
|
|
import { ref, computed, watch, onMounted, onBeforeUnmount, nextTick } from 'vue';
|
|
import { useRoute } from 'vue-router';
|
|
import LoadingView from '@/views/LoadingView.vue';
|
|
import BannerStack from '@/components/banners/BannerStack.vue';
|
|
import AskAssistantChat from '@/components/AskAssistant/AskAssistantChat.vue';
|
|
import Modals from '@/components/Modals.vue';
|
|
import Telemetry from '@/components/Telemetry.vue';
|
|
import AskAssistantFloatingButton from '@/components/AskAssistant/AskAssistantFloatingButton.vue';
|
|
import { loadLanguage } from '@/plugins/i18n';
|
|
import { APP_MODALS_ELEMENT_ID, HIRING_BANNER, VIEWS } from '@/constants';
|
|
import { useRootStore } from '@/stores/root.store';
|
|
import { useAssistantStore } from '@/stores/assistant.store';
|
|
import { useUIStore } from '@/stores/ui.store';
|
|
import { useUsersStore } from '@/stores/users.store';
|
|
import { useSettingsStore } from '@/stores/settings.store';
|
|
import { useHistoryHelper } from '@/composables/useHistoryHelper';
|
|
import { useStyles } from './composables/useStyles';
|
|
|
|
const route = useRoute();
|
|
const rootStore = useRootStore();
|
|
const assistantStore = useAssistantStore();
|
|
const uiStore = useUIStore();
|
|
const usersStore = useUsersStore();
|
|
const settingsStore = useSettingsStore();
|
|
|
|
const { setAppZIndexes } = useStyles();
|
|
|
|
// Initialize undo/redo
|
|
useHistoryHelper(route);
|
|
|
|
const loading = ref(true);
|
|
const defaultLocale = computed(() => rootStore.defaultLocale);
|
|
const isDemoMode = computed(() => route.name === VIEWS.DEMO);
|
|
const showAssistantButton = computed(() => assistantStore.canShowAssistantButtonsOnCanvas);
|
|
|
|
const appGrid = ref<Element | null>(null);
|
|
|
|
const assistantSidebarWidth = computed(() => assistantStore.chatWidth);
|
|
|
|
watch(defaultLocale, (newLocale) => {
|
|
void loadLanguage(newLocale);
|
|
});
|
|
|
|
onMounted(async () => {
|
|
setAppZIndexes();
|
|
logHiringBanner();
|
|
loading.value = false;
|
|
window.addEventListener('resize', updateGridWidth);
|
|
await updateGridWidth();
|
|
});
|
|
|
|
onBeforeUnmount(() => {
|
|
window.removeEventListener('resize', updateGridWidth);
|
|
});
|
|
|
|
// As assistant sidebar width changes, recalculate the total width regularly
|
|
watch(assistantSidebarWidth, async () => {
|
|
await updateGridWidth();
|
|
});
|
|
|
|
const logHiringBanner = () => {
|
|
if (settingsStore.isHiringBannerEnabled && !isDemoMode.value) {
|
|
console.log(HIRING_BANNER);
|
|
}
|
|
};
|
|
|
|
const updateGridWidth = async () => {
|
|
await nextTick();
|
|
if (appGrid.value) {
|
|
uiStore.appGridWidth = appGrid.value.clientWidth;
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<LoadingView v-if="loading" />
|
|
<div
|
|
v-else
|
|
id="n8n-app"
|
|
:class="{
|
|
[$style.container]: true,
|
|
[$style.sidebarCollapsed]: uiStore.sidebarMenuCollapsed,
|
|
}"
|
|
>
|
|
<div id="app-grid" ref="appGrid" :class="$style['app-grid']">
|
|
<div id="banners" :class="$style.banners">
|
|
<BannerStack v-if="!isDemoMode" />
|
|
</div>
|
|
<div id="header" :class="$style.header">
|
|
<router-view name="header"></router-view>
|
|
</div>
|
|
<div v-if="usersStore.currentUser" id="sidebar" :class="$style.sidebar">
|
|
<router-view name="sidebar"></router-view>
|
|
</div>
|
|
<div id="content" :class="$style.content">
|
|
<router-view v-slot="{ Component }">
|
|
<keep-alive v-if="$route.meta.keepWorkflowAlive" include="NodeViewSwitcher" :max="1">
|
|
<component :is="Component" />
|
|
</keep-alive>
|
|
<component :is="Component" v-else />
|
|
</router-view>
|
|
</div>
|
|
<div :id="APP_MODALS_ELEMENT_ID" :class="$style.modals">
|
|
<Modals />
|
|
</div>
|
|
<Telemetry />
|
|
<AskAssistantFloatingButton v-if="showAssistantButton" />
|
|
</div>
|
|
<AskAssistantChat />
|
|
</div>
|
|
</template>
|
|
|
|
<style lang="scss" module>
|
|
// On the root level, whole app is a flex container
|
|
// with app grid and assistant sidebar as children
|
|
.container {
|
|
height: 100vh;
|
|
overflow: hidden;
|
|
display: flex;
|
|
}
|
|
|
|
// App grid is the main app layout including modals and other absolute positioned elements
|
|
.app-grid {
|
|
position: relative;
|
|
display: grid;
|
|
height: 100vh;
|
|
flex-basis: 100%;
|
|
grid-template-areas:
|
|
'banners banners'
|
|
'sidebar header'
|
|
'sidebar content';
|
|
grid-auto-columns: minmax(0, max-content) 1fr;
|
|
grid-template-rows: auto fit-content($header-height) 1fr;
|
|
}
|
|
|
|
.banners {
|
|
grid-area: banners;
|
|
z-index: var(--z-index-top-banners);
|
|
}
|
|
|
|
.content {
|
|
display: flex;
|
|
grid-area: content;
|
|
position: relative;
|
|
overflow: auto;
|
|
height: 100%;
|
|
width: 100%;
|
|
justify-content: center;
|
|
|
|
main {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
}
|
|
|
|
.header {
|
|
grid-area: header;
|
|
z-index: var(--z-index-app-header);
|
|
}
|
|
|
|
.sidebar {
|
|
grid-area: sidebar;
|
|
height: 100%;
|
|
z-index: var(--z-index-app-sidebar);
|
|
}
|
|
|
|
.modals {
|
|
width: 100%;
|
|
}
|
|
</style>
|