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:
committed by
Pete Richards
parent
d13d59bfa0
commit
1069a45cfc
@@ -78,6 +78,7 @@
|
|||||||
openmct.install(openmct.plugins.Notebook());
|
openmct.install(openmct.plugins.Notebook());
|
||||||
openmct.install(openmct.plugins.FolderView());
|
openmct.install(openmct.plugins.FolderView());
|
||||||
openmct.install(openmct.plugins.Tabs());
|
openmct.install(openmct.plugins.Tabs());
|
||||||
|
openmct.install(openmct.plugins.FlexibleLayout());
|
||||||
openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0});
|
openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0});
|
||||||
openmct.time.timeSystem('utc');
|
openmct.time.timeSystem('utc');
|
||||||
openmct.start();
|
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 {
|
.l-grid-view {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: column nowrap;
|
flex-flow: column nowrap;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
&__item {
|
&__item {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ define([
|
|||||||
'./notebook/plugin',
|
'./notebook/plugin',
|
||||||
'./displayLayout/plugin',
|
'./displayLayout/plugin',
|
||||||
'./folderView/plugin',
|
'./folderView/plugin',
|
||||||
|
'./flexibleLayout/plugin',
|
||||||
'./tabs/plugin',
|
'./tabs/plugin',
|
||||||
'../../platform/features/fixed/plugin'
|
'../../platform/features/fixed/plugin'
|
||||||
], function (
|
], function (
|
||||||
@@ -56,6 +57,7 @@ define([
|
|||||||
Notebook,
|
Notebook,
|
||||||
DisplayLayoutPlugin,
|
DisplayLayoutPlugin,
|
||||||
FolderView,
|
FolderView,
|
||||||
|
FlexibleLayout,
|
||||||
Tabs,
|
Tabs,
|
||||||
FixedView
|
FixedView
|
||||||
) {
|
) {
|
||||||
@@ -171,6 +173,7 @@ define([
|
|||||||
plugins.FolderView = FolderView;
|
plugins.FolderView = FolderView;
|
||||||
plugins.Tabs = Tabs;
|
plugins.Tabs = Tabs;
|
||||||
plugins.FixedView = FixedView;
|
plugins.FixedView = FixedView;
|
||||||
|
plugins.FlexibleLayout = FlexibleLayout;
|
||||||
|
|
||||||
return plugins;
|
return plugins;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -88,17 +88,35 @@ $colorTimeSubtle: darken($colorTime, 20%);
|
|||||||
$colorTOI: $colorBodyFg; // was $timeControllerToiLineColor
|
$colorTOI: $colorBodyFg; // was $timeControllerToiLineColor
|
||||||
$colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov
|
$colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov
|
||||||
|
|
||||||
// Edit Colors
|
/************************************************** EDITING */
|
||||||
|
// Base Colors
|
||||||
|
$dlSpread: 20%;
|
||||||
$editColor: #00c7c3;
|
$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);
|
$browseBorderSelectableHov: 1px dotted rgba($colorBodyFg, 0.2);
|
||||||
$browseShdwSelectableHov: rgba($colorBodyFg, 0.2) 0 0 3px;
|
$browseShdwSelectableHov: rgba($colorBodyFg, 0.2) 0 0 3px;
|
||||||
$browseBorderSelected: 1px solid rgba($colorBodyFg, 0.6);
|
$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
|
// Icons
|
||||||
$colorIconAlias: #4af6f3;
|
$colorIconAlias: #4af6f3;
|
||||||
|
|||||||
@@ -88,17 +88,34 @@ $colorTimeSubtle: lighten($colorTime, 20%);
|
|||||||
$colorTOI: $colorBodyFg; // was $timeControllerToiLineColor
|
$colorTOI: $colorBodyFg; // was $timeControllerToiLineColor
|
||||||
$colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov
|
$colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov
|
||||||
|
|
||||||
// Edit Colors
|
/************************************************** EDITING */
|
||||||
|
// Base Colors
|
||||||
|
$dlSpread: 20%;
|
||||||
$editColor: #00c7c3;
|
$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);
|
$browseBorderSelectableHov: 1px dotted rgba($colorBodyFg, 0.2);
|
||||||
$browseShdwSelectableHov: rgba($colorBodyFg, 0.2) 0 0 3px;
|
$browseShdwSelectableHov: rgba($colorBodyFg, 0.2) 0 0 3px;
|
||||||
$browseBorderSelected: 1px solid rgba($colorBodyFg, 0.6);
|
$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
|
// Icons
|
||||||
$colorIconAlias: #4af6f3;
|
$colorIconAlias: #4af6f3;
|
||||||
@@ -125,9 +142,9 @@ $colorClickIcon: $colorKey;
|
|||||||
$colorClickIconBgHov: rgba($colorKey, 0.2);
|
$colorClickIconBgHov: rgba($colorKey, 0.2);
|
||||||
$colorClickIconFgHov: $colorKeyHov;
|
$colorClickIconFgHov: $colorKeyHov;
|
||||||
$colorDropHint: $colorKey;
|
$colorDropHint: $colorKey;
|
||||||
$colorDropHintBg: darken($colorDropHint, 10%);
|
$colorDropHintBg: lighten($colorDropHint, 30%);
|
||||||
$colorDropHintBgHov: $colorDropHint;
|
$colorDropHintBgHov: lighten($colorDropHint, 40%);
|
||||||
$colorDropHintFg: lighten($colorDropHint, 40%);
|
$colorDropHintFg: lighten($colorDropHint, 0);
|
||||||
|
|
||||||
// Menus
|
// Menus
|
||||||
$colorMenuBg: lighten($colorBodyBg, 10%);
|
$colorMenuBg: lighten($colorBodyBg, 10%);
|
||||||
|
|||||||
@@ -512,13 +512,15 @@ input[type=number]::-webkit-outer-spin-button {
|
|||||||
transition: $transOut;
|
transition: $transOut;
|
||||||
z-index: 50;
|
z-index: 50;
|
||||||
|
|
||||||
opacity: 0; // Must use this (rather than display: none) to enable transition effects
|
&:not(.c-drop-hint--always-show) {
|
||||||
pointer-events: none;
|
opacity: 0; // Must use this (rather than display: none) to enable transition effects
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
$h: 80%;
|
$h: 80%;
|
||||||
$mh: 50px;
|
$mh: 25px;
|
||||||
background: $bg-icon-activity; // TODO: change to $bg-icon-plus
|
background: $bg-icon-plus;
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
background-position: center center;
|
background-position: center center;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
@@ -529,13 +531,15 @@ input[type=number]::-webkit-outer-spin-button {
|
|||||||
max-height: $mh; max-width: $mh;
|
max-height: $mh; max-width: $mh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.is-dragging & {
|
.is-dragging &,
|
||||||
|
&.is-dragging {
|
||||||
pointer-events: inherit;
|
pointer-events: inherit;
|
||||||
transition: $transIn;
|
transition: $transIn;
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.is-mouse-over & {
|
.is-mouse-over &,
|
||||||
|
&.is-mouse-over {
|
||||||
transition: $transIn;
|
transition: $transIn;
|
||||||
background-color: $colorDropHintBgHov;
|
background-color: $colorDropHintBgHov;
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
|
|||||||
@@ -235,8 +235,8 @@ body.desktop .has-local-controls {
|
|||||||
.c-grid {
|
.c-grid {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
||||||
&__x { @include bgTicks($colorGridLines, 'x'); }
|
&__x { @include bgTicks($editCanvasColorGrid, 'x'); }
|
||||||
&__y { @include bgTicks($colorGridLines, 'y'); }
|
&__y { @include bgTicks($editCanvasColorGrid, 'y'); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/*************************** SELECTION */
|
/*************************** SELECTION */
|
||||||
@@ -249,15 +249,15 @@ body.desktop .has-local-controls {
|
|||||||
/**************************** EDITING */
|
/**************************** EDITING */
|
||||||
.is-editing {
|
.is-editing {
|
||||||
*:not(.is-drilled-in).is-selectable {
|
*:not(.is-drilled-in).is-selectable {
|
||||||
border: $editBorderSelectable;
|
border: $editSelectableBorder;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border: $editBorderSelectableHov;
|
border: $editSelectableBorderHov;
|
||||||
}
|
}
|
||||||
|
|
||||||
&[s-selected],
|
&[s-selected],
|
||||||
&.is-selected {
|
&.is-selected {
|
||||||
border: $editBorderSelected;
|
border: $editSelectableBorderSelected;
|
||||||
|
|
||||||
> .c-frame-edit {
|
> .c-frame-edit {
|
||||||
display: block; // Show the editing rect and handles
|
display: block; // Show the editing rect and handles
|
||||||
@@ -269,6 +269,10 @@ body.desktop .has-local-controls {
|
|||||||
border: $editBorderDrilledIn;
|
border: $editBorderDrilledIn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*[s-selected] .is-moveable {
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
|
||||||
.u-links {
|
.u-links {
|
||||||
// Applied in markup to objects that provide links. Disable while editing.
|
// Applied in markup to objects that provide links. Disable while editing.
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
@@ -284,7 +288,7 @@ body.desktop .has-local-controls {
|
|||||||
|
|
||||||
&__move {
|
&__move {
|
||||||
@include abs();
|
@include abs();
|
||||||
box-shadow: rgba($editColor, 0.5) 0 0 10px;
|
box-shadow: $editMoveableSelectedShdw;
|
||||||
cursor: move;
|
cursor: move;
|
||||||
z-index: $z;
|
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 > .l-layout,
|
||||||
.l-shell__main-container > .c-object-view .l-fixed-position {
|
.l-shell__main-container > .c-object-view .l-fixed-position {
|
||||||
// Target the top-most layout container and color its background
|
// Target the top-most layout container and color its background
|
||||||
background: rgba($editColor, 0.1);
|
background: $editCanvasColorBg;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Layouts
|
// Layouts
|
||||||
|
|||||||
@@ -128,10 +128,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@mixin bgVertStripes($c: yellow, $a: 0.1, $d: 40px) {
|
@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%,
|
rgba($c, $a) 0%, rgba($c, $a) 50%,
|
||||||
transparent 50%, transparent 100%
|
transparent 50%, transparent 100%
|
||||||
));
|
);
|
||||||
background-repeat: repeat;
|
background-repeat: repeat;
|
||||||
background-size: $d $d;
|
background-size: $d $d;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,14 +76,18 @@ export default {
|
|||||||
},
|
},
|
||||||
onDrop(event) {
|
onDrop(event) {
|
||||||
let parentObject = this.currentObject;
|
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 (d) {
|
||||||
if (!this.openmct.editor.isEditing() && parentObject.type !== 'folder'){
|
let childObject = JSON.parse(d);
|
||||||
this.openmct.editor.edit();
|
|
||||||
|
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();
|
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>
|
||||||
Reference in New Issue
Block a user