Files
Automata/packages/editor-ui/src/stores/ndv.store.ts
Iván Ovejero beedfb609c feat(editor): SQL editor overhaul (#6282)
* Draft setup
*  Implemented expression evaluation in Postgres node, minor SQL editor UI improvements, minor refacring
*  Added initial version of expression preview for SQL editor
*  Linking npm package for codemirror sql grammar instead of a local file
*  Moving expression editor wrapper elements to the component
*  Using expression preview in SQL editor
* Use SQL parser skipping whitespace
*  Added support for custom skipped segments specification
*  Fixing highlight problems with dots and expressions that resolve to zero
* 👕 Fixing linting error
*  Added current item support
*  Added expression support to more nodes with sql editor
*  Added expression support for other nodes
*  Implemented different SQL dialect support
* 🐛 Fixing hard-coded parameter names for editors
*  Fixing preview for nested queries, updating query when input data changes, adding keyboard shortcut to toggle comments
*  Adding a custom automcomplete notice for different editors
*  Updating SQL autocomplete notice
*  Added unit tests for SQL editor
*  Using latest grammar
* 🐛 Fixing code node editor rendering
* 💄 SQL preview dropdown matches editor width. Removing unnecessary css
*  Addressing PR review feedback
* 👌 Addressing PR review feedback pt2
* 👌 Added path alias for utils in nodes-base package
* 👌 Addressing more PR review feedback
*  Adding tests for `getResolvables` utility function
* Fixing lodash imports
* 👌 Better focus handling, adding more plugins to the editor, other minor imrovements
*  Not showing SQL autocomplete suggestions inside expressions
*  Using npm package for sql grammar
*  Removing autocomplete notice, adding line highlight on syntax error
* 👌 Addressing code review feedback
---------
Co-authored-by: Milorad Filipovic <milorad@n8n.io>
2023-06-22 16:47:28 +02:00

247 lines
6.6 KiB
TypeScript

import { LOCAL_STORAGE_MAPPING_IS_ONBOARDED, STORES } from '@/constants';
import type {
INodeUi,
IRunDataDisplayMode,
NDVState,
NodePanelType,
TargetItem,
XYPosition,
} from '@/Interface';
import type { INodeIssues, IRunData } from 'n8n-workflow';
import { defineStore } from 'pinia';
import { useWorkflowsStore } from './workflows.store';
export const useNDVStore = defineStore(STORES.NDV, {
state: (): NDVState => ({
activeNodeName: null,
mainPanelDimensions: {},
sessionId: '',
input: {
displayMode: 'schema',
nodeName: undefined,
run: undefined,
branch: undefined,
data: {
isEmpty: true,
},
},
output: {
displayMode: 'table',
branch: undefined,
data: {
isEmpty: true,
},
editMode: {
enabled: false,
value: '',
},
},
focusedMappableInput: '',
mappingTelemetry: {},
hoveringItem: null,
draggable: {
isDragging: false,
type: '',
data: '',
canDrop: false,
stickyPosition: null,
},
isMappingOnboarded: window.localStorage.getItem(LOCAL_STORAGE_MAPPING_IS_ONBOARDED) === 'true',
}),
getters: {
activeNode(): INodeUi | null {
const workflowsStore = useWorkflowsStore();
return workflowsStore.getNodeByName(this.activeNodeName || '');
},
ndvInputData(): IRunData[] {
const workflowsStore = useWorkflowsStore();
const executionData = workflowsStore.getWorkflowExecution;
const inputNodeName: string | undefined = this.input.nodeName;
const inputRunIndex: number = this.input.run ?? 0;
const inputBranchIndex: number = this.input.branch ?? 0;
if (
!executionData ||
!inputNodeName ||
inputRunIndex === undefined ||
inputBranchIndex === undefined
) {
return [];
}
return executionData.data?.resultData?.runData?.[inputNodeName]?.[inputRunIndex]?.data
?.main?.[inputBranchIndex];
},
getPanelDisplayMode() {
return (panel: NodePanelType) => this[panel].displayMode;
},
inputPanelDisplayMode(): IRunDataDisplayMode {
return this.input.displayMode;
},
outputPanelDisplayMode(): IRunDataDisplayMode {
return this.output.displayMode;
},
isDraggableDragging(): boolean {
return this.draggable.isDragging;
},
draggableType(): string {
return this.draggable.type;
},
draggableData(): string {
return this.draggable.data;
},
canDraggableDrop(): boolean {
return this.draggable.canDrop;
},
outputPanelEditMode(): NDVState['output']['editMode'] {
return this.output.editMode;
},
getMainPanelDimensions() {
return (panelType: string) => {
const defaults = { relativeRight: 1, relativeLeft: 1, relativeWidth: 1 };
return { ...defaults, ...this.mainPanelDimensions[panelType] };
};
},
draggableStickyPos(): XYPosition | null {
return this.draggable.stickyPosition;
},
ndvInputNodeName(): string | undefined {
return this.input.nodeName;
},
ndvInputRunIndex(): number | undefined {
return this.input.run;
},
ndvInputBranchIndex(): number | undefined {
return this.input.branch;
},
isDNVDataEmpty() {
return (panel: 'input' | 'output'): boolean => this[panel].data.isEmpty;
},
isInputParentOfActiveNode(): boolean {
const inputNodeName = this.ndvInputNodeName;
if (!this.activeNode || !inputNodeName) {
return false;
}
const workflow = useWorkflowsStore().getCurrentWorkflow();
const parentNodes = workflow.getParentNodes(this.activeNode.name, 'main', 1);
return parentNodes.includes(inputNodeName);
},
hoveringItemNumber(): number {
return (this.hoveringItem?.itemIndex ?? 0) + 1;
},
getHoveringItem(): TargetItem | null {
if (this.isInputParentOfActiveNode) {
return this.hoveringItem;
}
return null;
},
},
actions: {
setInputNodeName(nodeName: string | undefined): void {
this.input = {
...this.input,
nodeName,
};
},
setInputRunIndex(run?: number): void {
this.input = {
...this.input,
run,
};
},
setMainPanelDimensions(params: {
panelType: string;
dimensions: { relativeLeft?: number; relativeRight?: number; relativeWidth?: number };
}): void {
this.mainPanelDimensions = {
...this.mainPanelDimensions,
[params.panelType]: {
...this.mainPanelDimensions[params.panelType],
...params.dimensions,
},
};
},
setNDVSessionId(): void {
this.sessionId = `ndv-${Math.random().toString(36).slice(-8)}`;
},
resetNDVSessionId(): void {
this.sessionId = '';
},
setPanelDisplayMode(params: { pane: NodePanelType; mode: IRunDataDisplayMode }): void {
this[params.pane].displayMode = params.mode;
},
setOutputPanelEditModeEnabled(isEnabled: boolean): void {
this.output.editMode.enabled = isEnabled;
},
setOutputPanelEditModeValue(payload: string): void {
this.output.editMode.value = payload;
},
setMappableNDVInputFocus(paramName: string): void {
this.focusedMappableInput = paramName;
},
draggableStartDragging({ type, data }: { type: string; data: string }): void {
this.draggable = {
isDragging: true,
type,
data,
canDrop: false,
stickyPosition: null,
};
},
draggableStopDragging(): void {
this.draggable = {
isDragging: false,
type: '',
data: '',
canDrop: false,
stickyPosition: null,
};
},
setDraggableStickyPos(position: XYPosition | null): void {
this.draggable.stickyPosition = position;
},
setDraggableCanDrop(canDrop: boolean): void {
this.draggable.canDrop = canDrop;
},
setMappingTelemetry(telemetry: { [key: string]: string | number | boolean }): void {
this.mappingTelemetry = { ...this.mappingTelemetry, ...telemetry };
},
resetMappingTelemetry(): void {
this.mappingTelemetry = {};
},
setHoveringItem(item: null | NDVState['hoveringItem']): void {
this.hoveringItem = item;
},
setNDVBranchIndex(e: { pane: 'input' | 'output'; branchIndex: number }): void {
this[e.pane].branch = e.branchIndex;
},
setNDVPanelDataIsEmpty(payload: { panel: 'input' | 'output'; isEmpty: boolean }): void {
this[payload.panel].data.isEmpty = payload.isEmpty;
},
disableMappingHint(store = true) {
this.isMappingOnboarded = true;
if (store) {
window.localStorage.setItem(LOCAL_STORAGE_MAPPING_IS_ONBOARDED, 'true');
}
},
updateNodeParameterIssues(issues: INodeIssues): void {
const workflowsStore = useWorkflowsStore();
const activeNode = workflowsStore.getNodeByName(this.activeNodeName || '');
if (activeNode) {
const nodeIndex = workflowsStore.workflow.nodes.findIndex((node) => {
return node.name === activeNode.name;
});
workflowsStore.updateNodeAtIndex(nodeIndex, {
issues: {
...activeNode.issues,
...issues,
},
});
}
},
},
});