Compare commits
	
		
			8 Commits
		
	
	
		
			plots-insp
			...
			v1.7.2
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					21bf508171 | ||
| 
						 | 
					dacec48aec | ||
| 
						 | 
					3ca133c782 | ||
| 
						 | 
					12416b8079 | ||
| 
						 | 
					9920e67c83 | ||
| 
						 | 
					0e80a5b8a0 | ||
| 
						 | 
					05f9202fe4 | ||
| 
						 | 
					0da35a44b0 | 
							
								
								
									
										1
									
								
								API.md
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								API.md
									
									
									
									
									
								
							@@ -430,6 +430,7 @@ Known hints:
 | 
			
		||||
* `domain`: Values with a `domain` hint will be used for the x-axis of a plot, and tables will render columns for these values first.
 | 
			
		||||
* `range`: Values with a `range` hint will be used as the y-axis on a plot, and tables will render columns for these values after the `domain` values.
 | 
			
		||||
* `image`: Indicates that the value may be interpreted as the URL to an image file, in which case appropriate views will be made available.
 | 
			
		||||
* `imageDownloadName`: Indicates that the value may be interpreted as the name of the image file.
 | 
			
		||||
 | 
			
		||||
##### The Time Conductor and Telemetry 
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -50,11 +50,16 @@ define([
 | 
			
		||||
        const IMAGE_DELAY = 20000;
 | 
			
		||||
 | 
			
		||||
        function pointForTimestamp(timestamp, name) {
 | 
			
		||||
            const url = IMAGE_SAMPLES[Math.floor(timestamp / IMAGE_DELAY) % IMAGE_SAMPLES.length];
 | 
			
		||||
            const urlItems = url.split('/');
 | 
			
		||||
            const imageDownloadName = `example.imagery.${urlItems[urlItems.length - 1]}`;
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
                name: name,
 | 
			
		||||
                name,
 | 
			
		||||
                utc: Math.floor(timestamp / IMAGE_DELAY) * IMAGE_DELAY,
 | 
			
		||||
                local: Math.floor(timestamp / IMAGE_DELAY) * IMAGE_DELAY,
 | 
			
		||||
                url: IMAGE_SAMPLES[Math.floor(timestamp / IMAGE_DELAY) % IMAGE_SAMPLES.length]
 | 
			
		||||
                url,
 | 
			
		||||
                imageDownloadName
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -139,6 +144,14 @@ define([
 | 
			
		||||
                                hints: {
 | 
			
		||||
                                    image: 1
 | 
			
		||||
                                }
 | 
			
		||||
                            },
 | 
			
		||||
                            {
 | 
			
		||||
                                name: 'Image Download Name',
 | 
			
		||||
                                key: 'imageDownloadName',
 | 
			
		||||
                                format: 'imageDownloadName',
 | 
			
		||||
                                hints: {
 | 
			
		||||
                                    imageDownloadName: 1
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        ]
 | 
			
		||||
                    };
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "openmct",
 | 
			
		||||
  "version": "1.7.1-SNAPSHOT",
 | 
			
		||||
  "version": "1.7.2",
 | 
			
		||||
  "description": "The Open MCT core platform",
 | 
			
		||||
  "dependencies": {},
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
 
 | 
			
		||||
@@ -161,6 +161,7 @@ ObjectAPI.prototype.addProvider = function (namespace, provider) {
 | 
			
		||||
 | 
			
		||||
ObjectAPI.prototype.get = function (identifier, abortSignal) {
 | 
			
		||||
    let keystring = this.makeKeyString(identifier);
 | 
			
		||||
 | 
			
		||||
    if (this.cache[keystring] !== undefined) {
 | 
			
		||||
        return this.cache[keystring];
 | 
			
		||||
    }
 | 
			
		||||
@@ -176,15 +177,16 @@ ObjectAPI.prototype.get = function (identifier, abortSignal) {
 | 
			
		||||
        throw new Error('Provider does not support get!');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let objectPromise = provider.get(identifier, abortSignal);
 | 
			
		||||
    this.cache[keystring] = objectPromise;
 | 
			
		||||
 | 
			
		||||
    return objectPromise.then(result => {
 | 
			
		||||
    let objectPromise = provider.get(identifier, abortSignal).then(result => {
 | 
			
		||||
        delete this.cache[keystring];
 | 
			
		||||
        result = this.applyGetInterceptors(identifier, result);
 | 
			
		||||
 | 
			
		||||
        return result;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this.cache[keystring] = objectPromise;
 | 
			
		||||
 | 
			
		||||
    return objectPromise;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 
 | 
			
		||||
@@ -43,12 +43,16 @@ export default function LADTableSetViewProvider(openmct) {
 | 
			
		||||
                        components: {
 | 
			
		||||
                            LadTableSet: LadTableSet
 | 
			
		||||
                        },
 | 
			
		||||
                        data() {
 | 
			
		||||
                            return {
 | 
			
		||||
                                domainObject
 | 
			
		||||
                            };
 | 
			
		||||
                        },
 | 
			
		||||
                        provide: {
 | 
			
		||||
                            openmct,
 | 
			
		||||
                            domainObject,
 | 
			
		||||
                            objectPath
 | 
			
		||||
                        },
 | 
			
		||||
                        template: '<lad-table-set></lad-table-set>'
 | 
			
		||||
                        template: '<lad-table-set :domain-object="domainObject"></lad-table-set>'
 | 
			
		||||
                    });
 | 
			
		||||
                },
 | 
			
		||||
                destroy: function (element) {
 | 
			
		||||
 
 | 
			
		||||
@@ -56,7 +56,7 @@ export default {
 | 
			
		||||
            type: Object,
 | 
			
		||||
            required: true
 | 
			
		||||
        },
 | 
			
		||||
        objectPath: {
 | 
			
		||||
        pathToTable: {
 | 
			
		||||
            type: Array,
 | 
			
		||||
            required: true
 | 
			
		||||
        },
 | 
			
		||||
@@ -66,20 +66,19 @@ export default {
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
        let currentObjectPath = this.objectPath.slice();
 | 
			
		||||
        currentObjectPath.unshift(this.domainObject);
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            timestamp: undefined,
 | 
			
		||||
            value: '---',
 | 
			
		||||
            valueClass: '',
 | 
			
		||||
            currentObjectPath,
 | 
			
		||||
            unit: ''
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
        formattedTimestamp() {
 | 
			
		||||
            return this.timestamp !== undefined ? this.getFormattedTimestamp(this.timestamp) : '---';
 | 
			
		||||
        },
 | 
			
		||||
        objectPath() {
 | 
			
		||||
            return [this.domainObject, ...this.pathToTable];
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
@@ -182,7 +181,7 @@ export default {
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
        showContextMenu(event) {
 | 
			
		||||
            let actionCollection = this.openmct.actions.get(this.currentObjectPath, this.getView());
 | 
			
		||||
            let actionCollection = this.openmct.actions.get(this.objectPath, this.getView());
 | 
			
		||||
            let allActions = actionCollection.getActionsObject();
 | 
			
		||||
            let applicableActions = CONTEXT_MENU_ACTIONS.map(key => allActions[key]);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -33,10 +33,10 @@
 | 
			
		||||
        </thead>
 | 
			
		||||
        <tbody>
 | 
			
		||||
            <lad-row
 | 
			
		||||
                v-for="item in items"
 | 
			
		||||
                :key="item.key"
 | 
			
		||||
                :domain-object="item.domainObject"
 | 
			
		||||
                :object-path="objectPath"
 | 
			
		||||
                v-for="ladRow in items"
 | 
			
		||||
                :key="ladRow.key"
 | 
			
		||||
                :domain-object="ladRow.domainObject"
 | 
			
		||||
                :path-to-table="objectPath"
 | 
			
		||||
                :has-units="hasUnits"
 | 
			
		||||
            />
 | 
			
		||||
        </tbody>
 | 
			
		||||
 
 | 
			
		||||
@@ -43,9 +43,10 @@
 | 
			
		||||
                </td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <lad-row
 | 
			
		||||
                v-for="telemetryObject in ladTelemetryObjects[ladTable.key]"
 | 
			
		||||
                :key="telemetryObject.key"
 | 
			
		||||
                :domain-object="telemetryObject.domainObject"
 | 
			
		||||
                v-for="ladRow in ladTelemetryObjects[ladTable.key]"
 | 
			
		||||
                :key="ladRow.key"
 | 
			
		||||
                :domain-object="ladRow.domainObject"
 | 
			
		||||
                :path-to-table="ladTable.objectPath"
 | 
			
		||||
                :has-units="hasUnits"
 | 
			
		||||
            />
 | 
			
		||||
        </template>
 | 
			
		||||
@@ -60,7 +61,13 @@ export default {
 | 
			
		||||
    components: {
 | 
			
		||||
        LadRow
 | 
			
		||||
    },
 | 
			
		||||
    inject: ['openmct', 'domainObject'],
 | 
			
		||||
    inject: ['openmct', 'objectPath'],
 | 
			
		||||
    props: {
 | 
			
		||||
        domainObject: {
 | 
			
		||||
            type: Object,
 | 
			
		||||
            required: true
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            ladTableObjects: [],
 | 
			
		||||
@@ -106,6 +113,7 @@ export default {
 | 
			
		||||
            let ladTable = {};
 | 
			
		||||
            ladTable.domainObject = domainObject;
 | 
			
		||||
            ladTable.key = this.openmct.objects.makeKeyString(domainObject.identifier);
 | 
			
		||||
            ladTable.objectPath = [domainObject, ...this.objectPath];
 | 
			
		||||
 | 
			
		||||
            this.$set(this.ladTelemetryObjects, ladTable.key, []);
 | 
			
		||||
            this.ladTableObjects.push(ladTable);
 | 
			
		||||
 
 | 
			
		||||
@@ -272,11 +272,11 @@ export default class Condition extends EventEmitter {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    requestLADConditionResult() {
 | 
			
		||||
    requestLADConditionResult(options) {
 | 
			
		||||
        let latestTimestamp;
 | 
			
		||||
        let criteriaResults = {};
 | 
			
		||||
        const criteriaRequests = this.criteria
 | 
			
		||||
            .map(criterion => criterion.requestLAD(this.conditionManager.telemetryObjects));
 | 
			
		||||
            .map(criterion => criterion.requestLAD(this.conditionManager.telemetryObjects, options));
 | 
			
		||||
 | 
			
		||||
        return Promise.all(criteriaRequests)
 | 
			
		||||
            .then(results => {
 | 
			
		||||
 
 | 
			
		||||
@@ -282,7 +282,7 @@ export default class ConditionManager extends EventEmitter {
 | 
			
		||||
        return currentCondition;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    requestLADConditionSetOutput() {
 | 
			
		||||
    requestLADConditionSetOutput(options) {
 | 
			
		||||
        if (!this.conditions.length) {
 | 
			
		||||
            return Promise.resolve([]);
 | 
			
		||||
        }
 | 
			
		||||
@@ -291,7 +291,7 @@ export default class ConditionManager extends EventEmitter {
 | 
			
		||||
            let latestTimestamp;
 | 
			
		||||
            let conditionResults = {};
 | 
			
		||||
            const conditionRequests = this.conditions
 | 
			
		||||
                .map(condition => condition.requestLADConditionResult());
 | 
			
		||||
                .map(condition => condition.requestLADConditionResult(options));
 | 
			
		||||
 | 
			
		||||
            return Promise.all(conditionRequests)
 | 
			
		||||
                .then((results) => {
 | 
			
		||||
 
 | 
			
		||||
@@ -40,10 +40,10 @@ export default class ConditionSetTelemetryProvider {
 | 
			
		||||
        return domainObject.type === 'conditionSet';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    request(domainObject) {
 | 
			
		||||
    request(domainObject, options) {
 | 
			
		||||
        let conditionManager = this.getConditionManager(domainObject);
 | 
			
		||||
 | 
			
		||||
        return conditionManager.requestLADConditionSetOutput()
 | 
			
		||||
        return conditionManager.requestLADConditionSetOutput(options)
 | 
			
		||||
            .then(latestOutput => {
 | 
			
		||||
                return latestOutput;
 | 
			
		||||
            });
 | 
			
		||||
@@ -52,7 +52,9 @@ export default class ConditionSetTelemetryProvider {
 | 
			
		||||
    subscribe(domainObject, callback) {
 | 
			
		||||
        let conditionManager = this.getConditionManager(domainObject);
 | 
			
		||||
 | 
			
		||||
        conditionManager.on('conditionSetResultUpdated', callback);
 | 
			
		||||
        conditionManager.on('conditionSetResultUpdated', (data) => {
 | 
			
		||||
            callback(data);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return this.destroyConditionManager.bind(this, this.openmct.objects.makeKeyString(domainObject.identifier));
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -35,6 +35,7 @@ export default class StyleRuleManager extends EventEmitter {
 | 
			
		||||
        if (styleConfiguration) {
 | 
			
		||||
            this.initialize(styleConfiguration);
 | 
			
		||||
            if (styleConfiguration.conditionSetIdentifier) {
 | 
			
		||||
                this.openmct.time.on("bounds", this.refreshData.bind(this));
 | 
			
		||||
                this.subscribeToConditionSet();
 | 
			
		||||
            } else {
 | 
			
		||||
                this.applyStaticStyle();
 | 
			
		||||
@@ -83,6 +84,25 @@ export default class StyleRuleManager extends EventEmitter {
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    refreshData(bounds, isTick) {
 | 
			
		||||
        if (!isTick) {
 | 
			
		||||
            let options = {
 | 
			
		||||
                start: bounds.start,
 | 
			
		||||
                end: bounds.end,
 | 
			
		||||
                size: 1,
 | 
			
		||||
                strategy: 'latest'
 | 
			
		||||
            };
 | 
			
		||||
            this.openmct.objects.get(this.conditionSetIdentifier).then((conditionSetDomainObject) => {
 | 
			
		||||
                this.openmct.telemetry.request(conditionSetDomainObject, options)
 | 
			
		||||
                    .then(output => {
 | 
			
		||||
                        if (output && output.length) {
 | 
			
		||||
                            this.handleConditionSetResultUpdated(output[0]);
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    updateObjectStyleConfig(styleConfiguration) {
 | 
			
		||||
        if (!styleConfiguration || !styleConfiguration.conditionSetIdentifier) {
 | 
			
		||||
            this.initialize(styleConfiguration || {});
 | 
			
		||||
@@ -160,10 +180,14 @@ export default class StyleRuleManager extends EventEmitter {
 | 
			
		||||
 | 
			
		||||
    destroy() {
 | 
			
		||||
        if (this.stopProvidingTelemetry) {
 | 
			
		||||
 | 
			
		||||
            this.stopProvidingTelemetry();
 | 
			
		||||
            delete this.stopProvidingTelemetry;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.openmct.time.off("bounds", this.refreshData);
 | 
			
		||||
        this.openmct.editor.off('isEditing', this.toggleSubscription);
 | 
			
		||||
 | 
			
		||||
        this.conditionSetIdentifier = undefined;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -344,6 +344,11 @@ export default {
 | 
			
		||||
                const layoutItem = selectionItem[0].context.layoutItem;
 | 
			
		||||
                const isChildItem = selectionItem.length > 1;
 | 
			
		||||
 | 
			
		||||
                if (!item && !layoutItem) {
 | 
			
		||||
                    // cases where selection is used for table cells
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (!isChildItem) {
 | 
			
		||||
                    domainObject = item;
 | 
			
		||||
                    itemStyle = getApplicableStylesForItem(item);
 | 
			
		||||
 
 | 
			
		||||
@@ -147,12 +147,16 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
 | 
			
		||||
        this.result = evaluateResults(Object.values(this.telemetryDataCache), this.telemetry);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    requestLAD(telemetryObjects) {
 | 
			
		||||
        const options = {
 | 
			
		||||
    requestLAD(telemetryObjects, requestOptions) {
 | 
			
		||||
        let options = {
 | 
			
		||||
            strategy: 'latest',
 | 
			
		||||
            size: 1
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if (requestOptions !== undefined) {
 | 
			
		||||
            options = Object.assign(options, requestOptions);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!this.isValid()) {
 | 
			
		||||
            return this.formatData({}, telemetryObjects);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -137,12 +137,16 @@ export default class TelemetryCriterion extends EventEmitter {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    requestLAD() {
 | 
			
		||||
        const options = {
 | 
			
		||||
    requestLAD(telemetryObjects, requestOptions) {
 | 
			
		||||
        let options = {
 | 
			
		||||
            strategy: 'latest',
 | 
			
		||||
            size: 1
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if (requestOptions !== undefined) {
 | 
			
		||||
            options = Object.assign(options, requestOptions);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!this.isValid()) {
 | 
			
		||||
            return {
 | 
			
		||||
                id: this.id,
 | 
			
		||||
 
 | 
			
		||||
@@ -104,7 +104,7 @@ export function getConsolidatedStyleValues(multipleItemStyles) {
 | 
			
		||||
    const properties = Object.keys(styleProps);
 | 
			
		||||
    properties.forEach((property) => {
 | 
			
		||||
        const values = aggregatedStyleValues[property];
 | 
			
		||||
        if (values.length) {
 | 
			
		||||
        if (values && values.length) {
 | 
			
		||||
            if (values.every(value => value === values[0])) {
 | 
			
		||||
                styleValues[property] = values[0];
 | 
			
		||||
            } else {
 | 
			
		||||
 
 | 
			
		||||
@@ -269,7 +269,12 @@ export default {
 | 
			
		||||
        },
 | 
			
		||||
        subscribeToObject() {
 | 
			
		||||
            this.subscription = this.openmct.telemetry.subscribe(this.domainObject, function (datum) {
 | 
			
		||||
                if (this.openmct.time.clock() !== undefined) {
 | 
			
		||||
                const key = this.openmct.time.timeSystem().key;
 | 
			
		||||
                const datumTimeStamp = datum[key];
 | 
			
		||||
                if (this.openmct.time.clock() !== undefined
 | 
			
		||||
                    || (datumTimeStamp
 | 
			
		||||
                        && (this.openmct.time.bounds().end >= datumTimeStamp))
 | 
			
		||||
                ) {
 | 
			
		||||
                    this.updateView(datum);
 | 
			
		||||
                }
 | 
			
		||||
            }.bind(this));
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div
 | 
			
		||||
    class="c-compass"
 | 
			
		||||
    :style="compassDimensionsStyle"
 | 
			
		||||
    :style="`width: ${ sizedImageDimensions.width }px; height: ${ sizedImageDimensions.height }px`"
 | 
			
		||||
>
 | 
			
		||||
    <CompassHUD
 | 
			
		||||
        v-if="hasCameraFieldOfView"
 | 
			
		||||
@@ -34,6 +34,7 @@
 | 
			
		||||
    <CompassRose
 | 
			
		||||
        v-if="hasCameraFieldOfView"
 | 
			
		||||
        :heading="heading"
 | 
			
		||||
        :sized-image-width="sizedImageDimensions.width"
 | 
			
		||||
        :sun-heading="sunHeading"
 | 
			
		||||
        :camera-angle-of-view="cameraAngleOfView"
 | 
			
		||||
        :camera-pan="cameraPan"
 | 
			
		||||
@@ -77,6 +78,20 @@ export default {
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
        sizedImageDimensions() {
 | 
			
		||||
            let sizedImageDimensions = {};
 | 
			
		||||
            if ((this.containerWidth / this.containerHeight) > this.naturalAspectRatio) {
 | 
			
		||||
                // container is wider than image
 | 
			
		||||
                sizedImageDimensions.width = this.containerHeight * this.naturalAspectRatio;
 | 
			
		||||
                sizedImageDimensions.height = this.containerHeight;
 | 
			
		||||
            } else {
 | 
			
		||||
                // container is taller than image
 | 
			
		||||
                sizedImageDimensions.width = this.containerWidth;
 | 
			
		||||
                sizedImageDimensions.height = this.containerWidth * this.naturalAspectRatio;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return sizedImageDimensions;
 | 
			
		||||
        },
 | 
			
		||||
        hasCameraFieldOfView() {
 | 
			
		||||
            return this.cameraPan !== undefined && this.cameraAngleOfView > 0;
 | 
			
		||||
        },
 | 
			
		||||
@@ -94,25 +109,6 @@ export default {
 | 
			
		||||
        },
 | 
			
		||||
        cameraAngleOfView() {
 | 
			
		||||
            return CAMERA_ANGLE_OF_VIEW;
 | 
			
		||||
        },
 | 
			
		||||
        compassDimensionsStyle() {
 | 
			
		||||
            const containerAspectRatio = this.containerWidth / this.containerHeight;
 | 
			
		||||
 | 
			
		||||
            let width;
 | 
			
		||||
            let height;
 | 
			
		||||
 | 
			
		||||
            if (containerAspectRatio < this.naturalAspectRatio) {
 | 
			
		||||
                width = '100%';
 | 
			
		||||
                height = `${ this.containerWidth / this.naturalAspectRatio }px`;
 | 
			
		||||
            } else {
 | 
			
		||||
                width = `${ this.containerHeight * this.naturalAspectRatio }px`;
 | 
			
		||||
                height = '100%';
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
                width: width,
 | 
			
		||||
                height: height
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
 
 | 
			
		||||
@@ -22,129 +22,134 @@
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
<div
 | 
			
		||||
    class="c-direction-rose"
 | 
			
		||||
    @click="toggleLockCompass"
 | 
			
		||||
    class="w-direction-rose"
 | 
			
		||||
    :class="compassRoseSizingClasses"
 | 
			
		||||
>
 | 
			
		||||
    <div
 | 
			
		||||
        class="c-nsew"
 | 
			
		||||
        :style="compassRoseStyle"
 | 
			
		||||
        class="c-direction-rose"
 | 
			
		||||
        @click="toggleLockCompass"
 | 
			
		||||
    >
 | 
			
		||||
        <svg
 | 
			
		||||
            class="c-nsew__minor-ticks"
 | 
			
		||||
            viewBox="0 0 100 100"
 | 
			
		||||
        <div
 | 
			
		||||
            class="c-nsew"
 | 
			
		||||
            :style="compassRoseStyle"
 | 
			
		||||
        >
 | 
			
		||||
            <rect
 | 
			
		||||
                class="c-nsew__tick c-tick-ne"
 | 
			
		||||
                x="49"
 | 
			
		||||
                y="0"
 | 
			
		||||
                width="2"
 | 
			
		||||
                height="5"
 | 
			
		||||
            />
 | 
			
		||||
            <rect
 | 
			
		||||
                class="c-nsew__tick c-tick-se"
 | 
			
		||||
                x="95"
 | 
			
		||||
                y="49"
 | 
			
		||||
                width="5"
 | 
			
		||||
                height="2"
 | 
			
		||||
            />
 | 
			
		||||
            <rect
 | 
			
		||||
                class="c-nsew__tick c-tick-sw"
 | 
			
		||||
                x="49"
 | 
			
		||||
                y="95"
 | 
			
		||||
                width="2"
 | 
			
		||||
                height="5"
 | 
			
		||||
            />
 | 
			
		||||
            <rect
 | 
			
		||||
                class="c-nsew__tick c-tick-nw"
 | 
			
		||||
                x="0"
 | 
			
		||||
                y="49"
 | 
			
		||||
                width="5"
 | 
			
		||||
                height="2"
 | 
			
		||||
            />
 | 
			
		||||
            <svg
 | 
			
		||||
                class="c-nsew__minor-ticks"
 | 
			
		||||
                viewBox="0 0 100 100"
 | 
			
		||||
            >
 | 
			
		||||
                <rect
 | 
			
		||||
                    class="c-nsew__tick c-tick-ne"
 | 
			
		||||
                    x="49"
 | 
			
		||||
                    y="0"
 | 
			
		||||
                    width="2"
 | 
			
		||||
                    height="5"
 | 
			
		||||
                />
 | 
			
		||||
                <rect
 | 
			
		||||
                    class="c-nsew__tick c-tick-se"
 | 
			
		||||
                    x="95"
 | 
			
		||||
                    y="49"
 | 
			
		||||
                    width="5"
 | 
			
		||||
                    height="2"
 | 
			
		||||
                />
 | 
			
		||||
                <rect
 | 
			
		||||
                    class="c-nsew__tick c-tick-sw"
 | 
			
		||||
                    x="49"
 | 
			
		||||
                    y="95"
 | 
			
		||||
                    width="2"
 | 
			
		||||
                    height="5"
 | 
			
		||||
                />
 | 
			
		||||
                <rect
 | 
			
		||||
                    class="c-nsew__tick c-tick-nw"
 | 
			
		||||
                    x="0"
 | 
			
		||||
                    y="49"
 | 
			
		||||
                    width="5"
 | 
			
		||||
                    height="2"
 | 
			
		||||
                />
 | 
			
		||||
 | 
			
		||||
        </svg>
 | 
			
		||||
            </svg>
 | 
			
		||||
 | 
			
		||||
        <svg
 | 
			
		||||
            class="c-nsew__ticks"
 | 
			
		||||
            viewBox="0 0 100 100"
 | 
			
		||||
        >
 | 
			
		||||
            <polygon
 | 
			
		||||
                class="c-nsew__tick c-tick-n"
 | 
			
		||||
                points="50,0 57,5 43,5"
 | 
			
		||||
            />
 | 
			
		||||
            <rect
 | 
			
		||||
                class="c-nsew__tick c-tick-e"
 | 
			
		||||
                x="95"
 | 
			
		||||
                y="49"
 | 
			
		||||
                width="5"
 | 
			
		||||
                height="2"
 | 
			
		||||
            />
 | 
			
		||||
            <rect
 | 
			
		||||
                class="c-nsew__tick c-tick-w"
 | 
			
		||||
                x="0"
 | 
			
		||||
                y="49"
 | 
			
		||||
                width="5"
 | 
			
		||||
                height="2"
 | 
			
		||||
            />
 | 
			
		||||
            <rect
 | 
			
		||||
                class="c-nsew__tick c-tick-s"
 | 
			
		||||
                x="49"
 | 
			
		||||
                y="95"
 | 
			
		||||
                width="2"
 | 
			
		||||
                height="5"
 | 
			
		||||
            />
 | 
			
		||||
            <svg
 | 
			
		||||
                class="c-nsew__ticks"
 | 
			
		||||
                viewBox="0 0 100 100"
 | 
			
		||||
            >
 | 
			
		||||
                <polygon
 | 
			
		||||
                    class="c-nsew__tick c-tick-n"
 | 
			
		||||
                    points="50,0 60,10 40,10"
 | 
			
		||||
                />
 | 
			
		||||
                <rect
 | 
			
		||||
                    class="c-nsew__tick c-tick-e"
 | 
			
		||||
                    x="95"
 | 
			
		||||
                    y="49"
 | 
			
		||||
                    width="5"
 | 
			
		||||
                    height="2"
 | 
			
		||||
                />
 | 
			
		||||
                <rect
 | 
			
		||||
                    class="c-nsew__tick c-tick-w"
 | 
			
		||||
                    x="0"
 | 
			
		||||
                    y="49"
 | 
			
		||||
                    width="5"
 | 
			
		||||
                    height="2"
 | 
			
		||||
                />
 | 
			
		||||
                <rect
 | 
			
		||||
                    class="c-nsew__tick c-tick-s"
 | 
			
		||||
                    x="49"
 | 
			
		||||
                    y="95"
 | 
			
		||||
                    width="2"
 | 
			
		||||
                    height="5"
 | 
			
		||||
                />
 | 
			
		||||
 | 
			
		||||
            <text
 | 
			
		||||
                class="c-nsew__label c-label-n"
 | 
			
		||||
                text-anchor="middle"
 | 
			
		||||
                :transform="northTextTransform"
 | 
			
		||||
            >N</text>
 | 
			
		||||
            <text
 | 
			
		||||
                class="c-nsew__label c-label-e"
 | 
			
		||||
                text-anchor="middle"
 | 
			
		||||
                :transform="eastTextTransform"
 | 
			
		||||
            >E</text>
 | 
			
		||||
            <text
 | 
			
		||||
                class="c-nsew__label c-label-w"
 | 
			
		||||
                text-anchor="middle"
 | 
			
		||||
                :transform="southTextTransform"
 | 
			
		||||
            >W</text>
 | 
			
		||||
            <text
 | 
			
		||||
                class="c-nsew__label c-label-s"
 | 
			
		||||
                text-anchor="middle"
 | 
			
		||||
                :transform="westTextTransform"
 | 
			
		||||
            >S</text>
 | 
			
		||||
        </svg>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div
 | 
			
		||||
        v-if="hasHeading"
 | 
			
		||||
        class="c-spacecraft-body"
 | 
			
		||||
        :style="headingStyle"
 | 
			
		||||
    >
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div
 | 
			
		||||
        v-if="hasSunHeading"
 | 
			
		||||
        class="c-sun"
 | 
			
		||||
        :style="sunHeadingStyle"
 | 
			
		||||
    ></div>
 | 
			
		||||
 | 
			
		||||
    <div
 | 
			
		||||
        class="c-cam-field"
 | 
			
		||||
        :style="cameraPanStyle"
 | 
			
		||||
    >
 | 
			
		||||
        <div class="cam-field-half cam-field-half-l">
 | 
			
		||||
            <div
 | 
			
		||||
                class="cam-field-area"
 | 
			
		||||
                :style="cameraFOVStyleLeftHalf"
 | 
			
		||||
            ></div>
 | 
			
		||||
                <text
 | 
			
		||||
                    class="c-nsew__label c-label-n"
 | 
			
		||||
                    text-anchor="middle"
 | 
			
		||||
                    :transform="northTextTransform"
 | 
			
		||||
                >N</text>
 | 
			
		||||
                <text
 | 
			
		||||
                    class="c-nsew__label c-label-e"
 | 
			
		||||
                    text-anchor="middle"
 | 
			
		||||
                    :transform="eastTextTransform"
 | 
			
		||||
                >E</text>
 | 
			
		||||
                <text
 | 
			
		||||
                    class="c-nsew__label c-label-w"
 | 
			
		||||
                    text-anchor="middle"
 | 
			
		||||
                    :transform="southTextTransform"
 | 
			
		||||
                >W</text>
 | 
			
		||||
                <text
 | 
			
		||||
                    class="c-nsew__label c-label-s"
 | 
			
		||||
                    text-anchor="middle"
 | 
			
		||||
                    :transform="westTextTransform"
 | 
			
		||||
                >S</text>
 | 
			
		||||
            </svg>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="cam-field-half cam-field-half-r">
 | 
			
		||||
            <div
 | 
			
		||||
                class="cam-field-area"
 | 
			
		||||
                :style="cameraFOVStyleRightHalf"
 | 
			
		||||
            ></div>
 | 
			
		||||
 | 
			
		||||
        <div
 | 
			
		||||
            v-if="hasHeading"
 | 
			
		||||
            class="c-spacecraft-body"
 | 
			
		||||
            :style="headingStyle"
 | 
			
		||||
        >
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div
 | 
			
		||||
            v-if="hasSunHeading"
 | 
			
		||||
            class="c-sun"
 | 
			
		||||
            :style="sunHeadingStyle"
 | 
			
		||||
        ></div>
 | 
			
		||||
 | 
			
		||||
        <div
 | 
			
		||||
            class="c-cam-field"
 | 
			
		||||
            :style="cameraPanStyle"
 | 
			
		||||
        >
 | 
			
		||||
            <div class="cam-field-half cam-field-half-l">
 | 
			
		||||
                <div
 | 
			
		||||
                    class="cam-field-area"
 | 
			
		||||
                    :style="cameraFOVStyleLeftHalf"
 | 
			
		||||
                ></div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="cam-field-half cam-field-half-r">
 | 
			
		||||
                <div
 | 
			
		||||
                    class="cam-field-area"
 | 
			
		||||
                    :style="cameraFOVStyleRightHalf"
 | 
			
		||||
                ></div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
@@ -155,6 +160,10 @@ import { rotate } from './utils';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    props: {
 | 
			
		||||
        sizedImageWidth: {
 | 
			
		||||
            type: Number,
 | 
			
		||||
            required: true
 | 
			
		||||
        },
 | 
			
		||||
        heading: {
 | 
			
		||||
            type: Number,
 | 
			
		||||
            required: true
 | 
			
		||||
@@ -177,12 +186,24 @@ export default {
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
        north() {
 | 
			
		||||
            return this.lockCompass ? rotate(-this.cameraPan) : 0;
 | 
			
		||||
        compassRoseSizingClasses() {
 | 
			
		||||
            let compassRoseSizingClasses = '';
 | 
			
		||||
            if (this.sizedImageWidth < 300) {
 | 
			
		||||
                compassRoseSizingClasses = '--rose-small --rose-min';
 | 
			
		||||
            } else if (this.sizedImageWidth < 500) {
 | 
			
		||||
                compassRoseSizingClasses = '--rose-small';
 | 
			
		||||
            } else if (this.sizedImageWidth > 1000) {
 | 
			
		||||
                compassRoseSizingClasses = '--rose-max';
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return compassRoseSizingClasses;
 | 
			
		||||
        },
 | 
			
		||||
        compassRoseStyle() {
 | 
			
		||||
            return { transform: `rotate(${ this.north }deg)` };
 | 
			
		||||
        },
 | 
			
		||||
        north() {
 | 
			
		||||
            return this.lockCompass ? rotate(-this.cameraPan) : 0;
 | 
			
		||||
        },
 | 
			
		||||
        northTextTransform() {
 | 
			
		||||
            return this.cardinalPointsTextTransform.north;
 | 
			
		||||
        },
 | 
			
		||||
@@ -204,10 +225,10 @@ export default {
 | 
			
		||||
            const rotation = `rotate(${ -this.north })`;
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
                north: `translate(50,15) ${ rotation }`,
 | 
			
		||||
                east: `translate(87,50) ${ rotation }`,
 | 
			
		||||
                south: `translate(13,50) ${ rotation }`,
 | 
			
		||||
                west: `translate(50,87) ${ rotation }`
 | 
			
		||||
                north: `translate(50,23) ${ rotation }`,
 | 
			
		||||
                east: `translate(82,50) ${ rotation }`,
 | 
			
		||||
                south: `translate(18,50) ${ rotation }`,
 | 
			
		||||
                west: `translate(50,82) ${ rotation }`
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
        hasHeading() {
 | 
			
		||||
 
 | 
			
		||||
@@ -20,195 +20,252 @@ $elemBg: rgba(black, 0.7);
 | 
			
		||||
 | 
			
		||||
/***************************** COMPASS HUD */
 | 
			
		||||
.c-hud {
 | 
			
		||||
  // To be placed within a imagery view, in the bounding box of the image
 | 
			
		||||
  $m: 1px;
 | 
			
		||||
  $padTB: 2px;
 | 
			
		||||
  $padLR: $padTB;
 | 
			
		||||
  color: $interfaceKeyColor;
 | 
			
		||||
  font-size: 0.8em;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  top: $m; right: $m; left: $m;
 | 
			
		||||
  height: 18px;
 | 
			
		||||
 | 
			
		||||
  svg, div {
 | 
			
		||||
    // To be placed within a imagery view, in the bounding box of the image
 | 
			
		||||
    $m: 1px;
 | 
			
		||||
    $padTB: 2px;
 | 
			
		||||
    $padLR: $padTB;
 | 
			
		||||
    color: $interfaceKeyColor;
 | 
			
		||||
    font-size: 0.8em;
 | 
			
		||||
    position: absolute;
 | 
			
		||||
  }
 | 
			
		||||
    top: $m;
 | 
			
		||||
    right: $m;
 | 
			
		||||
    left: $m;
 | 
			
		||||
    height: 18px;
 | 
			
		||||
 | 
			
		||||
  &__display {
 | 
			
		||||
      height: 30px;
 | 
			
		||||
      pointer-events: all;
 | 
			
		||||
      position: absolute;
 | 
			
		||||
      top: 0;
 | 
			
		||||
      right: 0;
 | 
			
		||||
      left: 0;
 | 
			
		||||
  }
 | 
			
		||||
    svg, div {
 | 
			
		||||
        position: absolute;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  &__range {
 | 
			
		||||
    border: 1px solid $interfaceKeyColor;
 | 
			
		||||
    border-top-color: transparent;
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    top: 50%; right: $padLR; bottom: $padTB; left: $padLR;
 | 
			
		||||
  }
 | 
			
		||||
    &__display {
 | 
			
		||||
        height: 30px;
 | 
			
		||||
        pointer-events: all;
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        top: 0;
 | 
			
		||||
        right: 0;
 | 
			
		||||
        left: 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  [class*="__dir"] {
 | 
			
		||||
    // NSEW
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
    text-shadow: 0 1px 2px black;
 | 
			
		||||
    top: 50%;
 | 
			
		||||
    transform: translate(-50%,-50%);
 | 
			
		||||
    z-index: 2;
 | 
			
		||||
  }
 | 
			
		||||
    &__range {
 | 
			
		||||
        border: 1px solid $interfaceKeyColor;
 | 
			
		||||
        border-top-color: transparent;
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        top: 50%;
 | 
			
		||||
        right: $padLR;
 | 
			
		||||
        bottom: $padTB;
 | 
			
		||||
        left: $padLR;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  [class*="__dir--sub"] {
 | 
			
		||||
    font-weight: normal;
 | 
			
		||||
    opacity: 0.5;
 | 
			
		||||
  }
 | 
			
		||||
    [class*="__dir"] {
 | 
			
		||||
        // NSEW
 | 
			
		||||
        display: inline-block;
 | 
			
		||||
        font-weight: bold;
 | 
			
		||||
        text-shadow: 0 1px 2px black;
 | 
			
		||||
        top: 50%;
 | 
			
		||||
        transform: translate(-50%, -50%);
 | 
			
		||||
        z-index: 2;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  &__sun {
 | 
			
		||||
    $s: 10px;
 | 
			
		||||
    @include sun('circle farthest-side at bottom');
 | 
			
		||||
    bottom: $padTB + 2px;
 | 
			
		||||
    height: $s; width: $s*2;
 | 
			
		||||
    opacity: 0.8;
 | 
			
		||||
    transform: translateX(-50%);
 | 
			
		||||
    z-index: 1;
 | 
			
		||||
  }
 | 
			
		||||
    [class*="__dir--sub"] {
 | 
			
		||||
        font-weight: normal;
 | 
			
		||||
        opacity: 0.5;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__sun {
 | 
			
		||||
        $s: 10px;
 | 
			
		||||
        @include sun('circle farthest-side at bottom');
 | 
			
		||||
        bottom: $padTB + 2px;
 | 
			
		||||
        height: $s;
 | 
			
		||||
        width: $s*2;
 | 
			
		||||
        opacity: 0.8;
 | 
			
		||||
        transform: translateX(-50%);
 | 
			
		||||
        z-index: 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/***************************** COMPASS DIRECTIONS */
 | 
			
		||||
.c-nsew {
 | 
			
		||||
  $color: $interfaceKeyColor;
 | 
			
		||||
  $inset: 7%;
 | 
			
		||||
  $tickHeightPerc: 15%;
 | 
			
		||||
  text-shadow: black 0 0 10px;
 | 
			
		||||
  top: $inset; right: $inset; bottom: $inset; left: $inset;
 | 
			
		||||
  z-index: 3;
 | 
			
		||||
    $color: $interfaceKeyColor;
 | 
			
		||||
    $inset: 5%;
 | 
			
		||||
    $tickHeightPerc: 15%;
 | 
			
		||||
    text-shadow: black 0 0 10px;
 | 
			
		||||
    top: $inset;
 | 
			
		||||
    right: $inset;
 | 
			
		||||
    bottom: $inset;
 | 
			
		||||
    left: $inset;
 | 
			
		||||
    z-index: 3;
 | 
			
		||||
 | 
			
		||||
  &__tick,
 | 
			
		||||
  &__label {
 | 
			
		||||
    fill: $color;
 | 
			
		||||
  }
 | 
			
		||||
    &__tick,
 | 
			
		||||
    &__label {
 | 
			
		||||
        fill: $color;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  &__minor-ticks {
 | 
			
		||||
    opacity: 0.5;
 | 
			
		||||
    transform-origin: center;
 | 
			
		||||
    transform: rotate(45deg);
 | 
			
		||||
  }
 | 
			
		||||
    &__minor-ticks {
 | 
			
		||||
        opacity: 0.5;
 | 
			
		||||
        transform-origin: center;
 | 
			
		||||
        transform: rotate(45deg);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  &__label {
 | 
			
		||||
    dominant-baseline: central;
 | 
			
		||||
    font-size: 0.8em;
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
  }
 | 
			
		||||
    &__label {
 | 
			
		||||
        dominant-baseline: central;
 | 
			
		||||
        font-size: 1.25em;
 | 
			
		||||
        font-weight: bold;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  .c-label-n {
 | 
			
		||||
    font-size: 1.1em;
 | 
			
		||||
  }
 | 
			
		||||
    .c-label-n {
 | 
			
		||||
        font-size: 2em;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/***************************** CAMERA FIELD ANGLE */
 | 
			
		||||
.c-cam-field {
 | 
			
		||||
  $color: white;
 | 
			
		||||
  opacity: 0.2;
 | 
			
		||||
  top: 0;
 | 
			
		||||
  right: 0;
 | 
			
		||||
  bottom: 0;
 | 
			
		||||
  left: 0;
 | 
			
		||||
  z-index: 2;
 | 
			
		||||
 | 
			
		||||
  .cam-field-half {
 | 
			
		||||
    $color: white;
 | 
			
		||||
    opacity: 0.3;
 | 
			
		||||
    top: 0;
 | 
			
		||||
    right: 0;
 | 
			
		||||
    bottom: 0;
 | 
			
		||||
    left: 0;
 | 
			
		||||
    z-index: 2;
 | 
			
		||||
 | 
			
		||||
    .cam-field-area {
 | 
			
		||||
      background: $color;
 | 
			
		||||
      top: -30%;
 | 
			
		||||
      right: 0;
 | 
			
		||||
      bottom: -30%;
 | 
			
		||||
      left: 0;
 | 
			
		||||
    }
 | 
			
		||||
    .cam-field-half {
 | 
			
		||||
        top: 0;
 | 
			
		||||
        right: 0;
 | 
			
		||||
        bottom: 0;
 | 
			
		||||
        left: 0;
 | 
			
		||||
 | 
			
		||||
    // clip-paths overlap a bit to avoid a gap between halves
 | 
			
		||||
    &-l {
 | 
			
		||||
      clip-path: polygon(0 0, 50.5% 0, 50.5% 100%, 0 100%);
 | 
			
		||||
      .cam-field-area {
 | 
			
		||||
        transform-origin: left center;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
        .cam-field-area {
 | 
			
		||||
            background: $color;
 | 
			
		||||
            top: -30%;
 | 
			
		||||
            right: 0;
 | 
			
		||||
            bottom: -30%;
 | 
			
		||||
            left: 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    &-r {
 | 
			
		||||
      clip-path: polygon(49.5% 0, 100% 0, 100% 100%, 49.5% 100%);
 | 
			
		||||
      .cam-field-area {
 | 
			
		||||
        transform-origin: right center;
 | 
			
		||||
      }
 | 
			
		||||
        // clip-paths overlap a bit to avoid a gap between halves
 | 
			
		||||
        &-l {
 | 
			
		||||
            clip-path: polygon(0 0, 50.5% 0, 50.5% 100%, 0 100%);
 | 
			
		||||
 | 
			
		||||
            .cam-field-area {
 | 
			
		||||
                transform-origin: left center;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        &-r {
 | 
			
		||||
            clip-path: polygon(49.5% 0, 100% 0, 100% 100%, 49.5% 100%);
 | 
			
		||||
 | 
			
		||||
            .cam-field-area {
 | 
			
		||||
                transform-origin: right center;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/***************************** SPACECRAFT BODY */
 | 
			
		||||
.c-spacecraft-body {
 | 
			
		||||
  $color: $interfaceKeyColor;
 | 
			
		||||
  $s: 30%;
 | 
			
		||||
  background: $color;
 | 
			
		||||
  border-radius: 3px;
 | 
			
		||||
  height: $s; width: $s;
 | 
			
		||||
  left: 50%; top: 50%;
 | 
			
		||||
  opacity: 0.4;
 | 
			
		||||
  transform-origin: center top;
 | 
			
		||||
 | 
			
		||||
  &:before {
 | 
			
		||||
    // Direction arrow
 | 
			
		||||
    $color: rgba(black, 0.5);
 | 
			
		||||
    $arwPointerY: 60%;
 | 
			
		||||
    $arwBodyOffset: 25%;
 | 
			
		||||
    $color: $interfaceKeyColor;
 | 
			
		||||
    $s: 30%;
 | 
			
		||||
    background: $color;
 | 
			
		||||
    content: '';
 | 
			
		||||
    display: block;
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    top: 10%; right: 20%; bottom: 50%; left: 20%;
 | 
			
		||||
    clip-path: polygon(50% 0, 100% $arwPointerY, 100%-$arwBodyOffset $arwPointerY, 100%-$arwBodyOffset 100%, $arwBodyOffset 100%, $arwBodyOffset $arwPointerY, 0 $arwPointerY);
 | 
			
		||||
  }
 | 
			
		||||
    border-radius: 3px;
 | 
			
		||||
    height: $s;
 | 
			
		||||
    width: $s;
 | 
			
		||||
    left: 50%;
 | 
			
		||||
    top: 50%;
 | 
			
		||||
    opacity: 0.4;
 | 
			
		||||
    transform-origin: center top;
 | 
			
		||||
    transform: translateX(-50%); // center by default, overridden by CompassRose.vue / headingStyle()
 | 
			
		||||
 | 
			
		||||
    &:before {
 | 
			
		||||
        // Direction arrow
 | 
			
		||||
        $color: rgba(black, 0.5);
 | 
			
		||||
        $arwPointerY: 60%;
 | 
			
		||||
        $arwBodyOffset: 25%;
 | 
			
		||||
        background: $color;
 | 
			
		||||
        content: '';
 | 
			
		||||
        display: block;
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        top: 10%;
 | 
			
		||||
        right: 20%;
 | 
			
		||||
        bottom: 50%;
 | 
			
		||||
        left: 20%;
 | 
			
		||||
        clip-path: polygon(50% 0, 100% $arwPointerY, 100%-$arwBodyOffset $arwPointerY, 100%-$arwBodyOffset 100%, $arwBodyOffset 100%, $arwBodyOffset $arwPointerY, 0 $arwPointerY);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/***************************** DIRECTION ROSE */
 | 
			
		||||
.c-direction-rose {
 | 
			
		||||
  $d: 100px;
 | 
			
		||||
  $c2: rgba(white, 0.1);
 | 
			
		||||
  background: $elemBg;
 | 
			
		||||
  background-image: radial-gradient(circle closest-side, transparent, transparent 80%, $c2);
 | 
			
		||||
  width: $d;
 | 
			
		||||
  height: $d;
 | 
			
		||||
  transform-origin: 0 0;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  bottom: 10px; left: 10px;
 | 
			
		||||
  clip-path: circle(50% at 50% 50%);
 | 
			
		||||
  border-radius: 100%;
 | 
			
		||||
 | 
			
		||||
  svg, div {
 | 
			
		||||
.w-direction-rose {
 | 
			
		||||
    $s: 10%;
 | 
			
		||||
    $m: 2%;
 | 
			
		||||
    position: absolute;
 | 
			
		||||
  }
 | 
			
		||||
    bottom: $m;
 | 
			
		||||
    left: $m;
 | 
			
		||||
    width: $s;
 | 
			
		||||
    padding-top: $s;
 | 
			
		||||
 | 
			
		||||
  // Sun
 | 
			
		||||
  .c-sun {
 | 
			
		||||
    &.--rose-min {
 | 
			
		||||
        $s: 30px;
 | 
			
		||||
        width: $s;
 | 
			
		||||
        padding-top: $s;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &.--rose-small {
 | 
			
		||||
        .c-nsew__minor-ticks,
 | 
			
		||||
        .c-tick-w,
 | 
			
		||||
        .c-tick-s,
 | 
			
		||||
        .c-tick-e,
 | 
			
		||||
        .c-label-w,
 | 
			
		||||
        .c-label-s,
 | 
			
		||||
        .c-label-e {
 | 
			
		||||
            display: none;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .c-label-n {
 | 
			
		||||
            font-size: 2.5em;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &.--rose-max {
 | 
			
		||||
        $s: 100px;
 | 
			
		||||
        width: $s;
 | 
			
		||||
        padding-top: $s;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.c-direction-rose {
 | 
			
		||||
    $c2: rgba(white, 0.1);
 | 
			
		||||
    background: $elemBg;
 | 
			
		||||
    background-image: radial-gradient(circle closest-side, transparent, transparent 80%, $c2);
 | 
			
		||||
    transform-origin: 0 0;
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    top: 0;
 | 
			
		||||
    right: 0;
 | 
			
		||||
    bottom: 0;
 | 
			
		||||
    left: 0;
 | 
			
		||||
    clip-path: circle(50% at 50% 50%);
 | 
			
		||||
    border-radius: 100%;
 | 
			
		||||
 | 
			
		||||
    &:before {
 | 
			
		||||
      $s: 35%;
 | 
			
		||||
      @include sun();
 | 
			
		||||
      content: '';
 | 
			
		||||
      display: block;
 | 
			
		||||
      position: absolute;
 | 
			
		||||
      opacity: 0.7;
 | 
			
		||||
      top: 0; left: 50%;
 | 
			
		||||
      height:$s; width: $s;
 | 
			
		||||
      transform: translate(-50%, -60%);
 | 
			
		||||
    svg, div {
 | 
			
		||||
        position: absolute;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Sun
 | 
			
		||||
    .c-sun {
 | 
			
		||||
        top: 0;
 | 
			
		||||
        right: 0;
 | 
			
		||||
        bottom: 0;
 | 
			
		||||
        left: 0;
 | 
			
		||||
 | 
			
		||||
        &:before {
 | 
			
		||||
            $s: 35%;
 | 
			
		||||
            @include sun();
 | 
			
		||||
            content: '';
 | 
			
		||||
            display: block;
 | 
			
		||||
            position: absolute;
 | 
			
		||||
            opacity: 0.7;
 | 
			
		||||
            top: 0;
 | 
			
		||||
            left: 50%;
 | 
			
		||||
            height: $s;
 | 
			
		||||
            width: $s;
 | 
			
		||||
            transform: translate(-50%, -60%);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -135,9 +135,14 @@
 | 
			
		||||
             :class="{ selected: focusedImageIndex === index && isPaused }"
 | 
			
		||||
             @click="setFocusedImage(index, thumbnailClick)"
 | 
			
		||||
        >
 | 
			
		||||
            <img class="c-thumb__image"
 | 
			
		||||
                 :src="image.url"
 | 
			
		||||
            <a href=""
 | 
			
		||||
               :download="image.imageDownloadName"
 | 
			
		||||
               @click.prevent
 | 
			
		||||
            >
 | 
			
		||||
                <img class="c-thumb__image"
 | 
			
		||||
                     :src="image.url"
 | 
			
		||||
                >
 | 
			
		||||
            </a>
 | 
			
		||||
            <div class="c-thumb__timestamp">{{ image.formattedTime }}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -218,6 +223,9 @@ export default {
 | 
			
		||||
        canTrackDuration() {
 | 
			
		||||
            return this.openmct.time.clock() && this.timeSystem.isUTCBased;
 | 
			
		||||
        },
 | 
			
		||||
        focusedImageDownloadName() {
 | 
			
		||||
            return this.getImageDownloadName(this.focusedImage);
 | 
			
		||||
        },
 | 
			
		||||
        isNextDisabled() {
 | 
			
		||||
            let disabled = false;
 | 
			
		||||
 | 
			
		||||
@@ -345,6 +353,7 @@ export default {
 | 
			
		||||
        this.imageHints = { ...this.metadata.valuesForHints(['image'])[0] };
 | 
			
		||||
        this.durationFormatter = this.getFormatter(this.timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
 | 
			
		||||
        this.imageFormatter = this.openmct.telemetry.getValueFormatter(this.imageHints);
 | 
			
		||||
        this.imageDownloadNameHints = { ...this.metadata.valuesForHints(['imageDownloadName'])[0]};
 | 
			
		||||
 | 
			
		||||
        // related telemetry keys
 | 
			
		||||
        this.spacecraftPositionKeys = ['positionX', 'positionY', 'positionZ'];
 | 
			
		||||
@@ -532,6 +541,15 @@ export default {
 | 
			
		||||
            // Replace ISO "T" with a space to allow wrapping
 | 
			
		||||
            return dateTimeStr.replace("T", " ");
 | 
			
		||||
        },
 | 
			
		||||
        getImageDownloadName(datum) {
 | 
			
		||||
            let imageDownloadName = '';
 | 
			
		||||
            if (datum) {
 | 
			
		||||
                const key = this.imageDownloadNameHints.key;
 | 
			
		||||
                imageDownloadName = datum[key];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return imageDownloadName;
 | 
			
		||||
        },
 | 
			
		||||
        parseTime(datum) {
 | 
			
		||||
            if (!datum) {
 | 
			
		||||
                return;
 | 
			
		||||
@@ -655,6 +673,7 @@ export default {
 | 
			
		||||
            image.formattedTime = this.formatTime(datum);
 | 
			
		||||
            image.url = this.formatImageUrl(datum);
 | 
			
		||||
            image.time = datum[this.timeKey];
 | 
			
		||||
            image.imageDownloadName = this.getImageDownloadName(datum);
 | 
			
		||||
 | 
			
		||||
            this.imageHistory.push(image);
 | 
			
		||||
 | 
			
		||||
@@ -777,6 +796,9 @@ export default {
 | 
			
		||||
            this.focusedImageNaturalAspectRatio = undefined;
 | 
			
		||||
 | 
			
		||||
            const img = this.$refs.focusedImage;
 | 
			
		||||
            if (!img) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // TODO - should probably cache this
 | 
			
		||||
            img.addEventListener('load', () => {
 | 
			
		||||
 
 | 
			
		||||
@@ -94,6 +94,7 @@ define([
 | 
			
		||||
        initialize() {
 | 
			
		||||
            if (this.domainObject.type === 'table') {
 | 
			
		||||
                this.filterObserver = this.openmct.objects.observe(this.domainObject, 'configuration.filters', this.updateFilters);
 | 
			
		||||
                this.filters = this.domainObject.configuration.filters;
 | 
			
		||||
                this.loadComposition();
 | 
			
		||||
            } else {
 | 
			
		||||
                this.addTelemetryObject(this.domainObject);
 | 
			
		||||
@@ -138,7 +139,18 @@ define([
 | 
			
		||||
            this.emit('object-added', telemetryObject);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        updateFilters() {
 | 
			
		||||
        updateFilters(updatedFilters) {
 | 
			
		||||
            let deepCopiedFilters = JSON.parse(JSON.stringify(updatedFilters));
 | 
			
		||||
 | 
			
		||||
            if (this.filters && !_.isEqual(this.filters, deepCopiedFilters)) {
 | 
			
		||||
                this.filters = deepCopiedFilters;
 | 
			
		||||
                this.clearAndResubscribe();
 | 
			
		||||
            } else {
 | 
			
		||||
                this.filters = deepCopiedFilters;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        clearAndResubscribe() {
 | 
			
		||||
            this.filteredRows.clear();
 | 
			
		||||
            this.boundedRows.clear();
 | 
			
		||||
            Object.keys(this.subscriptions).forEach(this.unsubscribe, this);
 | 
			
		||||
 
 | 
			
		||||
@@ -100,6 +100,9 @@ define([
 | 
			
		||||
                    destroy: function (element) {
 | 
			
		||||
                        component.$destroy();
 | 
			
		||||
                        component = undefined;
 | 
			
		||||
                    },
 | 
			
		||||
                    _getTable: function () {
 | 
			
		||||
                        return table;
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -46,6 +46,7 @@ define(
 | 
			
		||||
                filter = filter.trim().toLowerCase();
 | 
			
		||||
 | 
			
		||||
                let rowsToFilter = this.getRowsToFilter(columnKey, filter);
 | 
			
		||||
 | 
			
		||||
                if (filter.length === 0) {
 | 
			
		||||
                    delete this.columnFilters[columnKey];
 | 
			
		||||
                } else {
 | 
			
		||||
@@ -56,6 +57,16 @@ define(
 | 
			
		||||
                this.emit('filter');
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            setColumnRegexFilter(columnKey, filter) {
 | 
			
		||||
                filter = filter.trim();
 | 
			
		||||
 | 
			
		||||
                let rowsToFilter = this.masterCollection.getRows();
 | 
			
		||||
 | 
			
		||||
                this.columnFilters[columnKey] = new RegExp(filter);
 | 
			
		||||
                this.rows = rowsToFilter.filter(this.matchesFilters, this);
 | 
			
		||||
                this.emit('filter');
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * @private
 | 
			
		||||
             */
 | 
			
		||||
@@ -71,6 +82,10 @@ define(
 | 
			
		||||
             * @private
 | 
			
		||||
             */
 | 
			
		||||
            isSubsetOfCurrentFilter(columnKey, filter) {
 | 
			
		||||
                if (this.columnFilters[columnKey] instanceof RegExp) {
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return this.columnFilters[columnKey]
 | 
			
		||||
                    && filter.startsWith(this.columnFilters[columnKey])
 | 
			
		||||
                    // startsWith check will otherwise fail when filter cleared
 | 
			
		||||
@@ -97,7 +112,11 @@ define(
 | 
			
		||||
                        return false;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    doesMatchFilters = formattedValue.toLowerCase().indexOf(this.columnFilters[key]) !== -1;
 | 
			
		||||
                    if (this.columnFilters[key] instanceof RegExp) {
 | 
			
		||||
                        doesMatchFilters = this.columnFilters[key].test(formattedValue);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        doesMatchFilters = formattedValue.toLowerCase().indexOf(this.columnFilters[key]) !== -1;
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                return doesMatchFilters;
 | 
			
		||||
 
 | 
			
		||||
@@ -188,7 +188,17 @@
 | 
			
		||||
                                class="c-table__search"
 | 
			
		||||
                                @input="filterChanged(key)"
 | 
			
		||||
                                @clear="clearFilter(key)"
 | 
			
		||||
                            />
 | 
			
		||||
                            >
 | 
			
		||||
 | 
			
		||||
                                <button
 | 
			
		||||
                                    class="c-search__use-regex"
 | 
			
		||||
                                    :class="{ 'is-active': enableRegexSearch[key] }"
 | 
			
		||||
                                    title="Click to enable regex: enter a string with slashes, like this: /regex_exp/"
 | 
			
		||||
                                    @click="toggleRegex(key)"
 | 
			
		||||
                                >
 | 
			
		||||
                                    /R/
 | 
			
		||||
                                </button>
 | 
			
		||||
                            </search>
 | 
			
		||||
                        </table-column-header>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                </thead>
 | 
			
		||||
@@ -361,6 +371,7 @@ export default {
 | 
			
		||||
            paused: false,
 | 
			
		||||
            markedRows: [],
 | 
			
		||||
            isShowingMarkedRowsOnly: false,
 | 
			
		||||
            enableRegexSearch: {},
 | 
			
		||||
            hideHeaders: configuration.hideHeaders,
 | 
			
		||||
            totalNumberOfRows: 0
 | 
			
		||||
        };
 | 
			
		||||
@@ -618,7 +629,16 @@ export default {
 | 
			
		||||
            this.headersHolderEl.scrollLeft = this.scrollable.scrollLeft;
 | 
			
		||||
        },
 | 
			
		||||
        filterChanged(columnKey) {
 | 
			
		||||
            this.table.filteredRows.setColumnFilter(columnKey, this.filters[columnKey]);
 | 
			
		||||
            if (this.enableRegexSearch[columnKey]) {
 | 
			
		||||
                if (this.isCompleteRegex(this.filters[columnKey])) {
 | 
			
		||||
                    this.table.filteredRows.setColumnRegexFilter(columnKey, this.filters[columnKey].slice(1, -1));
 | 
			
		||||
                } else {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                this.table.filteredRows.setColumnFilter(columnKey, this.filters[columnKey]);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.setHeight();
 | 
			
		||||
        },
 | 
			
		||||
        clearFilter(columnKey) {
 | 
			
		||||
@@ -956,6 +976,18 @@ export default {
 | 
			
		||||
 | 
			
		||||
            this.$nextTick().then(this.calculateColumnWidths);
 | 
			
		||||
        },
 | 
			
		||||
        toggleRegex(key) {
 | 
			
		||||
            this.$set(this.filters, key, '');
 | 
			
		||||
 | 
			
		||||
            if (this.enableRegexSearch[key] === undefined) {
 | 
			
		||||
                this.$set(this.enableRegexSearch, key, true);
 | 
			
		||||
            } else {
 | 
			
		||||
                this.$set(this.enableRegexSearch, key, !this.enableRegexSearch[key]);
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        isCompleteRegex(string) {
 | 
			
		||||
            return (string.length > 2 && string[0] === '/' && string[string.length - 1] === '/');
 | 
			
		||||
        },
 | 
			
		||||
        getViewContext() {
 | 
			
		||||
            return {
 | 
			
		||||
                type: 'telemetry-table',
 | 
			
		||||
 
 | 
			
		||||
@@ -113,6 +113,7 @@ describe("the plugin", () => {
 | 
			
		||||
        let applicableViews;
 | 
			
		||||
        let tableViewProvider;
 | 
			
		||||
        let tableView;
 | 
			
		||||
        let tableInstance;
 | 
			
		||||
 | 
			
		||||
        beforeEach(() => {
 | 
			
		||||
            testTelemetryObject = {
 | 
			
		||||
@@ -179,6 +180,8 @@ describe("the plugin", () => {
 | 
			
		||||
            tableView = tableViewProvider.view(testTelemetryObject, [testTelemetryObject]);
 | 
			
		||||
            tableView.show(child, true);
 | 
			
		||||
 | 
			
		||||
            tableInstance = tableView._getTable();
 | 
			
		||||
 | 
			
		||||
            return telemetryPromise.then(() => Vue.nextTick());
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
@@ -228,5 +231,41 @@ describe("the plugin", () => {
 | 
			
		||||
                expect(toColumnText).toEqual(firstColumnText);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("Supports filtering telemetry by regular text search", () => {
 | 
			
		||||
            tableInstance.filteredRows.setColumnFilter("some-key", "1");
 | 
			
		||||
 | 
			
		||||
            return Vue.nextTick().then(() => {
 | 
			
		||||
                let filteredRowElements = element.querySelectorAll('table.c-telemetry-table__body tr');
 | 
			
		||||
 | 
			
		||||
                expect(filteredRowElements.length).toEqual(1);
 | 
			
		||||
 | 
			
		||||
                tableInstance.filteredRows.setColumnFilter("some-key", "");
 | 
			
		||||
 | 
			
		||||
                return Vue.nextTick().then(() => {
 | 
			
		||||
                    let allRowElements = element.querySelectorAll('table.c-telemetry-table__body tr');
 | 
			
		||||
 | 
			
		||||
                    expect(allRowElements.length).toEqual(3);
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("Supports filtering using Regex", () => {
 | 
			
		||||
            tableInstance.filteredRows.setColumnRegexFilter("some-key", "^some-value$");
 | 
			
		||||
 | 
			
		||||
            return Vue.nextTick().then(() => {
 | 
			
		||||
                let filteredRowElements = element.querySelectorAll('table.c-telemetry-table__body tr');
 | 
			
		||||
 | 
			
		||||
                expect(filteredRowElements.length).toEqual(0);
 | 
			
		||||
 | 
			
		||||
                tableInstance.filteredRows.setColumnRegexFilter("some-key", "^some-value");
 | 
			
		||||
 | 
			
		||||
                return Vue.nextTick().then(() => {
 | 
			
		||||
                    let allRowElements = element.querySelectorAll('table.c-telemetry-table__body tr');
 | 
			
		||||
 | 
			
		||||
                    expect(allRowElements.length).toEqual(3);
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,11 @@
 | 
			
		||||
@mixin visibleRegexButton {
 | 
			
		||||
    opacity: 1;
 | 
			
		||||
    padding: 1px 3px;
 | 
			
		||||
    width: 24px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.c-search {
 | 
			
		||||
    @include wrappedInput();
 | 
			
		||||
 | 
			
		||||
    padding-top: 2px;
 | 
			
		||||
    padding-bottom: 2px;
 | 
			
		||||
 | 
			
		||||
@@ -9,11 +14,46 @@
 | 
			
		||||
        content: $glyph-icon-magnify;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__use-regex {
 | 
			
		||||
        // Button
 | 
			
		||||
        $c: $colorBodyFg;
 | 
			
		||||
        background: rgba($c, 0.2);
 | 
			
		||||
        border: 1px solid rgba($c, 0.3);
 | 
			
		||||
        color: $c;
 | 
			
		||||
        border-radius: $controlCr;
 | 
			
		||||
        font-weight: bold;
 | 
			
		||||
        letter-spacing: 1px;
 | 
			
		||||
        font-size: 0.8em;
 | 
			
		||||
        margin-left: $interiorMarginSm;
 | 
			
		||||
        min-width: 0;
 | 
			
		||||
        opacity: 0;
 | 
			
		||||
        order: 2;
 | 
			
		||||
        overflow: hidden;
 | 
			
		||||
        padding: 1px 0;
 | 
			
		||||
        transform-origin: left;
 | 
			
		||||
        transition: $transOut;
 | 
			
		||||
        width: 0;
 | 
			
		||||
 | 
			
		||||
        &.is-active {
 | 
			
		||||
            $c: $colorBtnActiveBg;
 | 
			
		||||
            @include visibleRegexButton();
 | 
			
		||||
            background: rgba($c, 0.3);
 | 
			
		||||
            border-color: $c;
 | 
			
		||||
            color: $c;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__clear-input {
 | 
			
		||||
        display: none;
 | 
			
		||||
        order: 99;
 | 
			
		||||
        padding: 1px 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &.is-active {
 | 
			
		||||
        .c-search__use-regex {
 | 
			
		||||
            margin-left: 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .c-search__clear-input {
 | 
			
		||||
            display: block;
 | 
			
		||||
        }
 | 
			
		||||
@@ -21,6 +61,15 @@
 | 
			
		||||
 | 
			
		||||
    input[type='text'],
 | 
			
		||||
    input[type='search'] {
 | 
			
		||||
        margin-left: $interiorMargin;
 | 
			
		||||
        order: 3;
 | 
			
		||||
        text-align: left;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &:hover {
 | 
			
		||||
        .c-search__use-regex {
 | 
			
		||||
            @include visibleRegexButton();
 | 
			
		||||
            transition: $transIn;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,7 @@
 | 
			
		||||
        class="c-search__clear-input icon-x-in-circle"
 | 
			
		||||
        @click="clearInput"
 | 
			
		||||
    ></a>
 | 
			
		||||
    <slot></slot>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user