mirror of
https://github.com/nasa/openmct.git
synced 2024-12-18 20:57:53 +00:00
Flexible Layout (#2201)
* first cut of flexible layout * better drag handling * add drop targets to every row * enable drag and drop between columns and rows * enable persistance * add editing capability * chage rows to frames and columns to containers, switch draggable to whole frame object * Merge latest, resolve conflicts. Need to just apply these changes to Deep's branch and push * enhancements to drag targets * WIP in flexibleLayout, container.vue files - Refined classes and markup; - min-width changed to flex-basis; - Added toggle direction button; * Significant progress but still WIP - Refined classes and markup; - Layout toggling working; - Add Container working properly; - TODOs: fix sizing in empty container, fix bordering, more refinements; * add resizing of frames - still wip * Significant enhancements - Moved all CSS into flexibleLayout.vue; - Layout now improved for empty container and drop hints; - Proportional sizing now better for frames and containers; * Resize handle WIP * abstract splitter and logic into self contained component that will emit an event when mouse is moving * Resize handle WIP - Minor tweak to handle padding and hover; * add container resize todo persist * persist container resize * add frame header, fix column resize on last column * Refinements to resize-handle - Fixed sizing; - Transition on hover; - TODOs: needs is-dragging to maintain hover style while dragging; * fix drop hints showing after drop * move header * improve mouse move gesture * Added frame size indicator * add snapto functionality * Refined container and frame size indicators - Also added overflow handling to l-grid-view * improve resizing logic * add selection on frames * Various resizing-frames related - Fixed overflow - now frame widths can be collapsed to 5% minimum; - Sizing indicators refined, better positioning and layout; - Added grippy drag indicators to column heads; - TODOs: add column head cursors and hover effects, hide indicators when not in edit mode, handle nested layout and flex layouts while editing * Selecting and emtpy layout messaging - Better empty layout message; - Moved s-selected to proper element in c-fl-frame; * Drop-hint and sizing related various - Drop-hints for first placeholder container now display; - Drop-hints moved into drag-wrapper; * add delete frame * Editing various - Adjust Snow theme constants related to editing; - Changed delete message wording; * Updated icon and added description * add toggle and remove container to toolbar * miscellaneous cleanup * add container button to toolbar * improve toolbar * code cleanup in plugin.js * Various icons, toolbar separator - Copied in c-toolbar__separator and associated changes in _controls from Pegah's layout_alpha branch - may have conflicts later. - Added separator to FL toolbar; - Updated icons for grippy-ew, toolbar icons; * add check for empty containers" * logic to resize frames on drop * fix delete frame and persisting toolbar * Significant changes to edit / selection styling - Both Flexible and fixed Display Layouts addressed; - Both themes addressed; - Changed drop-hint icon to icon-plus; * add correct icons to frame header and fix toolbars showing up in wrong views * Moving and resizing various - Cursors; - Grippy added to frame resize-handle, WIP!; * add container reordering * add frame/no frame support to toolbar' * fix regression of resize handles showing after last frame in container * force selection of flexible-layout when editing is first clicked, to apply correct toolbar * make changes to simplify toolbar * Modified sizing algorithm slightly * make changes reviewer requested * fix regression that causes top drop hint to not show * remove unused variables and bind events to vue * unsub selection before destroy
This commit is contained in:
parent
d13d59bfa0
commit
1069a45cfc
@ -78,6 +78,7 @@
|
||||
openmct.install(openmct.plugins.Notebook());
|
||||
openmct.install(openmct.plugins.FolderView());
|
||||
openmct.install(openmct.plugins.Tabs());
|
||||
openmct.install(openmct.plugins.FlexibleLayout());
|
||||
openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0});
|
||||
openmct.time.timeSystem('utc');
|
||||
openmct.start();
|
||||
|
214
src/plugins/flexibleLayout/components/container.vue
Normal file
214
src/plugins/flexibleLayout/components/container.vue
Normal file
@ -0,0 +1,214 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<div class="c-fl-container"
|
||||
:style="[{'flex-basis': size}]"
|
||||
:class="{'is-empty': frames.length === 1}">
|
||||
<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>
|
||||
</div>
|
||||
<div class="c-fl-container__frames-holder">
|
||||
<div class="u-contents"
|
||||
v-for="(frame, i) in frames"
|
||||
:key="i">
|
||||
|
||||
<frame-component
|
||||
class="c-fl-container__frame"
|
||||
:style="{
|
||||
'flex-basis': `${frame.height}%`
|
||||
}"
|
||||
: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">
|
||||
</frame-component>
|
||||
|
||||
<resize-handle
|
||||
v-if="i !== 0 && (i !== frames.length - 1)"
|
||||
v-show="isEditing"
|
||||
:index="i"
|
||||
:orientation="rowsLayout ? 'horizontal' : 'vertical'"
|
||||
@init-move="startFrameResizing"
|
||||
@move="frameResizing"
|
||||
@end-move="endFrameResizing">
|
||||
</resize-handle>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FrameComponent from './frame.vue';
|
||||
import Frame from '../utils/frame';
|
||||
import ResizeHandle from './resizeHandle.vue';
|
||||
|
||||
const SNAP_TO_PERCENTAGE = 1;
|
||||
const MIN_FRAME_SIZE = 5;
|
||||
|
||||
export default {
|
||||
inject:['openmct', 'domainObject'],
|
||||
props: ['size', 'frames', 'index', 'isEditing', 'isDragging', 'rowsLayout'],
|
||||
components: {
|
||||
FrameComponent,
|
||||
ResizeHandle
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
initialPos: 0,
|
||||
frameIndex: 0,
|
||||
maxMoveSize: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
frameDragFrom(frameIndex) {
|
||||
this.$emit('frame-drag-from', this.index, frameIndex);
|
||||
},
|
||||
frameDropTo(frameIndex, event) {
|
||||
let domainObject = event.dataTransfer.getData('domainObject'),
|
||||
frameObject;
|
||||
|
||||
if (domainObject) {
|
||||
frameObject = new Frame(JSON.parse(domainObject));
|
||||
}
|
||||
|
||||
this.$emit('frame-drop-to', this.index, frameIndex, frameObject);
|
||||
},
|
||||
startFrameResizing(index) {
|
||||
let beforeFrame = this.frames[index],
|
||||
afterFrame = this.frames[index + 1];
|
||||
|
||||
this.maxMoveSize = beforeFrame.height + afterFrame.height;
|
||||
},
|
||||
frameResizing(index, delta, event) {
|
||||
|
||||
let percentageMoved = (delta / this.getElSize(this.$el))*100,
|
||||
beforeFrame = this.frames[index],
|
||||
afterFrame = this.frames[index + 1];
|
||||
|
||||
beforeFrame.height = this.snapToPercentage(beforeFrame.height + percentageMoved);
|
||||
afterFrame.height = this.snapToPercentage(afterFrame.height - percentageMoved);
|
||||
},
|
||||
endFrameResizing(index, event) {
|
||||
this.persist();
|
||||
},
|
||||
getElSize(el) {
|
||||
if (this.rowsLayout) {
|
||||
return el.offsetWidth;
|
||||
} else {
|
||||
return el.offsetHeight;
|
||||
}
|
||||
},
|
||||
getFrameSize(size) {
|
||||
if (size < MIN_FRAME_SIZE) {
|
||||
return MIN_FRAME_SIZE
|
||||
} else if (size > (this.maxMoveSize - MIN_FRAME_SIZE)) {
|
||||
return (this.maxMoveSize - MIN_FRAME_SIZE);
|
||||
} else {
|
||||
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');
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
let context = {
|
||||
item: this.domainObject,
|
||||
method: this.deleteContainer,
|
||||
addContainer: this.addContainer,
|
||||
index: this.index,
|
||||
type: 'container'
|
||||
}
|
||||
|
||||
this.unsubscribeSelection = this.openmct.selection.selectable(this.$el, context, false);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.unsubscribeSelection();
|
||||
}
|
||||
}
|
||||
</script>
|
58
src/plugins/flexibleLayout/components/dropHint.vue
Normal file
58
src/plugins/flexibleLayout/components/dropHint.vue
Normal file
@ -0,0 +1,58 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="c-drop-hint c-drop-hint--always-show"
|
||||
:class="{'is-mouse-over': isMouseOver}"
|
||||
@dragenter="dragenter"
|
||||
@dragleave="dragleave"
|
||||
@drop="dropHandler">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props:['index'],
|
||||
data() {
|
||||
return {
|
||||
isMouseOver: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
dragenter() {
|
||||
this.isMouseOver = true;
|
||||
},
|
||||
dragleave() {
|
||||
this.isMouseOver = false;
|
||||
},
|
||||
dropHandler(event) {
|
||||
this.$emit('object-drop-to', event, this.index);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
684
src/plugins/flexibleLayout/components/flexibleLayout.vue
Normal file
684
src/plugins/flexibleLayout/components/flexibleLayout.vue
Normal file
@ -0,0 +1,684 @@
|
||||
<template>
|
||||
<div class="c-fl">
|
||||
<div class="c-fl__empty"
|
||||
v-if="areAllContainersEmpty()">
|
||||
<span class="c-fl__empty-message">This Flexible Layout is currently empty</span>
|
||||
</div>
|
||||
|
||||
<div class="c-fl__container-holder"
|
||||
:class="{
|
||||
'c-fl--rows': rowsLayout === true
|
||||
}">
|
||||
|
||||
<div class="u-contents"
|
||||
v-for="(container, index) in containers"
|
||||
:key="index">
|
||||
|
||||
<drop-hint
|
||||
style="flex-basis: 15px;"
|
||||
v-if="index === 0 && containers.length > 1"
|
||||
v-show="isContainerDragging"
|
||||
:index="-1"
|
||||
@object-drop-to="containerDropTo">
|
||||
</drop-hint>
|
||||
|
||||
<container-component
|
||||
class="c-fl__container"
|
||||
ref="containerComponent"
|
||||
:index="index"
|
||||
:size="`${Math.round(container.width)}%`"
|
||||
:frames="container.frames"
|
||||
:isEditing="isEditing"
|
||||
:isDragging="isDragging"
|
||||
:rowsLayout="rowsLayout"
|
||||
@addFrame="addFrame"
|
||||
@frame-drag-from="frameDragFromHandler"
|
||||
@frame-drop-to="frameDropToHandler"
|
||||
@persist="persist"
|
||||
@delete-container="promptBeforeDeletingContainer"
|
||||
@add-container="addContainer"
|
||||
@start-container-drag="startContainerDrag"
|
||||
@stop-container-drag="stopContainerDrag">
|
||||
</container-component>
|
||||
|
||||
<resize-handle
|
||||
v-if="index !== (containers.length - 1)"
|
||||
v-show="isEditing"
|
||||
:index="index"
|
||||
:orientation="rowsLayout ? 'vertical' : 'horizontal'"
|
||||
@init-move="startContainerResizing"
|
||||
@move="containerResizing"
|
||||
@end-move="endContainerResizing">
|
||||
</resize-handle>
|
||||
|
||||
<drop-hint
|
||||
style="flex-basis: 15px;"
|
||||
v-if="containers.length > 1"
|
||||
v-show="isContainerDragging"
|
||||
:index="index"
|
||||
@object-drop-to="containerDropTo">
|
||||
</drop-hint>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~styles/sass-base';
|
||||
|
||||
.c-fl {
|
||||
@include abs();
|
||||
display: flex;
|
||||
flex-direction: column; // TEMP: only needed to support temp-toolbar element
|
||||
|
||||
> * + * { margin-top: $interiorMargin; }
|
||||
|
||||
.temp-toolbar {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
&__container-holder {
|
||||
display: flex;
|
||||
flex: 1 1 100%; // Must needs to be 100% to work
|
||||
|
||||
// Columns by default
|
||||
flex-direction: row;
|
||||
> * + * { margin-left: 1px; }
|
||||
|
||||
&[class*='--rows'] {
|
||||
//@include test(blue, 0.1);
|
||||
flex-direction: column;
|
||||
> * + * {
|
||||
margin-left: 0;
|
||||
margin-top: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__empty {
|
||||
@include abs();
|
||||
background: rgba($colorBodyFg, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
|
||||
> * {
|
||||
font-style: italic;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.c-fl-container {
|
||||
/***************************************************** CONTAINERS */
|
||||
$headerSize: 16px;
|
||||
|
||||
border: 1px solid transparent;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
|
||||
// flex-basis is set with inline style in code, controls size
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
|
||||
&__header {
|
||||
// Only displayed when editing
|
||||
background: $editSelectableColor;
|
||||
color: $editSelectableColorFg;
|
||||
cursor: move;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 0 0 $headerSize;
|
||||
|
||||
&:before {
|
||||
// Drag grippy
|
||||
font-size: 0.8em;
|
||||
opacity: 0.5;
|
||||
position: absolute;
|
||||
left: 50%; top: 50%;
|
||||
transform-origin: center;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
|
||||
&__size-indicator {
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
right: $interiorMargin;
|
||||
}
|
||||
|
||||
&__frames-holder {
|
||||
display: flex;
|
||||
flex: 1 1 100%; // Must be 100% to work
|
||||
flex-direction: column; // Default
|
||||
align-content: stretch;
|
||||
align-items: stretch;
|
||||
overflow: hidden; // This sucks, but doing in the short-term
|
||||
}
|
||||
|
||||
.is-editing & {
|
||||
//background: $editCanvasColorBg;
|
||||
border-color: $editSelectableColor;
|
||||
|
||||
&:hover {
|
||||
border-color: $editSelectableColorHov;
|
||||
}
|
||||
|
||||
&[s-selected] {
|
||||
border-color: $editSelectableColorSelected;
|
||||
|
||||
.c-fl-container__header {
|
||||
background: $editSelectableColorSelected;
|
||||
color: $editSelectableColorSelectedFg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/****** THEIR FRAMES */
|
||||
// Frames get styled here because this is particular to their presence in this layout type
|
||||
.c-fl-frame {
|
||||
@include browserPrefix(margin-collapse, collapse);
|
||||
margin: 1px;
|
||||
|
||||
//&__drag-wrapper {
|
||||
// border: 1px solid $colorInteriorBorder; // Now handled by is-selectable
|
||||
//}
|
||||
}
|
||||
|
||||
/****** ROWS LAYOUT */
|
||||
.c-fl--rows & {
|
||||
// Layout is rows
|
||||
flex-direction: row;
|
||||
|
||||
&__header {
|
||||
flex-basis: $headerSize;
|
||||
overflow: hidden;
|
||||
|
||||
&:before {
|
||||
// Drag grippy
|
||||
transform: rotate(90deg) translate(-50%, 50%);
|
||||
}
|
||||
}
|
||||
|
||||
&__size-indicator {
|
||||
right: 0;
|
||||
top: $interiorMargin;
|
||||
transform-origin: top right;
|
||||
transform: rotate(-90deg) translateY(-100%);
|
||||
}
|
||||
|
||||
&__frames-holder {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.c-fl-frame {
|
||||
$sizeIndicatorM: 16px;
|
||||
$dropHintSize: 15px;
|
||||
|
||||
display: flex;
|
||||
justify-content: stretch;
|
||||
align-items: stretch;
|
||||
flex: 1 1;
|
||||
flex-direction: column;
|
||||
overflow: hidden; // Needed to allow frames to collapse when sized down
|
||||
|
||||
&__drag-wrapper {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
|
||||
.is-editing & {
|
||||
> * {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__size-indicator {
|
||||
$size: 35px;
|
||||
|
||||
@include ellipsize();
|
||||
background: $colorBtnBg;
|
||||
border-top-left-radius: $controlCr;
|
||||
color: $colorBtnFg;
|
||||
display: inline-block;
|
||||
padding: $interiorMarginSm 0;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
text-align: center;
|
||||
width: $size;
|
||||
z-index: 2;
|
||||
|
||||
// Changed when layout is different, see below
|
||||
border-top-right-radius: $controlCr;
|
||||
bottom: 1px;
|
||||
right: $sizeIndicatorM;
|
||||
}
|
||||
|
||||
&__drop-hint {
|
||||
flex: 0 0 $dropHintSize;
|
||||
.c-drop-hint {
|
||||
border-radius: $smallCr;
|
||||
}
|
||||
}
|
||||
|
||||
&__resize-handle {
|
||||
$size: 2px;
|
||||
$margin: 3px;
|
||||
$marginHov: 0;
|
||||
$grippyThickness: $size + 6;
|
||||
$grippyLen: $grippyThickness * 2;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 0 0 ($margin * 2) + $size;
|
||||
transition: $transOut;
|
||||
|
||||
&:before {
|
||||
// The visible resize line
|
||||
background: $editColor;
|
||||
content: '';
|
||||
display: block;
|
||||
flex: 1 1 auto;
|
||||
min-height: $size; min-width: $size;
|
||||
}
|
||||
|
||||
&:after {
|
||||
// Grippy element
|
||||
/*background: deeppink;*/
|
||||
$c: black;
|
||||
$a: 0.9;
|
||||
$d: 5px;
|
||||
background: $editColor;
|
||||
color: $editColorBg;
|
||||
border-radius: $smallCr;
|
||||
content: $glyph-icon-grippy-ew;
|
||||
font-family: symbolsfont;
|
||||
font-size: 0.8em;
|
||||
display: inline-block;
|
||||
padding: 10px 0;
|
||||
position: absolute;
|
||||
left: 50%; top: 50%;
|
||||
text-align: center;
|
||||
transform-origin: center center;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
&.vertical {
|
||||
padding: $margin $size;
|
||||
&:hover{
|
||||
// padding: $marginHov 0;
|
||||
cursor: row-resize;
|
||||
}
|
||||
|
||||
&:after {
|
||||
transform: rotate(90deg) translate(-50%, -50%);
|
||||
//top: $margin + $size - 2px;
|
||||
//left: 50%;
|
||||
// transform: translateX(-50%);
|
||||
/*width: $grippyLen;
|
||||
height: $grippyThickness;*/
|
||||
}
|
||||
}
|
||||
|
||||
&.horizontal {
|
||||
padding: $size $margin;
|
||||
&:hover{
|
||||
// padding: 0 $marginHov;
|
||||
cursor: col-resize;
|
||||
}
|
||||
|
||||
&:after {
|
||||
//left: $margin + $size - 2px;
|
||||
//top: 50%;
|
||||
//transform: translateY(-50%);
|
||||
/* height: $grippyLen;
|
||||
width: $grippyThickness;*/
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transition: $transOut;
|
||||
&:before {
|
||||
// The visible resize line
|
||||
background: $editColorHov;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hide the resize-handles in first and last c-fl-frame elements
|
||||
&:first-child,
|
||||
&:last-child {
|
||||
.c-fl-frame__resize-handle {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.c-fl--rows & {
|
||||
flex-direction: row;
|
||||
|
||||
&__size-indicator {
|
||||
border-bottom-left-radius: $controlCr;
|
||||
border-top-right-radius: 0;
|
||||
bottom: $sizeIndicatorM;
|
||||
right: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
&--first-in-container {
|
||||
border: none;
|
||||
flex: 0 0 0;
|
||||
.c-fl-frame__drag-wrapper {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.is-dragging {
|
||||
flex-basis: $dropHintSize;
|
||||
}
|
||||
}
|
||||
|
||||
.is-empty & {
|
||||
&.c-fl-frame--first-in-container {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
&__drop-hint {
|
||||
flex: 1 1 100%;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import ContainerComponent from './container.vue';
|
||||
import Container from '../utils/container';
|
||||
import ResizeHandle from './resizeHandle.vue';
|
||||
import DropHint from './dropHint.vue';
|
||||
|
||||
const SNAP_TO_PERCENTAGE = 1,
|
||||
MIN_CONTAINER_SIZE = 5;
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject'],
|
||||
components: {
|
||||
ContainerComponent,
|
||||
ResizeHandle,
|
||||
DropHint
|
||||
},
|
||||
data() {
|
||||
let containers = this.domainObject.configuration.containers,
|
||||
rowsLayout = this.domainObject.configuration.rowsLayout;
|
||||
|
||||
return {
|
||||
containers: containers,
|
||||
dragFrom: [],
|
||||
isEditing: false,
|
||||
isDragging: false,
|
||||
isContainerDragging: false,
|
||||
rowsLayout: rowsLayout,
|
||||
maxMoveSize: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
layoutDirectionStr() {
|
||||
if (this.rowsLayout) {
|
||||
return 'Rows'
|
||||
} else {
|
||||
return 'Columns'
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
areAllContainersEmpty() {
|
||||
return !!!this.containers.filter(container => container.frames.length > 1).length;
|
||||
},
|
||||
addContainer() {
|
||||
let newSize = 100/(this.containers.length+1);
|
||||
|
||||
let container = new Container(newSize)
|
||||
|
||||
this.recalculateContainerSize(newSize);
|
||||
|
||||
this.containers.push(container);
|
||||
},
|
||||
recalculateContainerSize(newSize) {
|
||||
this.containers.forEach((container) => {
|
||||
container.width = newSize;
|
||||
});
|
||||
},
|
||||
recalculateNewFrameSize(multFactor, framesArray){
|
||||
framesArray.forEach((frame, index) => {
|
||||
if (index === 0) {
|
||||
return;
|
||||
}
|
||||
let frameSize = frame.height
|
||||
frame.height = this.snapToPercentage(multFactor * frameSize);
|
||||
});
|
||||
},
|
||||
recalculateOldFrameSize(framesArray) {
|
||||
let totalRemainingSum = framesArray.map((frame,i) => {
|
||||
if (i !== 0) {
|
||||
return frame.height
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}).reduce((a, c) => a + c);
|
||||
|
||||
framesArray.forEach((frame, index) => {
|
||||
|
||||
if (index === 0) {
|
||||
return;
|
||||
}
|
||||
if (framesArray.length === 2) {
|
||||
|
||||
frame.height = 100;
|
||||
} else {
|
||||
|
||||
let newSize = frame.height + ((frame.height / totalRemainingSum) * (100 - totalRemainingSum));
|
||||
frame.height = this.snapToPercentage(newSize);
|
||||
}
|
||||
});
|
||||
},
|
||||
addFrame(frame, index) {
|
||||
this.containers[index].addFrame(frame);
|
||||
},
|
||||
frameDragFromHandler(containerIndex, frameIndex) {
|
||||
this.dragFrom = [containerIndex, frameIndex];
|
||||
},
|
||||
frameDropToHandler(containerIndex, frameIndex, frameObject) {
|
||||
let newContainer = this.containers[containerIndex];
|
||||
|
||||
this.isDragging = false;
|
||||
|
||||
if (!frameObject) {
|
||||
frameObject = this.containers[this.dragFrom[0]].frames.splice(this.dragFrom[1], 1)[0];
|
||||
this.recalculateOldFrameSize(this.containers[this.dragFrom[0]].frames);
|
||||
}
|
||||
|
||||
if (!frameObject.height) {
|
||||
frameObject.height = 100 / Math.max(newContainer.frames.length - 1, 1);
|
||||
}
|
||||
|
||||
newContainer.frames.splice((frameIndex + 1), 0, frameObject);
|
||||
|
||||
let newTotalHeight = newContainer.frames.reduce((total, frame) => {
|
||||
let num = Number(frame.height);
|
||||
|
||||
if(isNaN(num)) {
|
||||
return total;
|
||||
} else {
|
||||
return total + num;
|
||||
}
|
||||
},0);
|
||||
let newMultFactor = 100 / newTotalHeight;
|
||||
|
||||
this.recalculateNewFrameSize(newMultFactor, newContainer.frames);
|
||||
|
||||
this.persist();
|
||||
},
|
||||
persist(index){
|
||||
if (index) {
|
||||
this.openmct.objects.mutate(this.domainObject, `.configuration.containers[${index}]`, this.containers[index]);
|
||||
} else {
|
||||
this.openmct.objects.mutate(this.domainObject, '.configuration.containers', this.containers);
|
||||
}
|
||||
},
|
||||
isEditingHandler(isEditing) {
|
||||
this.isEditing = isEditing;
|
||||
|
||||
if (this.isEditing) {
|
||||
this.$el.click(); //force selection of flexible-layout for toolbar
|
||||
}
|
||||
|
||||
if (this.isDragging && isEditing === false) {
|
||||
this.isDragging = false;
|
||||
}
|
||||
},
|
||||
dragstartHandler() {
|
||||
if (this.isEditing) {
|
||||
this.isDragging = true;
|
||||
}
|
||||
},
|
||||
dragendHandler() {
|
||||
this.isDragging = false;
|
||||
},
|
||||
startContainerResizing(index) {
|
||||
let beforeContainer = this.containers[index],
|
||||
afterContainer = this.containers[index + 1];
|
||||
|
||||
this.maxMoveSize = beforeContainer.width + afterContainer.width;
|
||||
},
|
||||
containerResizing(index, delta, event) {
|
||||
let percentageMoved = (delta/this.getElSize(this.$el))*100,
|
||||
beforeContainer = this.containers[index],
|
||||
afterContainer = this.containers[index + 1];
|
||||
|
||||
beforeContainer.width = this.getContainerSize(this.snapToPercentage(beforeContainer.width + percentageMoved));
|
||||
afterContainer.width = this.getContainerSize(this.snapToPercentage(afterContainer.width - percentageMoved));
|
||||
},
|
||||
endContainerResizing(event) {
|
||||
this.persist();
|
||||
},
|
||||
getElSize(el) {
|
||||
if (this.rowsLayout) {
|
||||
return el.offsetHeight;
|
||||
} else {
|
||||
return el.offsetWidth;
|
||||
}
|
||||
},
|
||||
getContainerSize(size) {
|
||||
if (size < MIN_CONTAINER_SIZE) {
|
||||
return MIN_CONTAINER_SIZE
|
||||
} else if (size > (this.maxMoveSize - MIN_CONTAINER_SIZE)) {
|
||||
return (this.maxMoveSize - MIN_CONTAINER_SIZE);
|
||||
} else {
|
||||
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 roundedValue;
|
||||
},
|
||||
toggleLayoutDirection(v) {
|
||||
this.rowsLayout = v;
|
||||
},
|
||||
promptBeforeDeletingContainer(containerIndex) {
|
||||
let deleteContainer = this.deleteContainer;
|
||||
|
||||
let prompt = this.openmct.overlays.dialog({
|
||||
iconClass: 'alert',
|
||||
message: `This action will permanently delete container ${containerIndex + 1} from this Flexible Layout`,
|
||||
buttons: [
|
||||
{
|
||||
label: 'Ok',
|
||||
emphasis: 'true',
|
||||
callback: function () {
|
||||
deleteContainer(containerIndex);
|
||||
prompt.dismiss();
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Cancel',
|
||||
callback: function () {
|
||||
prompt.dismiss();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
},
|
||||
deleteContainer(containerIndex) {
|
||||
this.containers.splice(containerIndex, 1);
|
||||
|
||||
this.recalculateContainerSize(100/this.containers.length);
|
||||
this.persist();
|
||||
},
|
||||
addContainer(containerIndex) {
|
||||
let newContainer = new Container();
|
||||
|
||||
if (typeof containerIndex === 'number') {
|
||||
this.containers.splice(containerIndex+1, 0, newContainer);
|
||||
} else {
|
||||
|
||||
this.containers.push(newContainer);
|
||||
}
|
||||
|
||||
this.recalculateContainerSize(100/this.containers.length);
|
||||
this.persist();
|
||||
},
|
||||
startContainerDrag(index) {
|
||||
this.isContainerDragging = true;
|
||||
this.containerDragFrom = index;
|
||||
},
|
||||
stopContainerDrag() {
|
||||
this.isContainerDragging = false;
|
||||
},
|
||||
containerDropTo(event, index) {
|
||||
let fromContainer = this.containers.splice(this.containerDragFrom, 1)[0];
|
||||
|
||||
if (index === -1) {
|
||||
this.containers.unshift(fromContainer);
|
||||
} else {
|
||||
this.containers.splice(index, 0, fromContainer);
|
||||
}
|
||||
|
||||
this.persist();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
let context = {
|
||||
item: this.domainObject,
|
||||
addContainer: this.addContainer,
|
||||
type: 'flexible-layout'
|
||||
}
|
||||
|
||||
this.unsubscribeSelection = this.openmct.selection.selectable(this.$el, context, true);
|
||||
|
||||
this.openmct.objects.observe(this.domainObject, 'configuration.rowsLayout', this.toggleLayoutDirection);
|
||||
this.openmct.editor.on('isEditing', this.isEditingHandler);
|
||||
|
||||
document.addEventListener('dragstart', this.dragstartHandler);
|
||||
document.addEventListener('dragend', this.dragendHandler);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.unsubscribeSelection();
|
||||
|
||||
this.openmct.editor.off('isEditing', this.isEditingHandler);
|
||||
document.removeEventListener('dragstart', this.dragstartHandler);
|
||||
document.removeEventListener('dragend', this.dragendHandler);
|
||||
}
|
||||
}
|
||||
</script>
|
129
src/plugins/flexibleLayout/components/frame.vue
Normal file
129
src/plugins/flexibleLayout/components/frame.vue
Normal file
@ -0,0 +1,129 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<div class="c-fl-frame"
|
||||
:class="{
|
||||
'is-dragging': isDragging,
|
||||
[frame.cssClass]: true
|
||||
}"
|
||||
@dragstart="initDrag"
|
||||
@drag="continueDrag">
|
||||
|
||||
<div class="c-frame c-fl-frame__drag-wrapper is-selectable is-moveable"
|
||||
:class="{'no-frame': noFrame}"
|
||||
draggable="true"
|
||||
ref="frame"
|
||||
v-if="frame.domainObject">
|
||||
|
||||
<frame-header
|
||||
v-if="index !== 0"
|
||||
ref="dragObject"
|
||||
:domainObject="frame.domainObject">
|
||||
</frame-header>
|
||||
|
||||
<object-view
|
||||
class="c-object-view"
|
||||
:object="frame.domainObject">
|
||||
</object-view>
|
||||
|
||||
<div class="c-fl-frame__size-indicator"
|
||||
v-if="isEditing"
|
||||
v-show="frame.height && frame.height < 100">
|
||||
{{frame.height}}%
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<drop-hint
|
||||
v-show="isEditing && isDragging"
|
||||
class="c-fl-frame__drop-hint"
|
||||
:class="{'is-dragging': isDragging}"
|
||||
@object-drop-to="dropHandler">
|
||||
</drop-hint>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ObjectView from '../../../ui/components/layout/ObjectView.vue';
|
||||
import DropHint from './dropHint.vue';
|
||||
import ResizeHandle from './resizeHandle.vue';
|
||||
import FrameHeader from '../../../ui/components/utils/frameHeader.vue';
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject'],
|
||||
props: ['frame', 'index', 'containerIndex', 'isEditing', 'isDragging'],
|
||||
data() {
|
||||
return {
|
||||
noFrame: this.frame.noFrame
|
||||
}
|
||||
},
|
||||
components: {
|
||||
ObjectView,
|
||||
DropHint,
|
||||
ResizeHandle,
|
||||
FrameHeader
|
||||
},
|
||||
methods: {
|
||||
initDrag(event) {
|
||||
this.$emit('frame-drag-from', this.index);
|
||||
},
|
||||
dropHandler(event) {
|
||||
this.$emit('frame-drop-to', this.index, event);
|
||||
},
|
||||
continueDrag(event) {
|
||||
if (!this.isDragging) {
|
||||
this.isDragging = true;
|
||||
}
|
||||
},
|
||||
deleteFrame() {
|
||||
this.$emit('delete-frame', this.index);
|
||||
},
|
||||
addContainer() {
|
||||
this.$emit('add-container');
|
||||
},
|
||||
toggleFrame(v) {
|
||||
this.noFrame = v;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
if (this.frame.domainObject.identifier) {
|
||||
let context = {
|
||||
item: this.frame.domainObject,
|
||||
method: this.deleteFrame,
|
||||
addContainer: this.addContainer,
|
||||
type: 'frame',
|
||||
index: this.index
|
||||
}
|
||||
|
||||
this.unsubscribeSelection = this.openmct.selection.selectable(this.$refs.frame, context, false);
|
||||
|
||||
this.openmct.objects.observe(this.domainObject, `configuration.containers[${this.containerIndex}].frames[${this.index}].noFrame`, this.toggleFrame);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.unsubscribeSelection) {
|
||||
this.unsubscribeSelection();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
75
src/plugins/flexibleLayout/components/resizeHandle.vue
Normal file
75
src/plugins/flexibleLayout/components/resizeHandle.vue
Normal file
@ -0,0 +1,75 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<div class="c-fl-frame__resize-handle"
|
||||
:class="[orientation]"
|
||||
@mousedown="mousedown">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['orientation', 'index'],
|
||||
data() {
|
||||
return {
|
||||
initialPos: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
mousedown(event) {
|
||||
event.preventDefault();
|
||||
|
||||
this.$emit('init-move', this.index);
|
||||
|
||||
document.body.addEventListener('mousemove', this.mousemove);
|
||||
document.body.addEventListener('mouseup', this.mouseup);
|
||||
},
|
||||
mousemove(event) {
|
||||
event.preventDefault();
|
||||
|
||||
let delta = this.getMousePosition(event) - this.getElSizeFromRect(this.$el);
|
||||
this.$emit('move', this.index, delta, event);
|
||||
},
|
||||
mouseup(event) {
|
||||
this.$emit('end-move', event);
|
||||
|
||||
document.body.removeEventListener('mousemove', this.mousemove);
|
||||
document.body.removeEventListener('mouseup', this.mouseup);
|
||||
},
|
||||
getMousePosition(event) {
|
||||
if (this.orientation === 'horizontal') {
|
||||
return event.clientX;
|
||||
} else {
|
||||
return event.clientY;
|
||||
}
|
||||
},
|
||||
getElSizeFromRect(el) {
|
||||
if (this.orientation === 'horizontal') {
|
||||
return el.getBoundingClientRect().x;
|
||||
} else {
|
||||
return el.getBoundingClientRect().y;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
67
src/plugins/flexibleLayout/flexibleLayoutViewProvider.js
Normal file
67
src/plugins/flexibleLayout/flexibleLayoutViewProvider.js
Normal file
@ -0,0 +1,67 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'./components/flexibleLayout.vue',
|
||||
'vue'
|
||||
], function (
|
||||
FlexibleLayoutComponent,
|
||||
Vue
|
||||
) {
|
||||
function FlexibleLayoutViewProvider(openmct) {
|
||||
return {
|
||||
key: 'flexible-layout',
|
||||
name: 'FlexibleLayout',
|
||||
cssClass: 'icon-layout-view',
|
||||
canView: function (domainObject) {
|
||||
return domainObject.type === 'flexible-layout';
|
||||
},
|
||||
view: function (domainObject) {
|
||||
let component;
|
||||
|
||||
return {
|
||||
show: function (element) {
|
||||
component = new Vue({
|
||||
components: {
|
||||
FlexibleLayoutComponent: FlexibleLayoutComponent.default
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject
|
||||
},
|
||||
el: element,
|
||||
template: '<flexible-layout-component></flexible-layout-component>'
|
||||
});
|
||||
},
|
||||
destroy: function (element) {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
}
|
||||
};
|
||||
},
|
||||
priority: function () {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
}
|
||||
return FlexibleLayoutViewProvider;
|
||||
});
|
163
src/plugins/flexibleLayout/plugin.js
Normal file
163
src/plugins/flexibleLayout/plugin.js
Normal file
@ -0,0 +1,163 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'./flexibleLayoutViewProvider',
|
||||
'./utils/container'
|
||||
], function (
|
||||
FlexibleLayoutViewProvider,
|
||||
Container
|
||||
) {
|
||||
return function plugin() {
|
||||
|
||||
return function install(openmct) {
|
||||
openmct.objectViews.addProvider(new FlexibleLayoutViewProvider(openmct));
|
||||
|
||||
openmct.types.addType('flexible-layout', {
|
||||
name: "Flexible Layout",
|
||||
creatable: true,
|
||||
description: "A fluid, flexible layout canvas that can display multiple objects in rows or columns.",
|
||||
cssClass: 'icon-flexible-layout',
|
||||
initialize: function (domainObject) {
|
||||
domainObject.configuration = {
|
||||
containers: [new Container.default(50), new Container.default(50)],
|
||||
rowsLayout: false
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
openmct.toolbars.addProvider({
|
||||
name: "Flexible Layout Toolbar",
|
||||
key: "flex-layout",
|
||||
description: "A toolbar for objects inside a Flexible Layout.",
|
||||
forSelection: function (selection) {
|
||||
let context = selection[0].context;
|
||||
|
||||
return (openmct.editor.isEditing() && context && context.type &&
|
||||
(context.type === 'flexible-layout' || context.type === 'container' || context.type === 'frame'));
|
||||
},
|
||||
toolbar: function (selection) {
|
||||
|
||||
let primary = selection[0],
|
||||
parent = selection[1],
|
||||
deleteFrame,
|
||||
toggleContainer,
|
||||
deleteContainer,
|
||||
addContainer,
|
||||
toggleFrame,
|
||||
separator;
|
||||
|
||||
addContainer = {
|
||||
control: "button",
|
||||
domainObject: parent ? parent.context.item : primary.context.item,
|
||||
method: parent ? parent.context.addContainer : primary.context.addContainer,
|
||||
key: "add",
|
||||
icon: "icon-plus-in-rect",
|
||||
title: 'Add Container'
|
||||
};
|
||||
|
||||
separator = {
|
||||
control: "separator",
|
||||
domainObject: selection[0].context.item,
|
||||
key: "separator"
|
||||
};
|
||||
|
||||
toggleContainer = {
|
||||
control: 'toggle-button',
|
||||
key: 'toggle-layout',
|
||||
domainObject: parent ? parent.context.item : primary.context.item,
|
||||
property: 'configuration.rowsLayout',
|
||||
options: [
|
||||
{
|
||||
value: false,
|
||||
icon: 'icon-columns',
|
||||
title: 'Columns'
|
||||
},
|
||||
{
|
||||
value: true,
|
||||
icon: 'icon-rows',
|
||||
title: 'Rows'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
if (primary.context.type === 'frame') {
|
||||
|
||||
deleteFrame = {
|
||||
control: "button",
|
||||
domainObject: primary.context.item,
|
||||
method: primary.context.method,
|
||||
key: "remove",
|
||||
icon: "icon-trash",
|
||||
title: "Remove Frame"
|
||||
};
|
||||
toggleFrame = {
|
||||
control: "toggle-button",
|
||||
domainObject: parent.context.item,
|
||||
property: `configuration.containers[${parent.context.index}].frames[${primary.context.index}].noFrame`,
|
||||
options: [
|
||||
{
|
||||
value: false,
|
||||
icon: 'icon-frame-hide',
|
||||
title: "Hide frame"
|
||||
},
|
||||
{
|
||||
value: true,
|
||||
icon: 'icon-frame-show',
|
||||
title: "Show frame"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
} else if (primary.context.type === 'container') {
|
||||
|
||||
deleteContainer = {
|
||||
control: "button",
|
||||
domainObject: primary.context.item,
|
||||
method: primary.context.method,
|
||||
key: "remove",
|
||||
icon: "icon-trash",
|
||||
title: "Remove Container"
|
||||
};
|
||||
|
||||
} else if (primary.context.type === 'flexible-layout') {
|
||||
|
||||
addContainer = {
|
||||
control: "button",
|
||||
domainObject: primary.context.item,
|
||||
method: primary.context.addContainer,
|
||||
key: "add",
|
||||
icon: "icon-plus-in-rect",
|
||||
title: 'Add Container'
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
let toolbar = [toggleContainer, addContainer, toggleFrame, separator, deleteFrame, deleteContainer];
|
||||
|
||||
return toolbar.filter(button => button !== undefined);
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
});
|
14
src/plugins/flexibleLayout/utils/container.js
Normal file
14
src/plugins/flexibleLayout/utils/container.js
Normal file
@ -0,0 +1,14 @@
|
||||
import Frame from './frame';
|
||||
|
||||
class Container {
|
||||
constructor (width) {
|
||||
this.frames = [new Frame({}, '', 'c-fl-frame--first-in-container')];
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
addFrame(frameObject) {
|
||||
this.frames.push(frameObject);
|
||||
}
|
||||
}
|
||||
|
||||
export default Container;
|
10
src/plugins/flexibleLayout/utils/frame.js
Normal file
10
src/plugins/flexibleLayout/utils/frame.js
Normal file
@ -0,0 +1,10 @@
|
||||
class Frame {
|
||||
constructor(domainObject, height, cssClass) {
|
||||
this.domainObject = domainObject;
|
||||
this.height = height;
|
||||
this.cssClass = cssClass ? cssClass : '';
|
||||
this.noFrame = false;
|
||||
}
|
||||
}
|
||||
|
||||
export default Frame;
|
@ -33,6 +33,7 @@
|
||||
.l-grid-view {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
overflow: auto;
|
||||
|
||||
&__item {
|
||||
flex: 0 0 auto;
|
||||
|
@ -37,6 +37,7 @@ define([
|
||||
'./notebook/plugin',
|
||||
'./displayLayout/plugin',
|
||||
'./folderView/plugin',
|
||||
'./flexibleLayout/plugin',
|
||||
'./tabs/plugin',
|
||||
'../../platform/features/fixed/plugin'
|
||||
], function (
|
||||
@ -56,6 +57,7 @@ define([
|
||||
Notebook,
|
||||
DisplayLayoutPlugin,
|
||||
FolderView,
|
||||
FlexibleLayout,
|
||||
Tabs,
|
||||
FixedView
|
||||
) {
|
||||
@ -171,6 +173,7 @@ define([
|
||||
plugins.FolderView = FolderView;
|
||||
plugins.Tabs = Tabs;
|
||||
plugins.FixedView = FixedView;
|
||||
plugins.FlexibleLayout = FlexibleLayout;
|
||||
|
||||
return plugins;
|
||||
});
|
||||
|
@ -88,17 +88,35 @@ $colorTimeSubtle: darken($colorTime, 20%);
|
||||
$colorTOI: $colorBodyFg; // was $timeControllerToiLineColor
|
||||
$colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov
|
||||
|
||||
// Edit Colors
|
||||
/************************************************** EDITING */
|
||||
// Base Colors
|
||||
$dlSpread: 20%;
|
||||
$editColor: #00c7c3;
|
||||
$editColorFg: $colorBodyFg;
|
||||
$editColorBg: darken($editColor, $dlSpread);
|
||||
$editColorFg: lighten($editColor, $dlSpread);
|
||||
$editColorHov: lighten($editColor, 20%);
|
||||
// Canvas
|
||||
$editCanvasColorBg: #002524;
|
||||
$editCanvasColorGrid: darken($editCanvasColorBg, 2%);
|
||||
// Selectable
|
||||
$editSelectableColor: #006563;
|
||||
$editSelectableColorFg: lighten($editSelectableColor, 20%);
|
||||
$editSelectableColorHov: lighten($editSelectableColor, 10%);
|
||||
// Selectable selected
|
||||
$editSelectableColorSelected: $editSelectableColorHov;
|
||||
$editSelectableColorSelectedFg: lighten($editSelectableColorSelected, 30%);
|
||||
$editSelectableColorFg: darken($editSelectableColor, 40%);
|
||||
$editSelectableBorder: 1px dotted $editSelectableColor;
|
||||
$editSelectableBorderHov: 1px dotted $editColor;
|
||||
$editSelectableBorderSelected: 1px solid $editColor;
|
||||
$editMoveableSelectedShdw: rgba($editColor, 0.5) 0 0 10px;
|
||||
$editBorderDrilledIn: 1px dashed #9971ff;
|
||||
$colorGridLines: rgba($editColor, 0.2);
|
||||
|
||||
/************************************************** BROWSING */
|
||||
$browseBorderSelectableHov: 1px dotted rgba($colorBodyFg, 0.2);
|
||||
$browseShdwSelectableHov: rgba($colorBodyFg, 0.2) 0 0 3px;
|
||||
$browseBorderSelected: 1px solid rgba($colorBodyFg, 0.6);
|
||||
$editBorderSelectable: 1px dotted rgba($editColor, 1);
|
||||
$editBorderSelectableHov: 1px dashed rgba($editColor, 1);
|
||||
$editBorderSelected: 1px solid rgba($editColor, 0.7);
|
||||
$editBorderDrilledIn: 1px dashed #ff4d9a;
|
||||
$colorGridLines: rgba($editColor, 0.2);
|
||||
|
||||
// Icons
|
||||
$colorIconAlias: #4af6f3;
|
||||
|
@ -88,17 +88,34 @@ $colorTimeSubtle: lighten($colorTime, 20%);
|
||||
$colorTOI: $colorBodyFg; // was $timeControllerToiLineColor
|
||||
$colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov
|
||||
|
||||
// Edit Colors
|
||||
/************************************************** EDITING */
|
||||
// Base Colors
|
||||
$dlSpread: 20%;
|
||||
$editColor: #00c7c3;
|
||||
$editColorFg: $colorBodyFg;
|
||||
$editColorBg: darken($editColor, $dlSpread);
|
||||
$editColorFg: lighten($editColor, $dlSpread);
|
||||
$editColorHov: lighten($editColor, 20%);
|
||||
// Canvas
|
||||
$editCanvasColorBg: #e6ffff;
|
||||
$editCanvasColorGrid: darken($editCanvasColorBg, 10%);
|
||||
// Selectable
|
||||
$editSelectableColor: darken($colorBodyBg, 10%);
|
||||
$editSelectableColorFg: darken($editSelectableColor, 20%);
|
||||
$editSelectableColorHov: darken($editSelectableColor, 10%); //darken($colorBodyBg, 20%);
|
||||
// Selectable selected
|
||||
$editSelectableColorSelected: $editColor; //$editSelectableColorHov;
|
||||
$editSelectableColorSelectedFg: lighten($editSelectableColorSelected, 50%);
|
||||
$editSelectableColorFg: darken($editSelectableColor, 40%);
|
||||
$editSelectableBorder: 1px dotted $editSelectableColor;
|
||||
$editSelectableBorderHov: 1px dotted $editColor;
|
||||
$editSelectableBorderSelected: 1px solid $editColor;
|
||||
$editMoveableSelectedShdw: rgba($editColor, 0.5) 0 0 10px;
|
||||
$editBorderDrilledIn: 1px dashed #9971ff;
|
||||
|
||||
/************************************************** BROWSING */
|
||||
$browseBorderSelectableHov: 1px dotted rgba($colorBodyFg, 0.2);
|
||||
$browseShdwSelectableHov: rgba($colorBodyFg, 0.2) 0 0 3px;
|
||||
$browseBorderSelected: 1px solid rgba($colorBodyFg, 0.6);
|
||||
$editBorderSelectable: 1px dotted rgba($editColor, 1);
|
||||
$editBorderSelectableHov: 1px dashed rgba($editColor, 1);
|
||||
$editBorderSelected: 1px solid $editColor;
|
||||
$editBorderDrilledIn: 1px dashed #ff4d9a;
|
||||
$colorGridLines: rgba($editColor, 0.2);
|
||||
|
||||
// Icons
|
||||
$colorIconAlias: #4af6f3;
|
||||
@ -125,9 +142,9 @@ $colorClickIcon: $colorKey;
|
||||
$colorClickIconBgHov: rgba($colorKey, 0.2);
|
||||
$colorClickIconFgHov: $colorKeyHov;
|
||||
$colorDropHint: $colorKey;
|
||||
$colorDropHintBg: darken($colorDropHint, 10%);
|
||||
$colorDropHintBgHov: $colorDropHint;
|
||||
$colorDropHintFg: lighten($colorDropHint, 40%);
|
||||
$colorDropHintBg: lighten($colorDropHint, 30%);
|
||||
$colorDropHintBgHov: lighten($colorDropHint, 40%);
|
||||
$colorDropHintFg: lighten($colorDropHint, 0);
|
||||
|
||||
// Menus
|
||||
$colorMenuBg: lighten($colorBodyBg, 10%);
|
||||
|
@ -512,13 +512,15 @@ input[type=number]::-webkit-outer-spin-button {
|
||||
transition: $transOut;
|
||||
z-index: 50;
|
||||
|
||||
opacity: 0; // Must use this (rather than display: none) to enable transition effects
|
||||
pointer-events: none;
|
||||
&:not(.c-drop-hint--always-show) {
|
||||
opacity: 0; // Must use this (rather than display: none) to enable transition effects
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&:before {
|
||||
$h: 80%;
|
||||
$mh: 50px;
|
||||
background: $bg-icon-activity; // TODO: change to $bg-icon-plus
|
||||
$mh: 25px;
|
||||
background: $bg-icon-plus;
|
||||
background-size: contain;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
@ -529,13 +531,15 @@ input[type=number]::-webkit-outer-spin-button {
|
||||
max-height: $mh; max-width: $mh;
|
||||
}
|
||||
|
||||
.is-dragging & {
|
||||
.is-dragging &,
|
||||
&.is-dragging {
|
||||
pointer-events: inherit;
|
||||
transition: $transIn;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.is-mouse-over & {
|
||||
.is-mouse-over &,
|
||||
&.is-mouse-over {
|
||||
transition: $transIn;
|
||||
background-color: $colorDropHintBgHov;
|
||||
opacity: 0.9;
|
||||
|
@ -235,8 +235,8 @@ body.desktop .has-local-controls {
|
||||
.c-grid {
|
||||
pointer-events: none;
|
||||
|
||||
&__x { @include bgTicks($colorGridLines, 'x'); }
|
||||
&__y { @include bgTicks($colorGridLines, 'y'); }
|
||||
&__x { @include bgTicks($editCanvasColorGrid, 'x'); }
|
||||
&__y { @include bgTicks($editCanvasColorGrid, 'y'); }
|
||||
}
|
||||
|
||||
/*************************** SELECTION */
|
||||
@ -249,15 +249,15 @@ body.desktop .has-local-controls {
|
||||
/**************************** EDITING */
|
||||
.is-editing {
|
||||
*:not(.is-drilled-in).is-selectable {
|
||||
border: $editBorderSelectable;
|
||||
border: $editSelectableBorder;
|
||||
|
||||
&:hover {
|
||||
border: $editBorderSelectableHov;
|
||||
border: $editSelectableBorderHov;
|
||||
}
|
||||
|
||||
&[s-selected],
|
||||
&.is-selected {
|
||||
border: $editBorderSelected;
|
||||
border: $editSelectableBorderSelected;
|
||||
|
||||
> .c-frame-edit {
|
||||
display: block; // Show the editing rect and handles
|
||||
@ -269,6 +269,10 @@ body.desktop .has-local-controls {
|
||||
border: $editBorderDrilledIn;
|
||||
}
|
||||
|
||||
*[s-selected] .is-moveable {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.u-links {
|
||||
// Applied in markup to objects that provide links. Disable while editing.
|
||||
pointer-events: none;
|
||||
@ -284,7 +288,7 @@ body.desktop .has-local-controls {
|
||||
|
||||
&__move {
|
||||
@include abs();
|
||||
box-shadow: rgba($editColor, 0.5) 0 0 10px;
|
||||
box-shadow: $editMoveableSelectedShdw;
|
||||
cursor: move;
|
||||
z-index: $z;
|
||||
}
|
||||
@ -339,10 +343,11 @@ body.desktop .has-local-controls {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: move this into DisplayLayout and Fixed Position vue files respectively
|
||||
.l-shell__main-container > .l-layout,
|
||||
.l-shell__main-container > .c-object-view .l-fixed-position {
|
||||
// Target the top-most layout container and color its background
|
||||
background: rgba($editColor, 0.1);
|
||||
background: $editCanvasColorBg;
|
||||
}
|
||||
|
||||
// Layouts
|
||||
|
@ -128,10 +128,10 @@
|
||||
}
|
||||
|
||||
@mixin bgVertStripes($c: yellow, $a: 0.1, $d: 40px) {
|
||||
@include background-image(linear-gradient(-90deg,
|
||||
background-image: linear-gradient(-90deg,
|
||||
rgba($c, $a) 0%, rgba($c, $a) 50%,
|
||||
transparent 50%, transparent 100%
|
||||
));
|
||||
);
|
||||
background-repeat: repeat;
|
||||
background-size: $d $d;
|
||||
}
|
||||
|
@ -76,14 +76,18 @@ export default {
|
||||
},
|
||||
onDrop(event) {
|
||||
let parentObject = this.currentObject;
|
||||
let childObject = JSON.parse(event.dataTransfer.getData("domainObject"));
|
||||
let d = event.dataTransfer.getData("domainObject");
|
||||
|
||||
if (this.openmct.composition.checkPolicy(parentObject, childObject)){
|
||||
if (!this.openmct.editor.isEditing() && parentObject.type !== 'folder'){
|
||||
this.openmct.editor.edit();
|
||||
if (d) {
|
||||
let childObject = JSON.parse(d);
|
||||
|
||||
if (this.openmct.composition.checkPolicy(parentObject, childObject)){
|
||||
if (!this.openmct.editor.isEditing() && parentObject.type !== 'folder'){
|
||||
this.openmct.editor.edit();
|
||||
}
|
||||
parentObject.composition.push(childObject.identifier);
|
||||
this.openmct.objects.mutate(parentObject, 'composition', parentObject.composition);
|
||||
}
|
||||
parentObject.composition.push(childObject.identifier);
|
||||
this.openmct.objects.mutate(parentObject, 'composition', parentObject.composition);
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
25
src/ui/components/utils/frameHeader.vue
Normal file
25
src/ui/components/utils/frameHeader.vue
Normal file
@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<div class="c-frame__header">
|
||||
<div class="c-frame__header__start">
|
||||
<div class="c-frame__name" :class="cssClass">{{ domainObject.name }}</div>
|
||||
<div class="c-frame__context-actions c-disclosure-button"></div>
|
||||
</div>
|
||||
<div class="c-frame__header__end">
|
||||
<div class="c-button icon-expand local-controls--hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
props:['domainObject'],
|
||||
data () {
|
||||
let type = this.openmct.types.get(this.domainObject.type);
|
||||
|
||||
return {
|
||||
cssClass: type.definition.cssClass
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
Loading…
Reference in New Issue
Block a user