Flexible Layout Refactor (#2223)

* only store identifiers in frames

* move drop hints outside frame

* fix resizing

* fix drag and drop frames

* fix reordering of columns

* multiple improvements

* fix styling for drop hint in empty container

* fix frame reorder in same column

* better drop target show logic

* better frame drop to logic

* fix container reordering

* add uuids for frames and containers to prevent clashing

* toolbar updates

* use shared subobject component to ease styling

* add type cssClass to subobject component header, and delete frame header vue file

* add context menu in subobject views

* change height and width to size for both frames and containers

* remove uneccesary methods from resizeHanfle and inline logic instead

* remove left click logic from context-menu mixin, add a click handler to dropDownContextMenu to show menu

* make a mixin to listen for isEditing

* encapsulate drop hints, and pass allowDrop logic to check if drop hint is valid

* use event.dataTransfer instead of vue events for container reordering

* remove vue events for frame dnd and use html events

* better implementation of toolbar

* remove unused event

* fix container resizing when adding new container

* make reviewer requested changes

* add containerId to event vs having a JSON object

* watch domainObject from flexible layouts and pass down to components

* change domainObject directly on add Container

* update domainObject on change, and cahnge toolBarProvider to function that returns an object

* fix plugin

* set domainObject as data property in felxibleLayouts

* use class instead of inline styles

* Cleanup code

inline this.$el for components that measure their own size
replace snapToPercentage with Math.round
inject object as layoutObject, not dObject.

* reuse sizing logic between frames and containers

* clean up handlers

split handlers for createFrame and moveFrame events in container.
reorganize methods for each to clarify how they operate.

* ObjectView only stops propagation when it handles event

* use ids in toolbar to ensure correct items are mutated

Because index may change due to drag and drop events,
deleteFrame and deleteContainer should operate using identifiers
instead of index.

Also, generate path for hasFrame using indexes when object is
selected, otherwise hasFrame may refer to the incorrect path.
This commit is contained in:
Deep Tailor
2018-12-07 09:34:33 -08:00
committed by Pete Richards
parent e07cfc9394
commit a87fc51fbb
18 changed files with 674 additions and 619 deletions

View File

@@ -22,47 +22,52 @@
<template>
<div class="c-fl-container"
:style="[{'flex-basis': size}]"
:class="{'is-empty': frames.length === 1}">
:style="[{'flex-basis': sizeString}]"
:class="{'is-empty': !frames.length}">
<div class="c-fl-container__header icon-grippy-ew"
v-show="isEditing"
draggable="true"
@dragstart="startContainerDrag"
@dragend="stopContainerDrag">
<span class="c-fl-container__size-indicator">{{ size }}</span>
@dragstart="startContainerDrag">
<span class="c-fl-container__size-indicator">{{ sizeString }}</span>
</div>
<drop-hint
class="c-fl-frame__drop-hint"
:index="-1"
:allow-drop="allowDrop"
@object-drop-to="moveOrCreateFrame">
</drop-hint>
<div class="c-fl-container__frames-holder">
<div class="u-contents"
v-for="(frame, i) in frames"
:key="i">
<template
v-for="(frame, i) in frames">
<frame-component
class="c-fl-container__frame"
:style="{
'flex-basis': `${frame.height}%`
}"
:key="frame.id"
:frame="frame"
:size="frame.height"
:index="i"
:containerIndex="index"
:isEditing="isEditing"
:isDragging="isDragging"
@frame-drag-from="frameDragFrom"
@frame-drop-to="frameDropTo"
@delete-frame="promptBeforeDeletingFrame"
@add-container="addContainer">
:containerIndex="index">
</frame-component>
<drop-hint
class="c-fl-frame__drop-hint"
:key="i"
:index="i"
:allowDrop="allowDrop"
@object-drop-to="moveOrCreateFrame">
</drop-hint>
<resize-handle
v-if="i !== 0 && (i !== frames.length - 1)"
v-show="isEditing"
v-if="(i !== frames.length - 1)"
:key="i"
:index="i"
:orientation="rowsLayout ? 'horizontal' : 'vertical'"
@init-move="startFrameResizing"
@move="frameResizing"
@end-move="endFrameResizing">
</resize-handle>
</div>
</template>
</div>
</div>
</template>
@@ -71,61 +76,98 @@
import FrameComponent from './frame.vue';
import Frame from '../utils/frame';
import ResizeHandle from './resizeHandle.vue';
import DropHint from './dropHint.vue';
import isEditingMixin from '../mixins/isEditing';
const SNAP_TO_PERCENTAGE = 1;
const MIN_FRAME_SIZE = 5;
export default {
inject:['openmct', 'domainObject'],
props: ['size', 'frames', 'index', 'isEditing', 'isDragging', 'rowsLayout'],
inject:['openmct'],
props: ['container', 'index', 'rowsLayout'],
mixins: [isEditingMixin],
components: {
FrameComponent,
ResizeHandle
ResizeHandle,
DropHint
},
data() {
return {
initialPos: 0,
frameIndex: 0,
maxMoveSize: 0
computed: {
frames() {
return this.container.frames;
},
sizeString() {
return `${Math.round(this.container.size)}%`
}
},
methods: {
frameDragFrom(frameIndex) {
this.$emit('frame-drag-from', this.index, frameIndex);
},
frameDropTo(frameIndex, event) {
let domainObject = event.dataTransfer.getData('domainObject'),
frameObject;
allowDrop(event, index) {
if (event.dataTransfer.getData('domainObject')) {
return true;
}
let frameId = event.dataTransfer.getData('frameid'),
containerIndex = Number(event.dataTransfer.getData('containerIndex'));
if (domainObject) {
frameObject = new Frame(JSON.parse(domainObject));
if (!frameId) {
return false;
}
this.$emit('frame-drop-to', this.index, frameIndex, frameObject);
if (containerIndex === this.index) {
let frame = this.container.frames.filter((f) => f.id === frameId)[0],
framePos = this.container.frames.indexOf(frame);
if (index === -1) {
return framePos !== 0;
} else {
return framePos !== index && (framePos - 1) !== index
}
} else {
return true;
}
},
moveOrCreateFrame(insertIndex, event) {
if (event.dataTransfer.types.includes('domainobject')) {
// create frame using domain object
let domainObject = JSON.parse(event.dataTransfer.getData('domainObject'));
this.$emit(
'create-frame',
this.index,
insertIndex,
domainObject.identifier
);
return;
};
// move frame.
let frameId = event.dataTransfer.getData('frameid');
let containerIndex = Number(event.dataTransfer.getData('containerIndex'));
this.$emit(
'move-frame',
this.index,
insertIndex,
frameId,
containerIndex
);
},
startFrameResizing(index) {
let beforeFrame = this.frames[index],
afterFrame = this.frames[index + 1];
this.maxMoveSize = beforeFrame.height + afterFrame.height;
this.maxMoveSize = beforeFrame.size + afterFrame.size;
},
frameResizing(index, delta, event) {
let percentageMoved = (delta / this.getElSize(this.$el))*100,
let percentageMoved = Math.round(delta / this.getElSize() * 100),
beforeFrame = this.frames[index],
afterFrame = this.frames[index + 1];
beforeFrame.height = this.snapToPercentage(beforeFrame.height + percentageMoved);
afterFrame.height = this.snapToPercentage(afterFrame.height - percentageMoved);
beforeFrame.size = this.getFrameSize(beforeFrame.size + percentageMoved);
afterFrame.size = this.getFrameSize(afterFrame.size - percentageMoved);
},
endFrameResizing(index, event) {
this.persist();
},
getElSize(el) {
getElSize() {
if (this.rowsLayout) {
return el.offsetWidth;
return this.$el.offsetWidth;
} else {
return el.offsetHeight;
return this.$el.offsetHeight;
}
},
getFrameSize(size) {
@@ -137,76 +179,23 @@ export default {
return size;
}
},
snapToPercentage(value){
let rem = value % SNAP_TO_PERCENTAGE,
roundedValue;
if (rem < 0.5) {
roundedValue = Math.floor(value/SNAP_TO_PERCENTAGE)*SNAP_TO_PERCENTAGE;
} else {
roundedValue = Math.ceil(value/SNAP_TO_PERCENTAGE)*SNAP_TO_PERCENTAGE;
}
return this.getFrameSize(roundedValue);
},
persist() {
this.$emit('persist', this.index);
},
promptBeforeDeletingFrame(frameIndex) {
let deleteFrame = this.deleteFrame;
let prompt = this.openmct.overlays.dialog({
iconClass: 'alert',
message: `This action will remove ${this.frames[frameIndex].domainObject.name} from this Flexible Layout. Do you want to continue?`,
buttons: [
{
label: 'Ok',
emphasis: 'true',
callback: function () {
deleteFrame(frameIndex);
prompt.dismiss();
},
},
{
label: 'Cancel',
callback: function () {
prompt.dismiss();
}
}
]
});
},
deleteFrame(frameIndex) {
this.frames.splice(frameIndex, 1);
this.$parent.recalculateOldFrameSize(this.frames);
this.persist();
},
deleteContainer() {
this.$emit('delete-container', this.index);
},
addContainer() {
this.$emit('add-container', this.index);
},
startContainerDrag(event) {
event.stopPropagation();
this.$emit('start-container-drag', this.index);
},
stopContainerDrag(event) {
event.stopPropagation();
this.$emit('stop-container-drag');
event.dataTransfer.setData('containerid', this.container.id);
}
},
mounted() {
let context = {
item: this.domainObject,
method: this.deleteContainer,
item: this.$parent.domainObject,
addContainer: this.addContainer,
index: this.index,
type: 'container'
type: 'container',
containerId: this.container.id
}
this.unsubscribeSelection = this.openmct.selection.selectable(this.$el, context, false);
},
},
beforeDestroy() {
this.unsubscribeSelection();
}