feat(editor): Keyboard shortcuts for the log view (#15378)

This commit is contained in:
Suguru Inoue
2025-05-16 13:54:25 +02:00
committed by GitHub
parent de4c5fc716
commit 1935e62adf
40 changed files with 649 additions and 435 deletions

View File

@@ -69,6 +69,9 @@ const emit = defineEmits<{
'update:node:parameters': [id: string, parameters: Record<string, unknown>];
'update:node:inputs': [id: string];
'update:node:outputs': [id: string];
'update:logs-open': [open?: boolean];
'update:logs:input-open': [open?: boolean];
'update:logs:output-open': [open?: boolean];
'click:node': [id: string, position: XYPosition];
'click:node:add': [id: string, handle: string];
'run:node': [id: string];
@@ -100,6 +103,7 @@ const emit = defineEmits<{
'viewport:change': [viewport: ViewportTransform, dimensions: Dimensions];
'selection:end': [position: XYPosition];
'open:sub-workflow': [nodeId: string];
'start-chat': [];
}>();
const props = withDefaults(
@@ -287,6 +291,9 @@ const keyMap = computed(() => {
ArrowRight: emitWithLastSelectedNode(selectRightNode),
shift_ArrowLeft: emitWithLastSelectedNode(selectUpstreamNodes),
shift_ArrowRight: emitWithLastSelectedNode(selectDownstreamNodes),
l: () => emit('update:logs-open'),
i: () => emit('update:logs:input-open'),
o: () => emit('update:logs:output-open'),
};
if (props.readOnly) return readOnlyKeymap;
@@ -305,6 +312,7 @@ const keyMap = computed(() => {
ctrl_enter: () => emit('run:workflow'),
ctrl_s: () => emit('save:workflow'),
shift_alt_t: async () => await onTidyUp({ source: 'keyboard-shortcut' }),
c: () => emit('start-chat'),
};
return fullKeymap;
});

View File

@@ -1,10 +1,12 @@
<script lang="ts" setup>
import { LOGS_PANEL_STATE } from '@/components/CanvasChat/types/logs';
import KeyboardShortcutTooltip from '@/components/KeyboardShortcutTooltip.vue';
import { useCanvasOperations } from '@/composables/useCanvasOperations';
import { useI18n } from '@/composables/useI18n';
import { useRunWorkflow } from '@/composables/useRunWorkflow';
import { CHAT_TRIGGER_NODE_TYPE } from '@/constants';
import { useLogsStore } from '@/stores/logs.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { N8nButton } from '@n8n/design-system';
import { computed, useCssModule } from 'vue';
import { useRouter } from 'vue-router';
@@ -35,10 +37,11 @@ const containerClass = computed(() => ({
const router = useRouter();
const i18n = useI18n();
const workflowsStore = useWorkflowsStore();
const logsStore = useLogsStore();
const { runEntireWorkflow } = useRunWorkflow({ router });
const { toggleChatOpen } = useCanvasOperations({ router });
const { startChat } = useCanvasOperations({ router });
const isChatOpen = computed(() => workflowsStore.logsPanelState !== LOGS_PANEL_STATE.CLOSED);
const isChatOpen = computed(() => logsStore.isOpen);
const isExecuting = computed(() => workflowsStore.isWorkflowRunning);
const testId = computed(() => `execute-workflow-button-${name}`);
</script>
@@ -52,15 +55,31 @@ const testId = computed(() => `execute-workflow-button-${name}`);
</div>
<template v-if="!readOnly">
<N8nButton
v-if="type === CHAT_TRIGGER_NODE_TYPE"
:type="isChatOpen ? 'secondary' : 'primary'"
size="large"
:disabled="isExecuting"
:data-test-id="testId"
:label="isChatOpen ? i18n.baseText('chat.hide') : i18n.baseText('chat.open')"
@click.capture="toggleChatOpen('node')"
/>
<template v-if="type === CHAT_TRIGGER_NODE_TYPE">
<N8nButton
v-if="isChatOpen"
type="secondary"
size="large"
:disabled="isExecuting"
:data-test-id="testId"
:label="i18n.baseText('chat.hide')"
@click.capture="logsStore.toggleOpen(false)"
/>
<KeyboardShortcutTooltip
v-else
:label="i18n.baseText('chat.open')"
:shortcut="{ keys: ['c'] }"
>
<N8nButton
type="primary"
size="large"
:disabled="isExecuting"
:data-test-id="testId"
:label="i18n.baseText('chat.open')"
@click.capture="startChat('node')"
/>
</KeyboardShortcutTooltip>
</template>
<N8nButton
v-else
type="primary"