mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-13 16:14:07 -08:00
fix(editor): Ensure toasts show above modal overlays (#11410)
Some checks failed
Test Master / install-and-build (push) Has been cancelled
Test Master / Unit tests (18.x) (push) Has been cancelled
Test Master / Unit tests (20.x) (push) Has been cancelled
Test Master / Unit tests (22.4) (push) Has been cancelled
Test Master / Lint (push) Has been cancelled
Test Master / Notify Slack on failure (push) Has been cancelled
Some checks failed
Test Master / install-and-build (push) Has been cancelled
Test Master / Unit tests (18.x) (push) Has been cancelled
Test Master / Unit tests (20.x) (push) Has been cancelled
Test Master / Unit tests (22.4) (push) Has been cancelled
Test Master / Lint (push) Has been cancelled
Test Master / Notify Slack on failure (push) Has been cancelled
This commit is contained in:
parent
d6aaeea2ab
commit
351134f786
|
@ -15,7 +15,7 @@ import {
|
||||||
TRELLO_NODE_NAME,
|
TRELLO_NODE_NAME,
|
||||||
} from '../constants';
|
} from '../constants';
|
||||||
import { CredentialsModal, CredentialsPage, NDV, WorkflowPage } from '../pages';
|
import { CredentialsModal, CredentialsPage, NDV, WorkflowPage } from '../pages';
|
||||||
import { successToast } from '../pages/notifications';
|
import { errorToast, successToast } from '../pages/notifications';
|
||||||
import { getVisibleSelect } from '../utils';
|
import { getVisibleSelect } from '../utils';
|
||||||
|
|
||||||
const credentialsPage = new CredentialsPage();
|
const credentialsPage = new CredentialsPage();
|
||||||
|
@ -278,4 +278,25 @@ describe('Credentials', () => {
|
||||||
credentialsModal.getters.credentialAuthTypeRadioButtons().first().click();
|
credentialsModal.getters.credentialAuthTypeRadioButtons().first().click();
|
||||||
nodeDetailsView.getters.copyInput().should('not.exist');
|
nodeDetailsView.getters.copyInput().should('not.exist');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('ADO-2583 should show notifications above credential modal overlay', () => {
|
||||||
|
// check error notifications because they are sticky
|
||||||
|
cy.intercept('POST', '/rest/credentials', { forceNetworkError: true });
|
||||||
|
credentialsPage.getters.createCredentialButton().click();
|
||||||
|
|
||||||
|
credentialsModal.getters.newCredentialModal().should('be.visible');
|
||||||
|
credentialsModal.getters.newCredentialTypeSelect().should('be.visible');
|
||||||
|
credentialsModal.getters.newCredentialTypeOption('Notion API').click();
|
||||||
|
|
||||||
|
credentialsModal.getters.newCredentialTypeButton().click();
|
||||||
|
credentialsModal.getters.connectionParameter('Internal Integration Secret').type('1234567890');
|
||||||
|
|
||||||
|
credentialsModal.actions.setName('My awesome Notion account');
|
||||||
|
credentialsModal.getters.saveButton().click({ force: true });
|
||||||
|
errorToast().should('have.length', 1);
|
||||||
|
errorToast().should('be.visible');
|
||||||
|
|
||||||
|
errorToast().should('have.css', 'z-index', '2100');
|
||||||
|
cy.get('.el-overlay').should('have.css', 'z-index', '2001');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { useUIStore } from '@/stores/ui.store';
|
||||||
import { useUsersStore } from '@/stores/users.store';
|
import { useUsersStore } from '@/stores/users.store';
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
import { useHistoryHelper } from '@/composables/useHistoryHelper';
|
import { useHistoryHelper } from '@/composables/useHistoryHelper';
|
||||||
|
import { useStyles } from './composables/useStyles';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const rootStore = useRootStore();
|
const rootStore = useRootStore();
|
||||||
|
@ -24,6 +25,8 @@ const uiStore = useUIStore();
|
||||||
const usersStore = useUsersStore();
|
const usersStore = useUsersStore();
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
|
|
||||||
|
const { setAppZIndexes } = useStyles();
|
||||||
|
|
||||||
// Initialize undo/redo
|
// Initialize undo/redo
|
||||||
useHistoryHelper(route);
|
useHistoryHelper(route);
|
||||||
|
|
||||||
|
@ -41,6 +44,7 @@ watch(defaultLocale, (newLocale) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
setAppZIndexes();
|
||||||
logHiringBanner();
|
logHiringBanner();
|
||||||
void useExternalHooks().run('app.mount');
|
void useExternalHooks().run('app.mount');
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
|
@ -134,7 +138,7 @@ const updateGridWidth = async () => {
|
||||||
|
|
||||||
.banners {
|
.banners {
|
||||||
grid-area: banners;
|
grid-area: banners;
|
||||||
z-index: 999;
|
z-index: var(--z-index-top-banners);
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
|
@ -154,13 +158,13 @@ const updateGridWidth = async () => {
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
grid-area: header;
|
grid-area: header;
|
||||||
z-index: 99;
|
z-index: var(--z-index-app-header);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar {
|
.sidebar {
|
||||||
grid-area: sidebar;
|
grid-area: sidebar;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
z-index: 999;
|
z-index: var(--z-index-app-sidebar);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modals {
|
.modals {
|
||||||
|
|
|
@ -106,7 +106,7 @@ function onClose() {
|
||||||
.container {
|
.container {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
flex-basis: content;
|
flex-basis: content;
|
||||||
z-index: 300;
|
z-index: var(--z-index-ask-assistant-chat);
|
||||||
}
|
}
|
||||||
|
|
||||||
.wrapper {
|
.wrapper {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useI18n } from '@/composables/useI18n';
|
import { useI18n } from '@/composables/useI18n';
|
||||||
|
import { useStyles } from '@/composables/useStyles';
|
||||||
import { useAssistantStore } from '@/stores/assistant.store';
|
import { useAssistantStore } from '@/stores/assistant.store';
|
||||||
import AssistantAvatar from 'n8n-design-system/components/AskAssistantAvatar/AssistantAvatar.vue';
|
import AssistantAvatar from 'n8n-design-system/components/AskAssistantAvatar/AssistantAvatar.vue';
|
||||||
import AskAssistantButton from 'n8n-design-system/components/AskAssistantButton/AskAssistantButton.vue';
|
import AskAssistantButton from 'n8n-design-system/components/AskAssistantButton/AskAssistantButton.vue';
|
||||||
|
@ -7,6 +8,7 @@ import { computed } from 'vue';
|
||||||
|
|
||||||
const assistantStore = useAssistantStore();
|
const assistantStore = useAssistantStore();
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
|
const { APP_Z_INDEXES } = useStyles();
|
||||||
|
|
||||||
const lastUnread = computed(() => {
|
const lastUnread = computed(() => {
|
||||||
const msg = assistantStore.lastUnread;
|
const msg = assistantStore.lastUnread;
|
||||||
|
@ -39,7 +41,7 @@ const onClick = () => {
|
||||||
data-test-id="ask-assistant-floating-button"
|
data-test-id="ask-assistant-floating-button"
|
||||||
>
|
>
|
||||||
<n8n-tooltip
|
<n8n-tooltip
|
||||||
:z-index="4000"
|
:z-index="APP_Z_INDEXES.ASK_ASSISTANT_FLOATING_BUTTON_TOOLTIP"
|
||||||
placement="top"
|
placement="top"
|
||||||
:visible="!!lastUnread"
|
:visible="!!lastUnread"
|
||||||
:popper-class="$style.tooltip"
|
:popper-class="$style.tooltip"
|
||||||
|
@ -61,7 +63,7 @@ const onClick = () => {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: var(--spacing-s);
|
bottom: var(--spacing-s);
|
||||||
right: var(--spacing-s);
|
right: var(--spacing-s);
|
||||||
z-index: 3000;
|
z-index: var(--z-index-ask-assistant-floating-button);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip {
|
.tooltip {
|
||||||
|
|
|
@ -1,184 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { type StyleValue, defineComponent, type PropType } from 'vue';
|
|
||||||
|
|
||||||
import type { ITemplatesNode } from '@/Interface';
|
|
||||||
import type { INodeTypeDescription } from 'n8n-workflow';
|
|
||||||
import { mapStores } from 'pinia';
|
|
||||||
import { useRootStore } from '@/stores/root.store';
|
|
||||||
|
|
||||||
interface NodeIconData {
|
|
||||||
type: string;
|
|
||||||
path?: string;
|
|
||||||
icon?: string;
|
|
||||||
fileExtension?: string;
|
|
||||||
fileBuffer?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'HoverableNodeIcon',
|
|
||||||
props: {
|
|
||||||
circle: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
clickButton: {
|
|
||||||
type: Function,
|
|
||||||
},
|
|
||||||
disabled: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
nodeType: {
|
|
||||||
type: Object as PropType<INodeTypeDescription>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
size: {
|
|
||||||
type: Number,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapStores(useRootStore),
|
|
||||||
fontStyleData(): object {
|
|
||||||
return {
|
|
||||||
'max-width': this.size + 'px',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
iconStyleData(): StyleValue {
|
|
||||||
const nodeType = this.nodeType;
|
|
||||||
const nodeTypeColor = nodeType?.defaults?.color;
|
|
||||||
const color = typeof nodeTypeColor === 'string' ? nodeTypeColor : '';
|
|
||||||
|
|
||||||
if (!this.size) {
|
|
||||||
return { color };
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
color,
|
|
||||||
width: this.size + 'px',
|
|
||||||
height: this.size + 'px',
|
|
||||||
'font-size': this.size + 'px',
|
|
||||||
'line-height': this.size + 'px',
|
|
||||||
'border-radius': this.circle ? '50%' : '2px',
|
|
||||||
...(this.disabled && {
|
|
||||||
color: 'var(--color-text-light)',
|
|
||||||
'-webkit-filter': 'contrast(40%) brightness(1.5) grayscale(100%)',
|
|
||||||
filter: 'contrast(40%) brightness(1.5) grayscale(100%)',
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
imageStyleData(): StyleValue {
|
|
||||||
return {
|
|
||||||
width: '100%',
|
|
||||||
'max-width': '100%',
|
|
||||||
'max-height': '100%',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
nodeIconData(): null | NodeIconData {
|
|
||||||
const nodeType = this.nodeType as INodeTypeDescription | ITemplatesNode | null;
|
|
||||||
if (nodeType === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((nodeType as ITemplatesNode).iconData) {
|
|
||||||
return (nodeType as ITemplatesNode).iconData;
|
|
||||||
}
|
|
||||||
|
|
||||||
const restUrl = this.rootStore.restUrl;
|
|
||||||
|
|
||||||
if (typeof nodeType.icon === 'string') {
|
|
||||||
const [type, path] = nodeType.icon.split(':');
|
|
||||||
const returnData: NodeIconData = {
|
|
||||||
type,
|
|
||||||
path,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (type === 'file') {
|
|
||||||
returnData.path = restUrl + '/node-icon/' + nodeType.name;
|
|
||||||
returnData.fileExtension = path.split('.').slice(-1).join();
|
|
||||||
}
|
|
||||||
|
|
||||||
return returnData;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
showTooltip: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div
|
|
||||||
:class="$style.wrapper"
|
|
||||||
:style="iconStyleData"
|
|
||||||
@click="() => $emit('click')"
|
|
||||||
@mouseover="showTooltip = true"
|
|
||||||
@mouseleave="showTooltip = false"
|
|
||||||
>
|
|
||||||
<div :class="$style.tooltip">
|
|
||||||
<n8n-tooltip placement="top" :visible="showTooltip">
|
|
||||||
<template #content>
|
|
||||||
<div v-text="nodeType.displayName"></div>
|
|
||||||
</template>
|
|
||||||
<span />
|
|
||||||
</n8n-tooltip>
|
|
||||||
</div>
|
|
||||||
<div v-if="nodeIconData !== null" :class="$style.icon" title="">
|
|
||||||
<div :class="$style.iconWrapper" :style="iconStyleData">
|
|
||||||
<div v-if="nodeIconData !== null" :class="$style.icon">
|
|
||||||
<img
|
|
||||||
v-if="nodeIconData.type === 'file'"
|
|
||||||
:src="nodeIconData.fileBuffer || nodeIconData.path"
|
|
||||||
:style="imageStyleData"
|
|
||||||
/>
|
|
||||||
<font-awesome-icon
|
|
||||||
v-else
|
|
||||||
:icon="nodeIconData.icon || nodeIconData.path"
|
|
||||||
:style="fontStyleData"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div v-else class="node-icon-placeholder">
|
|
||||||
{{ nodeType !== null ? nodeType.displayName.charAt(0) : '?' }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else :class="$style.placeholder">
|
|
||||||
{{ nodeType !== null ? nodeType.displayName.charAt(0) : '?' }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" module>
|
|
||||||
.wrapper {
|
|
||||||
cursor: pointer;
|
|
||||||
z-index: 2000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.iconWrapper {
|
|
||||||
svg {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltip {
|
|
||||||
left: 10px;
|
|
||||||
position: relative;
|
|
||||||
z-index: 9999;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -5,6 +5,7 @@ import type { EventBus } from 'n8n-design-system';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
import { useUIStore } from '@/stores/ui.store';
|
||||||
import type { ModalKey } from '@/Interface';
|
import type { ModalKey } from '@/Interface';
|
||||||
import { APP_MODALS_ELEMENT_ID } from '@/constants';
|
import { APP_MODALS_ELEMENT_ID } from '@/constants';
|
||||||
|
import { useStyles } from '@/composables/useStyles';
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
@ -50,6 +51,8 @@ const props = withDefaults(
|
||||||
|
|
||||||
const emit = defineEmits<{ enter: [] }>();
|
const emit = defineEmits<{ enter: [] }>();
|
||||||
|
|
||||||
|
const { APP_Z_INDEXES } = useStyles();
|
||||||
|
|
||||||
const styles = computed(() => {
|
const styles = computed(() => {
|
||||||
const styles: { [prop: string]: string } = {};
|
const styles: { [prop: string]: string } = {};
|
||||||
if (props.height) {
|
if (props.height) {
|
||||||
|
@ -143,7 +146,7 @@ function getCustomClass() {
|
||||||
:append-to-body="appendToBody"
|
:append-to-body="appendToBody"
|
||||||
:data-test-id="`${name}-modal`"
|
:data-test-id="`${name}-modal`"
|
||||||
:modal-class="center ? $style.center : ''"
|
:modal-class="center ? $style.center : ''"
|
||||||
:z-index="2000"
|
:z-index="APP_Z_INDEXES.MODALS"
|
||||||
>
|
>
|
||||||
<template v-if="$slots.header" #header>
|
<template v-if="$slots.header" #header>
|
||||||
<slot v-if="!loading" name="header" />
|
<slot v-if="!loading" name="header" />
|
||||||
|
|
|
@ -173,7 +173,7 @@ onBeforeUnmount(() => {
|
||||||
top: $header-height;
|
top: $header-height;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
z-index: 200;
|
z-index: var(--z-index-node-creator);
|
||||||
width: $node-creator-width;
|
width: $node-creator-width;
|
||||||
color: $node-creator-text-color;
|
color: $node-creator-text-color;
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ import { usePinnedData } from '@/composables/usePinnedData';
|
||||||
import { useTelemetry } from '@/composables/useTelemetry';
|
import { useTelemetry } from '@/composables/useTelemetry';
|
||||||
import { useI18n } from '@/composables/useI18n';
|
import { useI18n } from '@/composables/useI18n';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
|
import { useStyles } from '@/composables/useStyles';
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
saveKeyboardShortcut: [event: KeyboardEvent];
|
saveKeyboardShortcut: [event: KeyboardEvent];
|
||||||
|
@ -73,6 +74,7 @@ const deviceSupport = useDeviceSupport();
|
||||||
const telemetry = useTelemetry();
|
const telemetry = useTelemetry();
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const message = useMessage();
|
const message = useMessage();
|
||||||
|
const { APP_Z_INDEXES } = useStyles();
|
||||||
|
|
||||||
const settingsEventBus = createEventBus();
|
const settingsEventBus = createEventBus();
|
||||||
const redrawRequired = ref(false);
|
const redrawRequired = ref(false);
|
||||||
|
@ -668,7 +670,7 @@ onBeforeUnmount(() => {
|
||||||
width="auto"
|
width="auto"
|
||||||
:append-to="`#${APP_MODALS_ELEMENT_ID}`"
|
:append-to="`#${APP_MODALS_ELEMENT_ID}`"
|
||||||
data-test-id="ndv"
|
data-test-id="ndv"
|
||||||
:z-index="1800"
|
:z-index="APP_Z_INDEXES.NDV"
|
||||||
:data-has-output-connection="hasOutputConnection"
|
:data-has-output-connection="hasOutputConnection"
|
||||||
>
|
>
|
||||||
<n8n-tooltip
|
<n8n-tooltip
|
||||||
|
|
|
@ -20,6 +20,7 @@ import { assert } from '@/utils/assert';
|
||||||
import type { BrowserJsPlumbInstance } from '@jsplumb/browser-ui';
|
import type { BrowserJsPlumbInstance } from '@jsplumb/browser-ui';
|
||||||
import { useNodeBase } from '@/composables/useNodeBase';
|
import { useNodeBase } from '@/composables/useNodeBase';
|
||||||
import { useTelemetry } from '@/composables/useTelemetry';
|
import { useTelemetry } from '@/composables/useTelemetry';
|
||||||
|
import { useStyles } from '@/composables/useStyles';
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
@ -54,6 +55,7 @@ const ndvStore = useNDVStore();
|
||||||
const nodeTypesStore = useNodeTypesStore();
|
const nodeTypesStore = useNodeTypesStore();
|
||||||
const uiStore = useUIStore();
|
const uiStore = useUIStore();
|
||||||
const workflowsStore = useWorkflowsStore();
|
const workflowsStore = useWorkflowsStore();
|
||||||
|
const { APP_Z_INDEXES } = useStyles();
|
||||||
|
|
||||||
const isResizing = ref<boolean>(false);
|
const isResizing = ref<boolean>(false);
|
||||||
const isTouchActive = ref<boolean>(false);
|
const isTouchActive = ref<boolean>(false);
|
||||||
|
@ -136,7 +138,9 @@ const stickySize = computed<StyleValue>(() => ({
|
||||||
const stickyPosition = computed<StyleValue>(() => ({
|
const stickyPosition = computed<StyleValue>(() => ({
|
||||||
left: position.value[0] + 'px',
|
left: position.value[0] + 'px',
|
||||||
top: position.value[1] + 'px',
|
top: position.value[1] + 'px',
|
||||||
zIndex: props.isActive ? 9999999 : -1 * Math.floor((height.value * width.value) / 1000),
|
zIndex: props.isActive
|
||||||
|
? APP_Z_INDEXES.ACTIVE_STICKY
|
||||||
|
: -1 * Math.floor((height.value * width.value) / 1000),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const workflowRunning = computed(() => uiStore.isActionActive.workflowRunning);
|
const workflowRunning = computed(() => uiStore.isActionActive.workflowRunning);
|
||||||
|
|
|
@ -242,7 +242,7 @@ watch(
|
||||||
left: 0;
|
left: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 9999999;
|
z-index: var(--z-index-workflow-preview-ndv);
|
||||||
}
|
}
|
||||||
|
|
||||||
.spinner {
|
.spinner {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { getMousePosition, getRelativePosition } from '@/utils/nodeViewUtils';
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed } from 'vue';
|
||||||
import { useCanvasStore } from '@/stores/canvas.store';
|
import { useCanvasStore } from '@/stores/canvas.store';
|
||||||
import { useContextMenu } from './useContextMenu';
|
import { useContextMenu } from './useContextMenu';
|
||||||
|
import { useStyles } from './useStyles';
|
||||||
|
|
||||||
interface ExtendedHTMLSpanElement extends HTMLSpanElement {
|
interface ExtendedHTMLSpanElement extends HTMLSpanElement {
|
||||||
x: number;
|
x: number;
|
||||||
|
@ -22,6 +23,7 @@ export default function useCanvasMouseSelect() {
|
||||||
const canvasStore = useCanvasStore();
|
const canvasStore = useCanvasStore();
|
||||||
const workflowsStore = useWorkflowsStore();
|
const workflowsStore = useWorkflowsStore();
|
||||||
const { isOpen: isContextMenuOpen } = useContextMenu();
|
const { isOpen: isContextMenuOpen } = useContextMenu();
|
||||||
|
const { APP_Z_INDEXES } = useStyles();
|
||||||
|
|
||||||
function _setSelectBoxStyle(styles: Record<string, string>) {
|
function _setSelectBoxStyle(styles: Record<string, string>) {
|
||||||
Object.assign(selectBox.value.style, styles);
|
Object.assign(selectBox.value.style, styles);
|
||||||
|
@ -106,7 +108,7 @@ export default function useCanvasMouseSelect() {
|
||||||
border: '2px dotted #FF0000',
|
border: '2px dotted #FF0000',
|
||||||
// Positioned absolutely within #node-view. This is consistent with how nodes are positioned.
|
// Positioned absolutely within #node-view. This is consistent with how nodes are positioned.
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
zIndex: '100',
|
zIndex: `${APP_Z_INDEXES.SELECT_BOX}`,
|
||||||
visibility: 'hidden',
|
visibility: 'hidden',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
25
packages/editor-ui/src/composables/useStyles.spec.ts
Normal file
25
packages/editor-ui/src/composables/useStyles.spec.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { useStyles } from './useStyles';
|
||||||
|
|
||||||
|
describe('useStyles', () => {
|
||||||
|
it('sets z-index as css variables', () => {
|
||||||
|
vi.spyOn(global.document.documentElement.style, 'setProperty');
|
||||||
|
|
||||||
|
const { setAppZIndexes } = useStyles();
|
||||||
|
|
||||||
|
setAppZIndexes();
|
||||||
|
|
||||||
|
expect(global.document.documentElement.style.setProperty).toHaveBeenNthCalledWith(
|
||||||
|
1,
|
||||||
|
'--z-index-app-header',
|
||||||
|
'99',
|
||||||
|
);
|
||||||
|
expect(global.document.documentElement.style.setProperty).toHaveBeenCalledWith(
|
||||||
|
'--z-index-canvas-add-button',
|
||||||
|
'101',
|
||||||
|
);
|
||||||
|
expect(global.document.documentElement.style.setProperty).toHaveBeenLastCalledWith(
|
||||||
|
'--z-index-workflow-preview-ndv',
|
||||||
|
'9999999',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
31
packages/editor-ui/src/composables/useStyles.ts
Normal file
31
packages/editor-ui/src/composables/useStyles.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
const APP_Z_INDEXES = {
|
||||||
|
APP_HEADER: 99,
|
||||||
|
SELECT_BOX: 100,
|
||||||
|
CANVAS_ADD_BUTTON: 101,
|
||||||
|
NODE_CREATOR: 200,
|
||||||
|
ASK_ASSISTANT_CHAT: 300,
|
||||||
|
APP_SIDEBAR: 999,
|
||||||
|
CANVAS_SELECT_BOX: 100,
|
||||||
|
TOP_BANNERS: 999,
|
||||||
|
NDV: 1800,
|
||||||
|
MODALS: 2000,
|
||||||
|
TOASTS: 2100,
|
||||||
|
ASK_ASSISTANT_FLOATING_BUTTON: 3000,
|
||||||
|
ASK_ASSISTANT_FLOATING_BUTTON_TOOLTIP: 3000,
|
||||||
|
DRAGGABLE: 9999999,
|
||||||
|
ACTIVE_STICKY: 9999999,
|
||||||
|
WORKFLOW_PREVIEW_NDV: 9999999,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
const setAppZIndexes = () => {
|
||||||
|
Object.keys(APP_Z_INDEXES).forEach((key) => {
|
||||||
|
const variableName = `--z-index-${key.toLowerCase().replaceAll('_', '-')}`;
|
||||||
|
const value = APP_Z_INDEXES[key as keyof typeof APP_Z_INDEXES];
|
||||||
|
document.documentElement.style.setProperty(variableName, `${value}`);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useStyles = () => ({
|
||||||
|
APP_Z_INDEXES,
|
||||||
|
setAppZIndexes,
|
||||||
|
});
|
|
@ -9,6 +9,7 @@ import { useI18n } from './useI18n';
|
||||||
import { useExternalHooks } from './useExternalHooks';
|
import { useExternalHooks } from './useExternalHooks';
|
||||||
import { VIEWS } from '@/constants';
|
import { VIEWS } from '@/constants';
|
||||||
import type { ApplicationError } from 'n8n-workflow';
|
import type { ApplicationError } from 'n8n-workflow';
|
||||||
|
import { useStyles } from './useStyles';
|
||||||
|
|
||||||
export interface NotificationErrorWithNodeAndDescription extends ApplicationError {
|
export interface NotificationErrorWithNodeAndDescription extends ApplicationError {
|
||||||
node: {
|
node: {
|
||||||
|
@ -17,15 +18,6 @@ export interface NotificationErrorWithNodeAndDescription extends ApplicationErro
|
||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const messageDefaults: Partial<Omit<NotificationOptions, 'message'>> = {
|
|
||||||
dangerouslyUseHTMLString: false,
|
|
||||||
position: 'bottom-right',
|
|
||||||
zIndex: 1900, // above NDV and below the modals
|
|
||||||
offset: 64,
|
|
||||||
appendTo: '#app-grid',
|
|
||||||
customClass: 'content-toast',
|
|
||||||
};
|
|
||||||
|
|
||||||
const stickyNotificationQueue: NotificationHandle[] = [];
|
const stickyNotificationQueue: NotificationHandle[] = [];
|
||||||
|
|
||||||
export function useToast() {
|
export function useToast() {
|
||||||
|
@ -34,6 +26,16 @@ export function useToast() {
|
||||||
const uiStore = useUIStore();
|
const uiStore = useUIStore();
|
||||||
const externalHooks = useExternalHooks();
|
const externalHooks = useExternalHooks();
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
|
const { APP_Z_INDEXES } = useStyles();
|
||||||
|
|
||||||
|
const messageDefaults: Partial<Omit<NotificationOptions, 'message'>> = {
|
||||||
|
dangerouslyUseHTMLString: false,
|
||||||
|
position: 'bottom-right',
|
||||||
|
zIndex: APP_Z_INDEXES.TOASTS, // above NDV and modal overlays
|
||||||
|
offset: 64,
|
||||||
|
appendTo: '#app-grid',
|
||||||
|
customClass: 'content-toast',
|
||||||
|
};
|
||||||
|
|
||||||
function showMessage(messageData: Partial<NotificationOptions>, track = true) {
|
function showMessage(messageData: Partial<NotificationOptions>, track = true) {
|
||||||
const { message, title } = messageData;
|
const { message, title } = messageData;
|
||||||
|
|
|
@ -55,7 +55,7 @@ const containerCssVars = computed(() => ({
|
||||||
left: var(--trigger-placeholder-left-position);
|
left: var(--trigger-placeholder-left-position);
|
||||||
// We have to increase z-index to make sure it's higher than selecting box in NodeView
|
// We have to increase z-index to make sure it's higher than selecting box in NodeView
|
||||||
// otherwise the clicks wouldn't register
|
// otherwise the clicks wouldn't register
|
||||||
z-index: 101;
|
z-index: var(--z-index-canvas-add-button);
|
||||||
|
|
||||||
&:hover .button svg path {
|
&:hover .button svg path {
|
||||||
fill: var(--color-primary);
|
fill: var(--color-primary);
|
||||||
|
|
Loading…
Reference in a new issue