mirror of
https://github.com/nasa/openmct.git
synced 2025-06-01 23:20:50 +00:00
Style Timestrip and Plan views (#3715)
* Time Strip styling WIP - Plan activity rects now don't use corner radius if smaller than the radius, and use a minimum width of 1; - Visual refinements to time axis and swimlanes; - Refactored CSS-related class names and file `swim-lane` to `swimlane`; * Compute row span dynamically * Remove activities only in the current plan when refreshing * Time Strip styling WIP - Refinement and consolidation of CSS between c-plan and c-timeline; - CSS cleanups; * Time Strip styling WIP - Added calculated activity text coloring based on background fill color; * Fix timestrip time bounds syncing * Unlisten for bounds * Update name of css file * Adds test for time system axis in timestrip Co-authored-by: Joshi <simplyrender@gmail.com>
This commit is contained in:
parent
3571004f5c
commit
201d622b85
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="plan"
|
<div ref="plan"
|
||||||
class="c-plan"
|
class="c-plan c-timeline-holder"
|
||||||
>
|
>
|
||||||
<template v-if="viewBounds && !options.compact">
|
<template v-if="viewBounds && !options.compact">
|
||||||
<swim-lane>
|
<swim-lane>
|
||||||
@ -22,10 +22,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import * as d3Selection from 'd3-selection';
|
|
||||||
import * as d3Scale from 'd3-scale';
|
import * as d3Scale from 'd3-scale';
|
||||||
import TimelineAxis from "../../ui/components/TimeSystemAxis.vue";
|
import TimelineAxis from "../../ui/components/TimeSystemAxis.vue";
|
||||||
import SwimLane from "@/ui/components/swim-lane/SwimLane.vue";
|
import SwimLane from "@/ui/components/swim-lane/SwimLane.vue";
|
||||||
|
import { getValidatedPlan } from "./util";
|
||||||
import Vue from "vue";
|
import Vue from "vue";
|
||||||
|
|
||||||
//TODO: UI direction needed for the following property values
|
//TODO: UI direction needed for the following property values
|
||||||
@ -38,9 +38,8 @@ const RESIZE_POLL_INTERVAL = 200;
|
|||||||
const ROW_HEIGHT = 25;
|
const ROW_HEIGHT = 25;
|
||||||
const LINE_HEIGHT = 12;
|
const LINE_HEIGHT = 12;
|
||||||
const MAX_TEXT_WIDTH = 300;
|
const MAX_TEXT_WIDTH = 300;
|
||||||
const EDGE_ROUNDING = 10;
|
const EDGE_ROUNDING = 5;
|
||||||
const DEFAULT_COLOR = 'yellow';
|
const DEFAULT_COLOR = '#cc9922';
|
||||||
const DEFAULT_TEXT_COLOR = 'white';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -72,7 +71,7 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.validateJSON(this.domainObject.selectFile.body);
|
this.getPlanData(this.domainObject);
|
||||||
|
|
||||||
this.canvas = this.$refs.plan.appendChild(document.createElement('canvas'));
|
this.canvas = this.$refs.plan.appendChild(document.createElement('canvas'));
|
||||||
this.canvas.height = 0;
|
this.canvas.height = 0;
|
||||||
@ -118,14 +117,8 @@ export default {
|
|||||||
|
|
||||||
return clientWidth - 200;
|
return clientWidth - 200;
|
||||||
},
|
},
|
||||||
validateJSON(jsonString) {
|
getPlanData(domainObject) {
|
||||||
try {
|
this.planData = getValidatedPlan(domainObject);
|
||||||
this.json = JSON.parse(jsonString);
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
},
|
||||||
updateViewBounds() {
|
updateViewBounds() {
|
||||||
this.viewBounds = this.openmct.time.bounds();
|
this.viewBounds = this.openmct.time.bounds();
|
||||||
@ -148,7 +141,8 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
clearPreviousActivities() {
|
clearPreviousActivities() {
|
||||||
d3Selection.selectAll(".c-plan__contents > div").remove();
|
let activities = this.$el.querySelectorAll(".c-plan__contents > div");
|
||||||
|
activities.forEach(activity => activity.remove());
|
||||||
},
|
},
|
||||||
setDimensions() {
|
setDimensions() {
|
||||||
const planHolder = this.$refs.plan;
|
const planHolder = this.$refs.plan;
|
||||||
@ -231,14 +225,14 @@ export default {
|
|||||||
return (currentRow || 0);
|
return (currentRow || 0);
|
||||||
},
|
},
|
||||||
calculatePlanLayout() {
|
calculatePlanLayout() {
|
||||||
let groups = Object.keys(this.json);
|
let groups = Object.keys(this.planData);
|
||||||
this.groupActivities = {};
|
this.groupActivities = {};
|
||||||
|
|
||||||
groups.forEach((key, index) => {
|
groups.forEach((key, index) => {
|
||||||
let activitiesByRow = {};
|
let activitiesByRow = {};
|
||||||
let currentRow = 0;
|
let currentRow = 0;
|
||||||
|
|
||||||
let activities = this.json[key];
|
let activities = this.planData[key];
|
||||||
activities.forEach((activity) => {
|
activities.forEach((activity) => {
|
||||||
if (this.isActivityInBounds(activity)) {
|
if (this.isActivityInBounds(activity)) {
|
||||||
const currentStart = Math.max(this.viewBounds.start, activity.start);
|
const currentStart = Math.max(this.viewBounds.start, activity.start);
|
||||||
@ -251,6 +245,13 @@ export default {
|
|||||||
//TODO: Fix bug for SVG where the rectWidth is not proportional to the canvas measuredWidth of the text
|
//TODO: Fix bug for SVG where the rectWidth is not proportional to the canvas measuredWidth of the text
|
||||||
const activityNameFitsRect = (rectWidth >= activityNameWidth);
|
const activityNameFitsRect = (rectWidth >= activityNameWidth);
|
||||||
const textStart = (activityNameFitsRect ? rectX : rectY) + TEXT_LEFT_PADDING;
|
const textStart = (activityNameFitsRect ? rectX : rectY) + TEXT_LEFT_PADDING;
|
||||||
|
const color = activity.color || DEFAULT_COLOR;
|
||||||
|
let textColor = '';
|
||||||
|
if (activity.textColor) {
|
||||||
|
textColor = activity.textColor;
|
||||||
|
} else if (activityNameFitsRect) {
|
||||||
|
textColor = this.getContrastingColor(color);
|
||||||
|
}
|
||||||
|
|
||||||
let textLines = this.getActivityDisplayText(this.canvasContext, activity.name, activityNameFitsRect);
|
let textLines = this.getActivityDisplayText(this.canvasContext, activity.name, activityNameFitsRect);
|
||||||
const textWidth = textStart + this.getTextWidth(textLines[0]) + TEXT_LEFT_PADDING;
|
const textWidth = textStart + this.getTextWidth(textLines[0]) + TEXT_LEFT_PADDING;
|
||||||
@ -269,8 +270,8 @@ export default {
|
|||||||
|
|
||||||
activitiesByRow[currentRow].push({
|
activitiesByRow[currentRow].push({
|
||||||
activity: {
|
activity: {
|
||||||
color: activity.color || DEFAULT_COLOR,
|
color: color,
|
||||||
textColor: activity.textColor || DEFAULT_TEXT_COLOR,
|
textColor: textColor,
|
||||||
name: activity.name,
|
name: activity.name,
|
||||||
exceeds: {
|
exceeds: {
|
||||||
start: this.xScale(this.viewBounds.start) > this.xScale(activity.start),
|
start: this.xScale(this.viewBounds.start) > this.xScale(activity.start),
|
||||||
@ -279,6 +280,7 @@ export default {
|
|||||||
},
|
},
|
||||||
textLines: textLines,
|
textLines: textLines,
|
||||||
textStart: textStart,
|
textStart: textStart,
|
||||||
|
textClass: activityNameFitsRect ? "" : "activity-label--outside-rect",
|
||||||
textY: textY,
|
textY: textY,
|
||||||
start: rectX,
|
start: rectX,
|
||||||
end: activityNameFitsRect ? rectY : textStart + textWidth,
|
end: activityNameFitsRect ? rectY : textStart + textWidth,
|
||||||
@ -423,11 +425,14 @@ export default {
|
|||||||
width = width + EDGE_ROUNDING;
|
width = width + EDGE_ROUNDING;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
width = Math.max(width, 1); // Set width to a minimum of 1
|
||||||
|
|
||||||
|
// rx: don't round corners if the width of the rect is smaller than the rounding radius
|
||||||
this.setNSAttributesForElement(rectElement, {
|
this.setNSAttributesForElement(rectElement, {
|
||||||
class: 'activity-bounds',
|
class: 'activity-bounds',
|
||||||
x: item.activity.exceeds.start ? item.start - EDGE_ROUNDING : item.start,
|
x: item.activity.exceeds.start ? item.start - EDGE_ROUNDING : item.start,
|
||||||
y: row,
|
y: row,
|
||||||
rx: EDGE_ROUNDING,
|
rx: (width < EDGE_ROUNDING * 2) ? 0 : EDGE_ROUNDING,
|
||||||
width: width,
|
width: width,
|
||||||
height: String(ROW_HEIGHT),
|
height: String(ROW_HEIGHT),
|
||||||
fill: activity.color
|
fill: activity.color
|
||||||
@ -438,7 +443,7 @@ export default {
|
|||||||
item.textLines.forEach((line, index) => {
|
item.textLines.forEach((line, index) => {
|
||||||
let textElement = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
let textElement = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
||||||
this.setNSAttributesForElement(textElement, {
|
this.setNSAttributesForElement(textElement, {
|
||||||
class: 'activity-label',
|
class: `activity-label ${item.textClass}`,
|
||||||
x: item.textStart,
|
x: item.textStart,
|
||||||
y: item.textY + (index * LINE_HEIGHT),
|
y: item.textY + (index * LINE_HEIGHT),
|
||||||
fill: activity.textColor
|
fill: activity.textColor
|
||||||
@ -449,6 +454,29 @@ export default {
|
|||||||
svgElement.appendChild(textElement);
|
svgElement.appendChild(textElement);
|
||||||
});
|
});
|
||||||
// this.addForeignElement(svgElement, activity.name, item.textStart, item.textY - LINE_HEIGHT);
|
// this.addForeignElement(svgElement, activity.name, item.textStart, item.textY - LINE_HEIGHT);
|
||||||
|
},
|
||||||
|
cutHex(h, start, end) {
|
||||||
|
const hStr = (h.charAt(0) === '#') ? h.substring(1, 7) : h;
|
||||||
|
|
||||||
|
return parseInt(hStr.substring(start, end), 16);
|
||||||
|
},
|
||||||
|
getContrastingColor(hexColor) {
|
||||||
|
// https://codepen.io/davidhalford/pen/ywEva/
|
||||||
|
// TODO: move this into a general utility function?
|
||||||
|
const cThreshold = 130;
|
||||||
|
|
||||||
|
if (hexColor.indexOf('#') === -1) {
|
||||||
|
// We weren't given a hex color
|
||||||
|
return "#ff0000";
|
||||||
|
}
|
||||||
|
|
||||||
|
const hR = this.cutHex(hexColor, 0, 2);
|
||||||
|
const hG = this.cutHex(hexColor, 2, 4);
|
||||||
|
const hB = this.cutHex(hexColor, 4, 6);
|
||||||
|
|
||||||
|
const cBrightness = ((hR * 299) + (hG * 587) + (hB * 114)) / 1000;
|
||||||
|
|
||||||
|
return cBrightness > cThreshold ? "#000000" : "#ffffff";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,21 +1,16 @@
|
|||||||
.c-plan {
|
.c-plan {
|
||||||
|
|
||||||
@include abs();
|
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
text-rendering: geometricPrecision;
|
text-rendering: geometricPrecision;
|
||||||
|
|
||||||
.activity-label, .no-activities {
|
text {
|
||||||
stroke: none;
|
stroke: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-activities {
|
.activity-label {
|
||||||
fill: #383838;
|
&--outside-rect {
|
||||||
}
|
fill: $colorBodyFg !important;
|
||||||
|
}
|
||||||
.activity-bounds {
|
}
|
||||||
fill-opacity: 0.5;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas {
|
canvas {
|
||||||
|
11
src/plugins/plan/util.js
Normal file
11
src/plugins/plan/util.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
export function getValidatedPlan(domainObject) {
|
||||||
|
let jsonString = domainObject.selectFile.body;
|
||||||
|
let json = {};
|
||||||
|
try {
|
||||||
|
json = JSON.parse(jsonString);
|
||||||
|
} catch (e) {
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}
|
@ -55,7 +55,7 @@
|
|||||||
<swim-lane :icon-class="item.type.definition.cssClass"
|
<swim-lane :icon-class="item.type.definition.cssClass"
|
||||||
:min-height="item.height"
|
:min-height="item.height"
|
||||||
:show-ucontents="item.domainObject.type === 'plan'"
|
:show-ucontents="item.domainObject.type === 'plan'"
|
||||||
:span-rows="item.domainObject.type === 'plan'"
|
:span-rows-count="item.rowCount"
|
||||||
>
|
>
|
||||||
<template slot="label">
|
<template slot="label">
|
||||||
{{ item.domainObject.name }}
|
{{ item.domainObject.name }}
|
||||||
@ -78,6 +78,7 @@
|
|||||||
import ObjectView from '@/ui/components/ObjectView.vue';
|
import ObjectView from '@/ui/components/ObjectView.vue';
|
||||||
import TimelineAxis from '../../ui/components/TimeSystemAxis.vue';
|
import TimelineAxis from '../../ui/components/TimeSystemAxis.vue';
|
||||||
import SwimLane from "@/ui/components/swim-lane/SwimLane.vue";
|
import SwimLane from "@/ui/components/swim-lane/SwimLane.vue";
|
||||||
|
import { getValidatedPlan } from "../plan/util";
|
||||||
|
|
||||||
const unknownObjectType = {
|
const unknownObjectType = {
|
||||||
definition: {
|
definition: {
|
||||||
@ -116,6 +117,8 @@ export default {
|
|||||||
this.composition.off('add', this.addItem);
|
this.composition.off('add', this.addItem);
|
||||||
this.composition.off('remove', this.removeItem);
|
this.composition.off('remove', this.removeItem);
|
||||||
this.composition.off('reorder', this.reorder);
|
this.composition.off('reorder', this.reorder);
|
||||||
|
this.openmct.time.off("bounds", this.updateViewBounds);
|
||||||
|
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.composition) {
|
if (this.composition) {
|
||||||
@ -126,6 +129,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.getTimeSystems();
|
this.getTimeSystems();
|
||||||
|
this.openmct.time.on("bounds", this.updateViewBounds);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
addItem(domainObject) {
|
addItem(domainObject) {
|
||||||
@ -133,6 +137,10 @@ export default {
|
|||||||
let keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
|
let keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||||
let objectPath = [domainObject].concat(this.objectPath.slice());
|
let objectPath = [domainObject].concat(this.objectPath.slice());
|
||||||
let viewKey = getViewKey(domainObject, this.openmct);
|
let viewKey = getViewKey(domainObject, this.openmct);
|
||||||
|
let rowCount = 0;
|
||||||
|
if (domainObject.type === 'plan') {
|
||||||
|
rowCount = Object.keys(getValidatedPlan(domainObject)).length;
|
||||||
|
}
|
||||||
|
|
||||||
let height = domainObject.type === 'telemetry.plot.stacked' ? `${domainObject.composition.length * 100}px` : '100px';
|
let height = domainObject.type === 'telemetry.plot.stacked' ? `${domainObject.composition.length * 100}px` : '100px';
|
||||||
let item = {
|
let item = {
|
||||||
@ -141,6 +149,7 @@ export default {
|
|||||||
type,
|
type,
|
||||||
keyString,
|
keyString,
|
||||||
viewKey,
|
viewKey,
|
||||||
|
rowCount,
|
||||||
height
|
height
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -174,6 +183,12 @@ export default {
|
|||||||
|
|
||||||
//TODO: Some kind of translation via an offset? of current bounds to target timeSystem
|
//TODO: Some kind of translation via an offset? of current bounds to target timeSystem
|
||||||
return currentBounds;
|
return currentBounds;
|
||||||
|
},
|
||||||
|
updateViewBounds(bounds) {
|
||||||
|
let currentTimeSystem = this.timeSystems.find(item => item.timeSystem.key === this.openmct.time.timeSystem().key);
|
||||||
|
if (currentTimeSystem) {
|
||||||
|
currentTimeSystem.bounds = bounds;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
import { createOpenMct, resetApplicationState } from "utils/testing";
|
import { createOpenMct, resetApplicationState } from "utils/testing";
|
||||||
import TimelinePlugin from "./plugin";
|
import TimelinePlugin from "./plugin";
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
describe('the plugin', function () {
|
describe('the plugin', function () {
|
||||||
let objectDef;
|
let objectDef;
|
||||||
@ -47,7 +48,7 @@ describe('the plugin', function () {
|
|||||||
child.style.height = '480px';
|
child.style.height = '480px';
|
||||||
element.appendChild(child);
|
element.appendChild(child);
|
||||||
|
|
||||||
openmct.time.bounds({
|
openmct.time.timeSystem('utc', {
|
||||||
start: 1597160002854,
|
start: 1597160002854,
|
||||||
end: 1597181232854
|
end: 1597181232854
|
||||||
});
|
});
|
||||||
@ -75,18 +76,32 @@ describe('the plugin', function () {
|
|||||||
it('is creatable', () => {
|
it('is creatable', () => {
|
||||||
expect(objectDef.creatable).toEqual(mockObject.creatable);
|
expect(objectDef.creatable).toEqual(mockObject.creatable);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('provides a timeline view', () => {
|
describe('the view', () => {
|
||||||
|
let timelineView;
|
||||||
|
|
||||||
|
beforeEach((done) => {
|
||||||
const testViewObject = {
|
const testViewObject = {
|
||||||
id: "test-object",
|
id: "test-object",
|
||||||
type: "time-strip"
|
type: "time-strip"
|
||||||
};
|
};
|
||||||
|
|
||||||
const applicableViews = openmct.objectViews.get(testViewObject);
|
const applicableViews = openmct.objectViews.get(testViewObject);
|
||||||
let timelineView = applicableViews.find((viewProvider) => viewProvider.key === 'time-strip.view');
|
timelineView = applicableViews.find((viewProvider) => viewProvider.key === 'time-strip.view');
|
||||||
|
let view = timelineView.view(testViewObject, element);
|
||||||
|
view.show(child, true);
|
||||||
|
Vue.nextTick(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('provides a view', () => {
|
||||||
expect(timelineView).toBeDefined();
|
expect(timelineView).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('displays a time axis', () => {
|
||||||
|
const el = element.querySelector('.c-timesystem-axis');
|
||||||
|
expect(el).toBeDefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
.c-timeline-holder {
|
.c-timeline-holder {
|
||||||
@include abs();
|
@include abs();
|
||||||
}
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
.c-timeline {
|
|
||||||
|
|
||||||
}
|
|
@ -34,7 +34,7 @@
|
|||||||
@import "../ui/components/object-label.scss";
|
@import "../ui/components/object-label.scss";
|
||||||
@import "../ui/components/progress-bar.scss";
|
@import "../ui/components/progress-bar.scss";
|
||||||
@import "../ui/components/search.scss";
|
@import "../ui/components/search.scss";
|
||||||
@import "../ui/components/swim-lane/swim-lane.scss";
|
@import "../ui/components/swim-lane/swimlane.scss";
|
||||||
@import "../ui/components/toggle-switch.scss";
|
@import "../ui/components/toggle-switch.scss";
|
||||||
@import "../ui/components/timesystem-axis.scss";
|
@import "../ui/components/timesystem-axis.scss";
|
||||||
@import "../ui/inspector/elements.scss";
|
@import "../ui/inspector/elements.scss";
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="u-contents"
|
<div class="u-contents"
|
||||||
:class="{'c-swim-lane': !isNested}"
|
:class="{'c-swimlane': !isNested}"
|
||||||
>
|
>
|
||||||
|
|
||||||
<div class="c-swim-lane__lane-label c-object-label"
|
<div class="c-swimlane__lane-label c-object-label"
|
||||||
:class="{'c-swim-lane__lane-label--span-rows': spanRows, 'c-swim-lane__lane-label--span-cols': (!spanRows && !isNested)}"
|
:class="{'c-swimlane__lane-label--span-cols': (!spanRowsCount && !isNested)}"
|
||||||
|
:style="gridRowSpan"
|
||||||
>
|
>
|
||||||
<div v-if="iconClass"
|
<div v-if="iconClass"
|
||||||
class="c-object-label__type-icon"
|
class="c-object-label__type-icon"
|
||||||
@ -17,7 +18,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="c-swim-lane__lane-object"
|
<div class="c-swimlane__lane-object"
|
||||||
:style="{'min-height': minHeight}"
|
:style="{'min-height': minHeight}"
|
||||||
:class="{'u-contents': showUcontents}"
|
:class="{'u-contents': showUcontents}"
|
||||||
data-selectable
|
data-selectable
|
||||||
@ -55,10 +56,19 @@ export default {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
spanRows: {
|
spanRowsCount: {
|
||||||
type: Boolean,
|
type: Number,
|
||||||
default() {
|
default() {
|
||||||
return false;
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
gridRowSpan() {
|
||||||
|
if (this.spanRowsCount) {
|
||||||
|
return `grid-row: span ${this.spanRowsCount}`;
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
.c-swim-lane {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 100px 100px 1fr;
|
|
||||||
grid-column-gap: 1px;
|
|
||||||
grid-row-gap: 1px;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
[class*='__lane-label'] {
|
|
||||||
background: rgba($colorBodyFg, 0.2); // TODO: convert to theme constant
|
|
||||||
color: $colorBodyFg; // TODO: convert to theme constant
|
|
||||||
padding: $interiorMarginSm;
|
|
||||||
}
|
|
||||||
|
|
||||||
[class*='--span-cols'] {
|
|
||||||
grid-column: span 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
[class*='--span-rows'] {
|
|
||||||
grid-row: span 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__lane-object {
|
|
||||||
.c-plan {
|
|
||||||
display: contents;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
30
src/ui/components/swim-lane/swimlane.scss
Normal file
30
src/ui/components/swim-lane/swimlane.scss
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
.c-swimlane {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 100px 100px 1fr;
|
||||||
|
grid-column-gap: 1px;
|
||||||
|
grid-row-gap: 1px;
|
||||||
|
margin-bottom: 1px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
[class*='__lane-label'] {
|
||||||
|
background: rgba($colorBodyFg, 0.2);
|
||||||
|
color: $colorBodyFg;
|
||||||
|
padding: $interiorMarginSm;
|
||||||
|
}
|
||||||
|
|
||||||
|
[class*='--span-cols'] {
|
||||||
|
grid-column: span 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
[class*='--span-rows'] {
|
||||||
|
grid-row: span 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__lane-object {
|
||||||
|
background: rgba(black, 0.1);
|
||||||
|
|
||||||
|
.c-plan {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,35 +1,42 @@
|
|||||||
.c-timesystem-axis {
|
.c-timesystem-axis {
|
||||||
$h: 30px;
|
$h: 30px;
|
||||||
height: $h;
|
height: $h;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
text-rendering: geometricPrecision;
|
$lineC: rgba($colorBodyFg, 0.3) !important;
|
||||||
width: 100%;
|
text-rendering: geometricPrecision;
|
||||||
height: 100%;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
text:not(.activity) {
|
.domain {
|
||||||
// Tick labels
|
stroke: $lineC;
|
||||||
fill: $colorBodyFg;
|
}
|
||||||
paint-order: stroke;
|
|
||||||
font-weight: bold;
|
.tick {
|
||||||
stroke: $colorBodyBg;
|
line {
|
||||||
stroke-linecap: butt;
|
stroke: $lineC;
|
||||||
stroke-linejoin: bevel;
|
}
|
||||||
stroke-width: 6px;
|
|
||||||
|
text {
|
||||||
|
// Tick labels
|
||||||
|
fill: $colorBodyFg;
|
||||||
|
paint-order: stroke;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.nowMarker {
|
.nowMarker {
|
||||||
width: 2px;
|
width: 2px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
background: gray;
|
background: gray;
|
||||||
|
|
||||||
& .icon-arrow-down {
|
& .icon-arrow-down {
|
||||||
font-size: large;
|
font-size: large;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -8px;
|
top: -8px;
|
||||||
left: -8px;
|
left: -8px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user