Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
42 changes: 29 additions & 13 deletions src/components/interaction-designer/Block.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
:start-x="x"
:start-y="y"
:is-editable="isEditable"
@initialized="handleDraggableInitializedForBlock(block, $event)"
@dragged="onMoved"
@dragStarted="selectBlock"
@dragEnded="handleDraggableEndedForBlock"
Expand Down Expand Up @@ -131,11 +132,11 @@
'btn-info': exit.destination_block != null,
}"
:is-editable="isEditable"
@initialized="handleDraggableInitializedFor(exit, $event)"
@initialized="handleDraggableInitializedForExit(exit, $event)"
@dragStarted="onCreateExitDragStarted($event, exit)"
@dragged="onCreateExitDragged($event)"
@dragEnded="onCreateExitDragEnded($event, exit)"
@destroyed="handleDraggableDestroyedFor(exit)">
@destroyed="handleDraggableDestroyedForExit(exit)">
<i class="glyphicon glyphicon-move" />
</plain-draggable>

Expand Down Expand Up @@ -259,11 +260,17 @@ export default {
computed: {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reporter: ESLint
Rule: eslint.rules.vue/order-in-components
Severity: WARN
File: src/components/interaction-designer/Block.vue L260

The "computed" property should be above the "watch" property on line 242. (vue/order-in-components)

...mapState('flow', [
'resources',
'selectedBlocks',
'selectedBlockUuids',
]),
...mapState(
'builder',
['activeBlockId', 'operations', 'activeConnectionsContext', 'draggableForExitsByUuid'],
[
'activeBlockId',
'operations',
'activeConnectionsContext',
'draggableForExitsByUuid',
'draggableForBlocksByUuid',
],
),
...mapState({
blockClasses: ({trees: {ui}}) => ui.blockClasses,
Expand All @@ -285,7 +292,7 @@ export default {
},

isBlockSelected() {
return includes(this.selectedBlocks, this.block.uuid)
return includes(this.selectedBlockUuids, this.block.uuid)
},

// todo: does this component know too much, what out of the above mapped state can be mapped?
Expand Down Expand Up @@ -318,6 +325,7 @@ export default {
}),

...mapActions('builder', [
'changeBlockPositionTo',
// ConnectionSourceRelocate
'initializeConnectionSourceRelocateWith',
'setConnectionSourceRelocateValue',
Expand Down Expand Up @@ -455,16 +463,16 @@ export default {
this.setConnectionCreateTargetBlockToNullFrom({block})
},

onMoved({position: {left: x, top: y}}) {
onMoved({position}) {
// todo: try this the vuejs way where we push the change into state, then return false + modify draggable w/in store ?

const {left: x, top: y} = position
const {block} = this
this.$nextTick(() => {
this.setBlockPositionTo({position: {x, y}, block})
this.changeBlockPositionTo({position: {x, y}, block})

forEach(this.draggableForExitsByUuid, (draggable, key) => {
forEach(this.draggableForExitsByUuid, (exitDraggable, key) => {
try {
draggable.position()
exitDraggable.position()
} catch (e) {
console.warn('Block', 'onMoved', 'positioning draggable on', key, 'can\'t access property "initElm", props is undefined')
}
Expand All @@ -480,16 +488,24 @@ export default {
this.labelContainerMaxWidth += 0
},

handleDraggableInitializedFor({uuid}, {draggable}) {
handleDraggableInitializedForBlock(block, {draggable}) {
this.draggableForBlocksByUuid[block.uuid] = draggable

const {left, top} = draggable

console.debug('Block', 'handleDraggableInitializedForBlock', {blockId: block.uuid, coords: {left, top}})
},

handleDraggableInitializedForExit({uuid}, {draggable}) {
this.draggableForExitsByUuid[uuid] = draggable

const {left, top} = draggable
const {uuid: blockId} = this.block

console.debug('Block', 'handleDraggableInitializedFor', {blockId, exitId: uuid, coords: {left, top}})
console.debug('Block', 'handleDraggableInitializedForExit', {blockId, exitId: uuid, coords: {left, top}})
},

handleDraggableDestroyedFor({uuid}) {
handleDraggableDestroyedForExit({uuid}) {
delete this.draggableForExitsByUuid[uuid]
},

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,6 @@ export default class ErrorNotifications extends mixins(Routes, Lang) {
</script>

<style scoped lang="scss">
.error-notifications-wrapper {
z-index: 2 * 10;
}

.notification {
width: 500px;
padding: 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export default class SelectionBanner extends mixins(Lang) {
}

get countSelectedBlocks(): number {
return size(this.selectedBlocks)
return size(this.selectedBlockUuids)
}

async confirmMultipleDeletion(): Promise<void> {
Expand All @@ -83,7 +83,7 @@ export default class SelectionBanner extends mixins(Lang) {
await this.flow_clearMultiSelection()
}

@flowVuexNamespace.State selectedBlocks!: IBlock['uuid'][]
@flowVuexNamespace.State selectedBlockUuids!: IBlock['uuid'][]
@flowVuexNamespace.Action flow_clearMultiSelection!: () => Promise<void>
@flowVuexNamespace.Action flow_removeAllSelectedBlocks!: () => Promise<void>
@flowVuexNamespace.Action flow_duplicateAllSelectedBlocks!: () => Promise<void>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<div
ref="builder-toolbar"
class="tree-builder-toolbar">
class="tree-builder-toolbar d-flex flex-column">
<div class="tree-builder-toolbar-main-menu">
<div
v-if="isImporterVisible"
Expand Down Expand Up @@ -183,7 +183,7 @@
</div>
</div>
</div>
<div class="tree-builder-toolbar-alerts w-100">
<div class="tree-builder-toolbar-alerts w-100 d-flex flex-column">
<selection-banner @updated="handleHeightChangeFromDOM" />
<error-notifications @updated="handleHeightChangeFromDOM" />
</div>
Expand Down
127 changes: 123 additions & 4 deletions src/store/builder/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import {filter, flatMap, get, isEqual, keyBy, map, mapValues, union} from 'lodash'
import {
flatMap, isEqual, keyBy, map, mapValues, get, filter, union, includes, clone, forEach, minBy,
} from 'lodash'
import Vue from 'vue'
import {ActionTree, GetterTree, Module, MutationTree} from 'vuex'
import {IRootState} from '@/store'
Expand Down Expand Up @@ -55,7 +57,9 @@ export interface IBuilderState {
[OperationKind.CONNECTION_CREATE]: IConnectionCreateOperation,
[OperationKind.BLOCK_RELOCATE]: null,
},
draggableForExitsByUuid: object,
toolbarHeight: number,
draggableForExitsByUuid: {[key: string]: object},
draggableForBlocksByUuid: {[key: string]: object},
}

export const stateFactory = (): IBuilderState => ({
Expand All @@ -73,7 +77,9 @@ export const stateFactory = (): IBuilderState => ({
},
[OperationKind.BLOCK_RELOCATE]: null,
},
toolbarHeight: 60,
draggableForExitsByUuid: {},
draggableForBlocksByUuid: {},
})

export const getters: GetterTree<IBuilderState, IRootState> = {
Expand All @@ -89,6 +95,26 @@ export const getters: GetterTree<IBuilderState, IRootState> = {
exitLabelsById: (_state, _getters, {flow: {flows}}) => mapValues(keyBy(flatMap(flows[0].blocks, 'exits'), 'uuid'), 'label'),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reporter: ESLint
Rule: eslint.rules.lodash/prop-shorthand
Severity: WARN
File: src/store/builder/index.ts L95

Do not use property shorthand syntax (lodash/prop-shorthand)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reporter: ESLint
Rule: eslint.rules.lodash/prop-shorthand
Severity: WARN
File: src/store/builder/index.ts L95

Do not use property shorthand syntax (lodash/prop-shorthand)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reporter: ESLint
Rule: eslint.rules.lodash/prop-shorthand
Severity: WARN
File: src/store/builder/index.ts L95

Do not use property shorthand syntax (lodash/prop-shorthand)


isEditable: (state) => state.isEditable,

selectedBlocks(_state, _getters, _rootState, rootGetters) {
return rootGetters['flow/selectedBlocks']
},

selectedBlockAtTheTopPosition(_state, getters) {
return minBy(getters.selectedBlocks, 'vendor_metadata.io_viamo.uiData.yPosition')

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reporter: ESLint
Rule: eslint.rules.lodash/prop-shorthand
Severity: WARN
File: src/store/builder/index.ts L104

Do not use property shorthand syntax (lodash/prop-shorthand)

},

selectedBlockAtTheFurthestLeftPosition(_state, getters) {
return minBy(getters.selectedBlocks, 'vendor_metadata.io_viamo.uiData.xPosition')

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reporter: ESLint
Rule: eslint.rules.lodash/prop-shorthand
Severity: WARN
File: src/store/builder/index.ts L108

Do not use property shorthand syntax (lodash/prop-shorthand)

},

isAnyLeftSpaceAvailable(_state, getters) {
return getters.selectedBlockAtTheFurthestLeftPosition.vendor_metadata.io_viamo.uiData.xPosition > 0
},

isAnyTopSpaceAvailable(state, getters) {
return getters.selectedBlockAtTheTopPosition.vendor_metadata.io_viamo.uiData.yPosition - state.toolbarHeight > 0
Copy link
Member Author

@safydy-r safydy-r Jun 21, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bzabos , your comment about separating pixel computation is still relevant here.
I introduced this toolbarHeight because we would like to know in JS side if there is no more space between the topBlock and the toolbar, and we know the toolbar has a dynamic height.

I tried your proposal

Using the same logic as we already use to show/hide alerts-toolbar, we can (create, then) apply .has-notification-alerts-banner class to canvas which can add a margin-top of whatever the magic number is. This mitigates mixing javascript and css, keeps pixel variations within css, and avoids having those magic numbers sprinkled throughout our formulas and vuex state.

... but I cannot resolve the challenge of removing this formula getters.selectedBlockAtTheTopPosition.vendor_metadata.io_viamo.uiData.yPosition - state.toolbarHeight > 0 from JS. Any idea ?

Screen Shot 2021-06-21 at 3 08 05 PM

},
}

export const mutations: MutationTree<IBuilderState> = {
Expand Down Expand Up @@ -119,8 +145,14 @@ export const mutations: MutationTree<IBuilderState> = {
// Vue.observable(block.vendor_metadata.io_viamo.uiData)
// }

block.vendor_metadata.io_viamo.uiData.xPosition = x
block.vendor_metadata.io_viamo.uiData.yPosition = y
if (x !== undefined) {
// eslint-disable-next-line no-param-reassign
block.vendor_metadata.io_viamo.uiData.xPosition = x
}
if (y !== undefined) {
// eslint-disable-next-line no-param-reassign
block.vendor_metadata.io_viamo.uiData.yPosition = y
}
},

setIsEditable(state, value) {
Expand All @@ -130,9 +162,96 @@ export const mutations: MutationTree<IBuilderState> = {
initDraggableForExitsByUuid(state) {
state.draggableForExitsByUuid = {}
},

updateBlockDraggablePosition(state, {uuid, position}: { uuid: IBlock['uuid'], position: IPosition }) {
console.debug('Builder', 'updateBlockDraggablePosition for ', state.draggableForBlocksByUuid[uuid], 'to ', position)
Object.assign(state.draggableForBlocksByUuid[uuid], {left: position.x, top: position.y})
},

updateToolBarHeight(state, height) {
state.toolbarHeight = height
},
}

export const actions: ActionTree<IBuilderState, IRootState> = {
changeBlockPositionTo({commit, getters, dispatch, rootState}, {position: {x, y}, block}) {
if (!includes(rootState.flow.selectedBlockUuids, block.uuid)) {
commit('setBlockPositionTo', {position: {x, y}, block})
return
}

// The block is selected
// Store UI Position
const initialPosition = {
x: clone(block.vendor_metadata.io_viamo.uiData.xPosition),
y: clone(block.vendor_metadata.io_viamo.uiData.yPosition),
} as IPosition

// Prepare translation
const translationDelta: IPosition = {
x: x - initialPosition.x,
y: y - initialPosition.y,
}

let shouldReversePosition = false
const isMovingToTheRight = translationDelta.x > 0
if (isMovingToTheRight || getters.isAnyLeftSpaceAvailable) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reporter: ESLint
Rule: eslint.rules.@typescript-eslint/strict-boolean-expressions
Severity: ERROR
File: src/store/builder/index.ts L198

Unexpected any value in conditional. An explicit comparison or type cast is required. (@typescript-eslint/strict-boolean-expressions)

commit('setBlockPositionTo', {position: {x}, block})
} else {
translationDelta.x = 0
commit('setBlockPositionTo', {position: {x: initialPosition.x}, block})
shouldReversePosition = true
}

const isMovingToTheTop = translationDelta.y > 0
if (isMovingToTheTop || getters.isAnyTopSpaceAvailable) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reporter: ESLint
Rule: eslint.rules.@typescript-eslint/strict-boolean-expressions
Severity: ERROR
File: src/store/builder/index.ts L207

Unexpected any value in conditional. An explicit comparison or type cast is required. (@typescript-eslint/strict-boolean-expressions)

commit('setBlockPositionTo', {position: {y}, block})
} else {
translationDelta.y = 0
commit('setBlockPositionTo', {position: {y: initialPosition.y}, block})
shouldReversePosition = true
}

// Reverse the draggable position
if (shouldReversePosition) {
commit('updateBlockDraggablePosition', {
uuid: block.uuid,
position: {
x: block.vendor_metadata.io_viamo.uiData.xPosition,
y: block.vendor_metadata.io_viamo.uiData.yPosition,
},
})
}

// Translate other selected blocks
forEach(getters.selectedBlocks, (currentBlock: IBlock) => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reporter: ESLint
Rule: eslint.rules.lodash/prefer-filter
Severity: ERROR
File: src/store/builder/index.ts L227

Prefer _.filter or _.some over an if statement inside a _.forEach (lodash/prefer-filter)

if (currentBlock.uuid !== block.uuid) {
dispatch('setBlockAndSyncDraggablePositionFromDelta', {delta: translationDelta, block: currentBlock})
}
})
},

setBlockAndSyncDraggablePositionFromDelta({commit}, {delta: {x, y}, block}) {
const {
vendor_metadata: {
io_viamo: {
uiData: {
xPosition: initialXPosition,
yPosition: initialYPosition,
},
},
},
} = block

const newPosition: IPosition = {
x: initialXPosition + x,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reporter: ESLint
Rule: eslint.rules.@typescript-eslint/restrict-plus-operands
Severity: ERROR
File: src/store/builder/index.ts L247

Operands of '+' operation must either be both strings or both numbers. (@typescript-eslint/restrict-plus-operands)

y: initialYPosition + y,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reporter: ESLint
Rule: eslint.rules.@typescript-eslint/restrict-plus-operands
Severity: ERROR
File: src/store/builder/index.ts L248

Operands of '+' operation must either be both strings or both numbers. (@typescript-eslint/restrict-plus-operands)

}

commit('setBlockPositionTo', {position: newPosition, block})
commit('updateBlockDraggablePosition', {uuid: block.uuid, position: newPosition})
},

removeConnectionFrom({commit}, {block: {uuid: blockId}, exit: {uuid: exitId}}) {
commit('flow/block_setBlockExitDestinationBlockId', {
blockId,
Expand Down
4 changes: 2 additions & 2 deletions src/store/flow/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,12 +150,12 @@ export const actions: ActionTree<IFlowsState, IRootState> = {
},

async block_select({state}, {blockId}: { blockId: IBlock['uuid'] }) {
state.selectedBlocks.push(blockId)
state.selectedBlockUuids.push(blockId)
},

async block_deselect({state}, {blockId}: { blockId: IBlock['uuid'] }) {
// remove it
state.selectedBlocks = state.selectedBlocks.filter((item) => item !== blockId)
state.selectedBlockUuids = state.selectedBlockUuids.filter((item) => item !== blockId)
},
}

Expand Down
17 changes: 9 additions & 8 deletions src/store/flow/flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {IdGeneratorUuidV4} from '@floip/flow-runner/dist/domain/IdGeneratorUuidV
import moment from 'moment'
import {ActionTree, GetterTree, MutationTree} from 'vuex'
import {IRootState} from '@/store'
import {cloneDeep, defaults, every, forEach, get, has, includes, omit} from 'lodash'
import {cloneDeep, defaults, every, forEach, filter, get, has, includes, minBy, omit} from 'lodash'
import {discoverContentTypesFor} from '@/store/flow/resource'
import {computeBlockUiData} from '@/store/builder'
import {IFlowsState} from '.'
Expand Down Expand Up @@ -63,6 +63,7 @@ export const getters: GetterTree<IFlowsState, IRootState> = {
hasVoiceMode: (state, getters) => includes(getters.activeFlow.supported_modes || [], SupportedMode.IVR),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reporter: ESLint
Rule: eslint.rules.@typescript-eslint/strict-boolean-expressions
Severity: ERROR
File: src/store/flow/flow.ts L63

Unexpected any value in conditional. An explicit comparison or type cast is required. (@typescript-eslint/strict-boolean-expressions)

hasOfflineMode: (state, getters) => includes(getters.activeFlow.supported_modes || [], SupportedMode.OFFLINE),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reporter: ESLint
Rule: eslint.rules.@typescript-eslint/strict-boolean-expressions
Severity: ERROR
File: src/store/flow/flow.ts L64

Unexpected any value in conditional. An explicit comparison or type cast is required. (@typescript-eslint/strict-boolean-expressions)

currentFlowsState: (state) => state,
selectedBlocks: (state, getters) => filter(getters.activeFlow.blocks, (block) => includes(state.selectedBlockUuids, block.uuid)),
}

export const mutations: MutationTree<IFlowsState> = {
Expand Down Expand Up @@ -363,26 +364,26 @@ export const actions: ActionTree<IFlowsState, IRootState> = {
},

async flow_clearMultiSelection({state, dispatch}) {
forEach(state.selectedBlocks, (blockId: IBlock['uuid']) => {
forEach(state.selectedBlockUuids, (blockId: IBlock['uuid']) => {
dispatch('block_deselect', {blockId})
})
},

async flow_removeAllSelectedBlocks({state, dispatch}) {
forEach(state.selectedBlocks, (blockId: IBlock['uuid']) => {
forEach(state.selectedBlockUuids, (blockId: IBlock['uuid']) => {
dispatch('flow_removeBlock', {blockId})
})

state.selectedBlocks = []
state.selectedBlockUuids = []
},

async flow_duplicateAllSelectedBlocks({state, dispatch}) {
const newBlocksUuid: string[] = []
forEach(state.selectedBlocks, async (blockId: IBlock['uuid']) => {
const newBlocksUuids: string[] = []
forEach(state.selectedBlockUuids, async (blockId: IBlock['uuid']) => {
const duplicatedBlock: IBlock = await dispatch('flow_duplicateBlock', {blockId})
newBlocksUuid.push(duplicatedBlock.uuid)
newBlocksUuids.push(duplicatedBlock.uuid)
})
state.selectedBlocks = newBlocksUuid
state.selectedBlockUuids = newBlocksUuids
},
}

Expand Down
Loading