Compare commits

...

26 Commits

Author SHA1 Message Date
d4269e2546 refactor styles manager
remove openmct dependency

use provide/inject
2020-09-18 12:45:13 -07:00
c3cb47c670 allow conditional and static styles to have saved style applied
limit saved styles to 20
2020-09-17 17:13:13 -07:00
42d519cf4a remove default style
tweak selector display
2020-09-16 19:37:19 -07:00
7c98d809c0 normalize styles before saving
prevent apply style if conditional and static styles are simultaneously selected
2020-09-16 13:36:16 -07:00
306b9db8ed do not show delete icon if not in edit mode 2020-09-15 21:18:35 -07:00
e05580b973 show save and delete only on hover 2020-09-15 19:46:21 -07:00
d44ad83445 refactor singleton usage
refactor save method
2020-09-15 16:54:21 -07:00
1397c45a96 update style properties only if they exist and do not erase properties 2020-09-15 12:37:13 -07:00
15c0a1cf36 add style description 2020-09-14 23:13:37 -07:00
106ef8994a naming refactor 2020-09-14 20:03:48 -07:00
72e5618706 rename for consistency 2020-09-14 20:01:56 -07:00
5e9c51a22c move applystyle to selector component 2020-09-14 20:00:21 -07:00
1270976a0e Merge branch 'master' into save-styles 2020-09-14 16:30:03 -07:00
ad9ac4ca1e change save order 2020-09-11 16:19:33 -07:00
0c2c21a015 delete style message 2020-09-11 15:08:56 -07:00
92b8b811d5 prevent additional StylesView from being created 2020-09-11 14:57:04 -07:00
f5a0d6620b WIP apply static style works but also to layout... 2020-09-11 11:19:57 -07:00
dbf3f3c6d8 allow deleting styles except default style 2020-09-10 22:28:21 -07:00
cfa7717204 display border color in saved styles swatch 2020-09-10 19:58:50 -07:00
5127a40bd5 WIP can apply conditional styles
* static styles do not work yet
2020-09-10 17:01:40 -07:00
3eb2a64969 fix props syntax 2020-09-10 10:57:34 -07:00
8f38a0da9a WIP saving and persisting styles
* no duplicate styles prevention
2020-09-09 15:20:15 -07:00
21f337c308 WIP styles manager to communicate between vue components 2020-09-09 12:02:57 -07:00
bad0b380b9 add vue component for selector 2020-09-02 16:27:48 -07:00
b83a38a00b WIP 2020-09-02 16:26:55 -07:00
dd1f57b2bf add saved styles inspector view 2020-08-28 16:34:59 -07:00
11 changed files with 622 additions and 15 deletions

View File

@ -21,9 +21,9 @@
*****************************************************************************/
<template>
<div class="c-style">
<div class="c-style has-local-controls">
<span :class="[
{ 'is-style-invisible': styleItem.style.isStyleInvisible },
{ 'is-style-invisible': styleItem.style && styleItem.style.isStyleInvisible },
{ 'c-style-thumb--mixed': mixedStyles.indexOf('backgroundColor') > -1 }
]"
:style="[styleItem.style.imageUrl ? { backgroundImage:'url(' + styleItem.style.imageUrl + ')'} : itemStyle ]"
@ -61,6 +61,13 @@
:options="isStyleInvisibleOption"
@change="updateStyleValue"
/>
<!-- Save Styles -->
<toolbar-button v-if="canSaveStyle()"
class="c-style__toolbar-button--save c-local-controls--show-on-hover"
:options="saveOptions"
@click="saveItemStyle()"
/>
</span>
</div>
</template>
@ -81,11 +88,12 @@ export default {
ToolbarToggleButton
},
inject: [
'openmct'
'openmct', 'stylesManager'
],
props: {
isEditing: {
type: Boolean
type: Boolean,
required: true
},
mixedStyles: {
type: Array,
@ -182,7 +190,16 @@ export default {
}
]
};
},
saveOptions() {
return {
icon: 'icon-save',
title: 'Save style',
// value: this.normalizeValueForSwatch(value),
// property: 'color',
isEditing: this.isEditing
// nonSpecific: this.mixedStyles.indexOf('color') > -1
};
}
},
methods: {
@ -216,6 +233,12 @@ export default {
}
this.$emit('persist', this.styleItem, item.property);
},
canSaveStyle() {
return this.isEditing && !this.mixedStyles.length;
},
saveItemStyle() {
this.stylesManager.save(this.itemStyle);
}
}
};

View File

@ -125,7 +125,8 @@ export default {
},
inject: [
'openmct',
'selection'
'selection',
'stylesManager'
],
data() {
return {
@ -149,6 +150,8 @@ export default {
},
destroyed() {
this.removeListeners();
this.openmct.editor.off('isEditing', this.setEditState);
this.stylesManager.off('styleSelected', this.updateSelectionStyle);
},
mounted() {
this.items = [];
@ -167,6 +170,7 @@ export default {
}
this.openmct.editor.on('isEditing', this.setEditState);
this.stylesManager.on('styleSelected', this.updateSelectionStyle);
},
methods: {
getObjectStyles() {
@ -637,6 +641,30 @@ export default {
},
persist(domainObject, style) {
this.openmct.objects.mutate(domainObject, 'configuration.objectStyles', style);
},
updateSelectionStyle(style) {
if (!this.allowEditing) {
return;
}
const foundStyle = this.findStyleByConditionId(this.selectedConditionId);
if (foundStyle && !this.isStaticAndConditionalStyles) {
Object.entries(style).forEach(([property, value]) => {
if (foundStyle.style[property] !== undefined && foundStyle.style[property] !== value) {
foundStyle.style[property] = value;
}
});
this.getAndPersistStyles();
} else {
this.removeConditionSet();
Object.entries(style).forEach(([property, value]) => {
if (this.staticStyle.style[property] !== undefined && this.staticStyle.style[property] !== value) {
this.staticStyle.style[property] = value;
this.getAndPersistStyles(property);
}
});
}
}
}
};

View File

@ -87,6 +87,15 @@
}
}
&__selector {
&:hover {
$c: $colorBodyFg;
border-color: rgba($c, 0.2);
background: rgba($c, 0.2);
transition: $transIn;
}
}
.c-style {
padding: 2px; // Allow a bit of room for thumb box-shadow

View File

@ -113,6 +113,10 @@ button {
}
}
.c-icon-button--disabled {
@include cClickIconButtonLayout();
}
.c-icon-link {
&:before {
// Icon
@ -120,7 +124,8 @@ button {
}
}
.c-icon-button {
.c-icon-button,
.c-icon-button--disabled {
&__label {
margin-left: $interiorMargin;
}

View File

@ -15,7 +15,7 @@
</div>
<div class="c-inspector__content">
<multipane v-if="currentTabbedView.key === '__properties'"
<multipane v-show="currentTabbedView.key === '__properties'"
type="vertical"
>
<pane class="c-inspector__properties">
@ -32,9 +32,13 @@
<elements />
</pane>
</multipane>
<template v-else>
<styles-inspector-view />
</template>
<StylesInspectorView
v-show="currentTabbedView.key === '__styles'"
/>
<SavedStylesInspectorView
v-show="currentTabbedView.key === '__styles'"
:is-editing="isEditing"
/>
</div>
</div>
</template>
@ -48,12 +52,18 @@ import Properties from './Properties.vue';
import ObjectName from './ObjectName.vue';
import InspectorViews from './InspectorViews.vue';
import _ from "lodash";
import StylesInspectorView from "./StylesInspectorView.vue";
import stylesManager from '@/ui/inspector/StylesManager';
import StylesInspectorView from './StylesInspectorView.vue';
import SavedStylesInspectorView from './SavedStylesInspectorView.vue';
export default {
provide: {
stylesManager: stylesManager
},
inject: ['openmct'],
components: {
StylesInspectorView,
SavedStylesInspectorView,
multipane,
pane,
Elements,
@ -63,7 +73,10 @@ export default {
InspectorViews
},
props: {
'isEditing': Boolean
isEditing: {
type: Boolean,
required: true
}
},
data() {
return {

View File

@ -0,0 +1,208 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, 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-saved-style"
@click="selectStyle()"
>
<div class="c-style has-local-controls">
<span
class="c-disclosure-triangle is-enabled"
:class="{ 'c-disclosure-triangle--expanded': expanded }"
@click.stop="toggleExpanded()"
></span>
<span
class="c-style-thumb"
:style="thumbStyle"
>
<span
class="c-style-thumb__text"
:class="{ 'hide-nice': !hasProperty(savedStyle.color) }"
>
ABC
</span>
</span>
<span class="c-toolbar">
<div class="c-ctrl-wrapper">
<div
class="c-icon-button--disabled c-icon-button--swatched icon-line-horz"
title="Border color"
>
<div
class="c-swatch"
:style="{
background: savedStyle.border
}"
></div>
</div>
</div>
<div class="c-ctrl-wrapper">
<div
class="c-icon-button--disabled c-icon-button--swatched icon-paint-bucket"
title="Background color"
>
<div
class="c-swatch"
:style="{
background: savedStyle.backgroundColor
}"
></div>
</div>
</div>
<div class="c-ctrl-wrapper">
<div
class="c-icon-button--disabled c-icon-button--swatched icon-font"
title="Text color"
>
<div
class="c-swatch"
:style="{
background: savedStyle.color
}"
></div>
</div>
</div>
<!-- delete saved style -->
<div
v-if="canDeleteStyle"
class="c-ctrl-wrapper c-local-controls--show-on-hover"
>
<div
class="c-icon-button icon-trash"
title="Delete Style"
@click.stop="deleteStyle()"
>
</div>
</div>
</span>
</div>
<div v-if="expanded">
{{ description }}
</div>
</div>
</template>
<script>
export default {
name: 'SavedStyleSelector',
inject: [
'openmct',
'stylesManager'
],
props: {
isEditing: {
type: Boolean,
required: true
},
savedStyle: {
type: Object,
required: true
}
},
data() {
return {
expanded: false
};
},
computed: {
thumbStyle() {
return {
border: `1px solid ${this.savedStyle.border}`,
backgroundColor: this.savedStyle.backgroundColor,
color: this.savedStyle.color
};
},
description() {
const fill = `Fill: ${this.savedStyle.backgroundColor || 'None'};`;
const border = `Border: ${this.savedStyle.border || 'None'};`;
const color = `Text Color: ${this.savedStyle.color || 'None'};`;
const fontSize = this.savedStyle.fontSize ? `Font Size: ${this.savedStyle.fontSize};` : '';
const font = this.savedStyle.font ? `Font Family: ${this.savedStyle.font};` : '';
return `
${fill}
${border}
${color}
${fontSize}
${font}
`;
},
canDeleteStyle() {
return this.isEditing;
}
},
methods: {
selectStyle() {
if (this.isEditing) {
this.stylesManager.select(this.savedStyle);
}
},
deleteStyle(style) {
this.showDeleteStyleDialog(style)
.then(() => {
this.stylesManager.delete(this.savedStyle);
})
.catch(() => {});
},
showDeleteStyleDialog(style) {
const message = `
This will delete this saved style.
This action will not effect styling that has already been applied.
Do you want to continue?
`;
return new Promise((resolve, reject) => {
let dialog = this.openmct.overlays.dialog({
title: 'Delete Saved Style',
iconClass: 'alert',
message: message,
buttons: [
{
label: 'OK',
callback: () => {
dialog.dismiss();
resolve();
}
},
{
label: 'Cancel',
callback: () => {
dialog.dismiss();
reject();
}
}
]
});
});
},
hasProperty(property) {
return property !== undefined;
},
toggleExpanded() {
this.expanded = !this.expanded;
}
}
};
</script>

View File

@ -0,0 +1,72 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, 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="u-contents"></div>
</template>
<script>
import SavedStylesView from './SavedStylesView.vue';
import Vue from 'vue';
export default {
inject: ['openmct', 'stylesManager'],
data() {
return {
selection: []
};
},
mounted() {
this.openmct.selection.on('change', this.updateSelection);
this.updateSelection(this.openmct.selection.get());
},
destroyed() {
this.openmct.selection.off('change', this.updateSelection);
},
methods: {
updateSelection(selection) {
if (selection.length > 0 && selection[0].length > 0) {
if (this.component) {
this.component.$destroy();
this.component = undefined;
this.$el.innerHTML = '';
}
let viewContainer = document.createElement('div');
this.$el.append(viewContainer);
this.component = new Vue({
el: viewContainer,
components: {
SavedStylesView
},
provide: {
openmct: this.openmct,
selection: selection,
stylesManager: this.stylesManager
},
template: '<saved-styles-view />'
});
}
}
}
};
</script>

View File

@ -0,0 +1,132 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, 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-inspector__saved-styles c-inspect-styles">
<div class="c-inspect-styles__header">
Saved Styles
</div>
<div class="c-inspect-styles__content">
<div class="c-inspect-styles__saved-style">
<div
v-for="(savedStyle, index) in savedStyles"
:key="index"
class="c-inspect-styles__saved-style"
>
<saved-style-selector
class="c-inspect-styles__selector"
:is-editing="isEditing"
:saved-style="savedStyle"
/>
</div>
</div>
</div>
</div>
</template>
<script>
import SavedStyleSelector from './SavedStyleSelector.vue';
export default {
name: 'SavedStylesView',
components: {
SavedStyleSelector
},
inject: [
'openmct',
'selection',
'stylesManager'
],
data() {
return {
isEditing: this.openmct.editor.isEditing(),
savedStyles: undefined
};
},
mounted() {
this.openmct.editor.on('isEditing', this.setIsEditing);
this.stylesManager.on('stylesUpdated', this.setStyles);
this.stylesManager.on('limitReached', this.showLimitReachedDialog);
this.stylesManager.on('persistError', this.showPersistErrorDialog);
this.loadStyles();
},
destroyed() {
this.openmct.editor.off('isEditing', this.setIsEditing);
this.stylesManager.off('stylesUpdated', this.setStyles);
this.stylesManager.off('limitReached', this.showLimitReachedDialog);
this.stylesManager.off('persistError', this.showPersistErrorDialog);
},
methods: {
setIsEditing(isEditing) {
this.isEditing = isEditing;
},
loadStyles() {
const styles = this.stylesManager.load();
this.setStyles(styles);
},
setStyles(styles) {
this.savedStyles = styles;
},
showLimitReachedDialog(limit) {
const message = `
You have reached the limit on the number of saved styles.
Please delete one or more saved styles and try again.
`;
let dialog = this.openmct.overlays.dialog({
title: 'Saved Styles Limit',
iconClass: 'alert',
message: message,
buttons: [
{
label: 'OK',
callback: () => {
dialog.dismiss();
}
}
]
});
},
showPersistErrorDialog() {
const message = `
Problem encountered saving styles.
Try again or delete one or more styles before trying again.
`;
let dialog = this.openmct.overlays.dialog({
title: 'Error Saving Style',
iconClass: 'error',
message: message,
buttons: [
{
label: 'OK',
callback: () => {
dialog.dismiss();
}
}
]
});
}
}
};
</script>

View File

@ -29,7 +29,7 @@ import StylesView from '../../plugins/condition/components/inspector/StylesView.
import Vue from 'vue';
export default {
inject: ['openmct'],
inject: ['openmct', 'stylesManager'],
data() {
return {
selection: []
@ -56,7 +56,8 @@ export default {
this.component = new Vue({
provide: {
openmct: this.openmct,
selection: selection
selection: selection,
stylesManager: this.stylesManager
},
el: viewContainer,
components: {

View File

@ -0,0 +1,111 @@
import EventEmitter from 'EventEmitter';
const LOCAL_STORAGE_KEY = 'mct-saved-styles';
const LIMIT = 20;
const STYLE_PROPERTIES = [
'backgroundColor', 'border', 'color'
];
/**
* @typedef {Object} Style
* @property {*} property
*/
class StylesManager extends EventEmitter {
load() {
let styles = window.localStorage.getItem(LOCAL_STORAGE_KEY);
styles = styles ? JSON.parse(styles) : [];
return styles;
}
save(style) {
const normalizedStyle = this.normalizeStyle(style);
const styles = this.load();
if (!this.isSaveLimitReached(styles)) {
styles.unshift(normalizedStyle);
const persistSucceeded = this.persist(styles);
if (persistSucceeded) {
this.emit('stylesUpdated', styles);
}
}
}
/**
* @private
*/
normalizeStyle(style) {
const normalizedStyle = style;
// strip border styling down to border color only
style.border = style.border.substring(style.border.lastIndexOf('#'));
return normalizedStyle;
}
/**
* @private
*/
isSaveLimitReached(styles) {
if (styles.length >= LIMIT) {
this.emit('limitReached');
return true;
}
return false;
}
/**
* @private
*/
isExistingStyle(style, styles) {
return styles.some(existingStyle => this.isEqual(style, existingStyle));
}
/**
* @private
*/
persist(styles) {
try {
window.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(styles));
return true;
} catch {
this.emit('persistError');
}
return false;
}
isEqual(style1, style2) {
const keys = Object.keys(Object.assign({}, style1, style2));
const different = keys.some(key => (!style1[key] && style2[key])
|| (style1[key] && !style2[key])
|| (style1[key] !== style2[key])
);
return !different;
}
select(style) {
this.emit('styleSelected', style);
}
delete(style) {
const styles = this.load();
const remainingStyles = styles.filter(keep => !this.isEqual(keep, style));
const persistSuccess = this.persist(remainingStyles);
if (persistSuccess) {
this.emit('stylesUpdated', remainingStyles);
}
}
}
const stylesManager = new StylesManager();
// breaks on adding listener later
// Object.freeze(stylesManager);
export default stylesManager;

View File

@ -162,6 +162,11 @@
}
}
/********************************************* INSPECTOR PROPERTIES TAB */
.c-saved-style {
cursor: default;
}
/********************************************* LEGACY SUPPORT */
.c-inspector {
// FilterField.vue