Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(editor): Encapsulate canvas actions #4416

Merged
merged 32 commits into from
Nov 7, 2022
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
2d867f7
feat(editor): encapsulating canvas actions
cstuncsik Oct 23, 2022
a0abbeb
fiz(editor): zoomToFit
cstuncsik Oct 23, 2022
027cc6e
fiz(editor): zoomToFit
cstuncsik Oct 23, 2022
d0fd863
fiz(editor): fix imoprts in canvas controls
cstuncsik Oct 23, 2022
d577370
fiz(editor): fix imports in node view
cstuncsik Oct 23, 2022
da96542
fiz(editor): remove unused props from canvas controls
cstuncsik Oct 23, 2022
8f10608
fiz(editor): fix zoomToFit functionality
cstuncsik Oct 24, 2022
d75db28
fiz(editor): move more functions from NodeView to canvas store
cstuncsik Oct 24, 2022
297750b
chore(editor): code formatting fixes
cstuncsik Oct 24, 2022
88085cf
Merge remote-tracking branch 'origin/master' into n8n-5144-encapsulat…
cstuncsik Oct 24, 2022
fdf41f2
fix(editor): adding back some lost refactoring after merge
cstuncsik Oct 24, 2022
98cde8e
fix(editor): remove console.log
cstuncsik Oct 25, 2022
f02af52
fix(editor): add missing canvasAddButtonPosition
cstuncsik Oct 25, 2022
b83a7f3
fix(editor): modify root store env query
cstuncsik Oct 25, 2022
4fe3881
fix(editor): modify canvas control position styling
cstuncsik Oct 25, 2022
e6c2e2b
fix(editor): modify canvas control position styling
cstuncsik Oct 25, 2022
6d92771
fix(editor): roll back process.env
cstuncsik Oct 26, 2022
5f00ee7
fix(editor): fix canvas controls positioning
cstuncsik Oct 26, 2022
e0e3b99
fix(editor): fix canvas controls positioning
cstuncsik Oct 26, 2022
8be7266
Merge remote-tracking branch 'origin/master' into n8n-5144-encapsulat…
cstuncsik Oct 26, 2022
23e81c2
fix(editor): adopting new styles after merge
cstuncsik Oct 26, 2022
e18fff9
fix(editor): not storing html element in the store
cstuncsik Oct 26, 2022
6de36e6
fix(editor): remove unused variables
cstuncsik Oct 26, 2022
0ce9925
Merge remote-tracking branch 'origin/master' into n8n-5144-encapsulat…
cstuncsik Nov 4, 2022
a558477
fix(editor): update canvas controls after conflict resolution
cstuncsik Nov 4, 2022
390882e
fix(editor): revert main.ts to reduce change noise
cstuncsik Nov 4, 2022
7161acc
fix(editor): remove old store commit
cstuncsik Nov 4, 2022
0df367b
fix(editor): simplify canvas store
cstuncsik Nov 4, 2022
1b1d205
fix(editor): reposition execute workflow button in mobile view
cstuncsik Nov 4, 2022
14751b6
Merge remote-tracking branch 'origin/master' into n8n-5144-encapsulat…
cstuncsik Nov 6, 2022
e75da52
fix(editor): fox mouse scroll zoom in canvas
cstuncsik Nov 6, 2022
4624120
fix(editor): move canvas scroll handling into canvas controls
cstuncsik Nov 7, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/editor-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"n8n-design-system": "~0.39.0",
"n8n-workflow": "~0.121.0",
"normalize-wheel": "^1.0.1",
"pinia": "^2.0.23",
"prismjs": "^1.17.1",
"quill": "2.0.0-dev.4",
"quill-autoformat": "^0.1.1",
Expand Down
93 changes: 93 additions & 0 deletions packages/editor-ui/src/components/CanvasControls.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<template>
<div
:class="{ [$style.zoomMenu]: true, [$style.regularZoomMenu]: !isDemo, [$style.demoZoomMenu]: isDemo, [$style.expanded]: !isSidebarMenuCollapsed }">
<n8n-icon-button @click="zoomToFit" type="tertiary" size="large" :title="$locale.baseText('nodeView.zoomToFit')"
icon="expand" />
<n8n-icon-button @click="zoomIn" type="tertiary" size="large" :title="$locale.baseText('nodeView.zoomIn')"
icon="search-plus" />
<n8n-icon-button @click="zoomOut" type="tertiary" size="large" :title="$locale.baseText('nodeView.zoomOut')"
icon="search-minus" />
<n8n-icon-button v-if="nodeViewScale !== 1 && !isDemo" @click="resetZoom" type="tertiary" size="large"
:title="$locale.baseText('nodeView.resetZoom')" icon="undo" />
</div>
</template>
<script lang="ts" setup>
import { onBeforeMount, onBeforeUnmount } from 'vue';
import { storeToRefs } from 'pinia';
import { useCanvasStore } from '@/modules/canvas';

const props = defineProps<{
isDemo: boolean
isSidebarMenuCollapsed: boolean
}>();
const canvasStore = useCanvasStore();
const { zoomToFit, zoomIn, zoomOut, resetZoom } = canvasStore;
const { nodeViewScale } = storeToRefs(canvasStore);

const keyDown = (e: KeyboardEvent) => {
const isCtrlKeyPressed = e.metaKey || e.ctrlKey;
if ((e.key === '=' || e.key === '+') && !isCtrlKeyPressed) {
zoomIn();
} else if ((e.key === '_' || e.key === '-') && !isCtrlKeyPressed) {
zoomOut();
} else if ((e.key === '0') && !isCtrlKeyPressed) {
resetZoom();
} else if ((e.key === '1') && !isCtrlKeyPressed) {
zoomToFit();
}
};

onBeforeMount(() => {
document.addEventListener('keydown', keyDown);
});

onBeforeUnmount(() => {
document.removeEventListener('keydown', keyDown);
});

</script>

<style lang="scss" module>
.zoomMenu {
$--zoom-menu-margin: 15;

position: fixed;
left: 0;
transform: translateX($sidebar-width + $--zoom-menu-margin);
width: 210px;
bottom: 44px;
line-height: 25px;
color: #444;
padding-right: 5px;
transition: transform 150ms ease-in-out;

&:not(.demoZoomMenu).expanded {
transform: translateX($sidebar-expanded-width + $--zoom-menu-margin);
}

button {
border: var(--border-base);
}

>* {
+* {
margin-left: var(--spacing-3xs);
}

&:hover {
transform: scale(1.1);
}
}
}

.regularZoomMenu {
@media (max-width: $breakpoint-2xs) {
bottom: 90px;
}
}

.demoZoomMenu {
left: 10px;
bottom: 10px;
}
cstuncsik marked this conversation as resolved.
Show resolved Hide resolved
</style>
7 changes: 6 additions & 1 deletion packages/editor-ui/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue';
import { createPinia, PiniaVuePlugin } from 'pinia';

import './plugins';
import 'prismjs';
Expand Down Expand Up @@ -31,15 +32,19 @@ router.afterEach((to, from) => {

Vue.use(TelemetryPlugin);
Vue.use((vue) => I18nPlugin(vue, store));
Vue.use(PiniaVuePlugin);

const pinia = createPinia();

new Vue({
i18n: i18nInstance,
router,
store,
pinia,
render: h => h(App),
}).$mount('#app');

if (import.meta.env.NODE_ENV !== 'production') {
if (process.env.NODE_ENV !== 'production') {
// Make sure that we get all error messages properly displayed
// as long as we are not in production mode
window.onerror = (message, source, lineno, colno, error) => {
Expand Down
122 changes: 122 additions & 0 deletions packages/editor-ui/src/modules/canvas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { computed, ref } from 'vue';
import { defineStore } from 'pinia';
import { jsPlumb } from 'jsplumb';
import { v4 as uuid } from 'uuid';
import { useRootStore } from '@/store';
import { INodeUi, XYPosition } from '@/Interface';
import * as CanvasHelpers from '@/views/canvasHelpers';
import { START_NODE_TYPE } from '@/constants';
import '@/plugins/N8nCustomConnectorType';
import '@/plugins/PlusEndpointType';

export const useCanvasStore = defineStore('canvas', () => {
const rootStore = useRootStore();
const jsPlumbInstance = jsPlumb.getInstance();

const nodes = computed<INodeUi[]>(() => rootStore.getters.allNodes);
const triggerNodes = computed<INodeUi[]>(
() => nodes.value.filter(
node => node.type === START_NODE_TYPE || rootStore.getters['nodeTypes/isTriggerNode'](node.type),
),
);
const nodeViewHtmlElement = ref<HTMLDivElement | null | undefined>(null);
const nodeViewScale = ref<number>(1);
const canvasAddButtonPosition = ref<XYPosition>([1, 1]);

const setRecenteredCanvasAddButtonPosition = (offset?: XYPosition) => {
const position = CanvasHelpers.getMidCanvasPosition(nodeViewScale.value, offset || [0, 0]);

position[0] -= CanvasHelpers.PLACEHOLDER_TRIGGER_NODE_SIZE / 2;
position[1] -= CanvasHelpers.PLACEHOLDER_TRIGGER_NODE_SIZE / 2;

canvasAddButtonPosition.value = CanvasHelpers.getNewNodePosition(nodes.value, position);
};

const getPlaceholderTriggerNodeUI = (): INodeUi => {
setRecenteredCanvasAddButtonPosition();

return {
id: uuid(),
...CanvasHelpers.DEFAULT_PLACEHOLDER_TRIGGER_BUTTON,
position: canvasAddButtonPosition.value,
};
};

const getNodesWithPlaceholderNode = (): INodeUi[] =>
triggerNodes.value.length > 0 ? nodes.value : [getPlaceholderTriggerNodeUI(), ...nodes.value];

const setZoomLevel = (zoomLevel: number) => {
nodeViewScale.value = zoomLevel;
const element = nodeViewHtmlElement.value;
cstuncsik marked this conversation as resolved.
Show resolved Hide resolved
if (!element) {
return;
}

// https://docs.jsplumbtoolkit.com/community/current/articles/zooming.html
const scaleString = 'scale(' + zoomLevel + ')';

['webkit', 'moz', 'ms', 'o'].forEach((prefix) => {
// @ts-ignore
element.style[prefix + 'Transform'] = scaleString;
});
element.style.transform = scaleString;

jsPlumbInstance.setZoom(zoomLevel);
};

const resetZoom = () => {
const {scale, offset} = CanvasHelpers.scaleReset({
scale: nodeViewScale.value,
offset: rootStore.getters.getNodeViewOffsetPosition,
});

setZoomLevel(scale);
rootStore.commit('setNodeViewOffsetPosition', {newOffset: offset});
};

const zoomIn = () => {
const {scale, offset: [xOffset, yOffset]} = CanvasHelpers.scaleBigger({
scale: nodeViewScale.value,
offset: rootStore.getters.getNodeViewOffsetPosition,
});

setZoomLevel(scale);
rootStore.commit('setNodeViewOffsetPosition', {newOffset: [xOffset, yOffset]});
};

const zoomOut = () => {
const {scale, offset: [xOffset, yOffset]} = CanvasHelpers.scaleSmaller({
scale: nodeViewScale.value,
offset: rootStore.getters.getNodeViewOffsetPosition,
});

setZoomLevel(scale);
rootStore.commit('setNodeViewOffsetPosition', {newOffset: [xOffset, yOffset]});
};

const zoomToFit = () => {
const nodes = getNodesWithPlaceholderNode();
if (!nodes.length) { // some unknown workflow executions
return;
}

const {zoomLevel, offset} = CanvasHelpers.getZoomToFit(nodes);

setZoomLevel(zoomLevel);
rootStore.commit('setNodeViewOffsetPosition', {newOffset: offset});
};

return {
jsPlumbInstance,
nodeViewHtmlElement,
nodeViewScale,
canvasAddButtonPosition,
setRecenteredCanvasAddButtonPosition,
getNodesWithPlaceholderNode,
setZoomLevel,
resetZoom,
zoomIn,
zoomOut,
zoomToFit,
};
});
Loading