Convert battery icon to vue component (#2726)

* feat: use vue battery icon
* Add battery icon stories
* feat: use battery state if available
10.9-maintenance
Tomas Chmelevskij 2023-01-07 00:04:21 +01:00 committed by GitHub
parent 1301f97a4f
commit e0209c6b59
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 213 additions and 87 deletions

View File

@ -50,7 +50,7 @@ export const decorators = [
template: ` template: `
<div style="margin: 1rem;"> <div style="margin: 1rem;">
<link rel="stylesheet" href="/css/opensans_webfontkit/fonts.css" /> <link rel="stylesheet" href="/css/opensans_webfontkit/fonts.css" />
<link rel="stylesheet" href="/css/main.css" /> <link rel="stylesheet" href="/css/theme.css" />
<story /> <story />
</div> </div>
`, `,

View File

@ -10,6 +10,7 @@ import vueI18n from "./vueI18n.js";
import BatteryLegend from "./quad-status/BatteryLegend.vue"; import BatteryLegend from "./quad-status/BatteryLegend.vue";
import BetaflightLogo from "./betaflight-logo/BetaflightLogo.vue"; import BetaflightLogo from "./betaflight-logo/BetaflightLogo.vue";
import StatusBar from "./status-bar/StatusBar.vue"; import StatusBar from "./status-bar/StatusBar.vue";
import BatteryIcon from "./quad-status/BatteryIcon.vue";
// Most of the global objects can go here at first. // Most of the global objects can go here at first.
// It's a bit of overkill for simple components, // It's a bit of overkill for simple components,
@ -38,6 +39,7 @@ i18next.on('initialized', function() {
BatteryLegend, BatteryLegend,
BetaflightLogo, BetaflightLogo,
StatusBar, StatusBar,
BatteryIcon,
}, },
data: betaflightModel, data: betaflightModel,
}); });

View File

@ -0,0 +1,32 @@
import BatteryIcon from "./BatteryIcon.vue";
// More on default export: https://storybook.js.org/docs/vue/writing-stories/introduction#default-export
export default {
title: "Battery Icon",
component: BatteryIcon,
};
// More on component templates: https://storybook.js.org/docs/vue/writing-stories/introduction#using-args
const Template = (_args, { argTypes }) => ({
props: Object.keys(argTypes),
components: { BatteryIcon },
template: '<battery-icon v-bind="$props" />',
});
export const OK = Template.bind({});
OK.args = {
voltage: 16,
vbatmincellvoltage: 3.7,
vbatmaxcellvoltage: 4.2,
vbatwarningcellvoltage: 3.8,
};
export const Warning = Template.bind({});
Warning.args = {
...OK.args,
voltage: 15.1,
};
export const Empty = Template.bind({});
Empty.args = {
...OK.args,
voltage: 14,
};

View File

@ -0,0 +1,136 @@
<template>
<div class="battery-icon">
<div class="quad-status-contents">
<div
class="battery-status"
:class="classes"
:style="{ width: batteryWidth + '%' }"
/>
</div>
</div>
</template>
<script>
const NO_BATTERY_VOLTAGE_MAXIMUM = 1.8; // Maybe is better to add a call to MSP_BATTERY_STATE but is not available for all versions
export default {
props: {
voltage: {
type: Number,
default: 0,
},
vbatmincellvoltage: {
type: Number,
default: 1,
},
vbatmaxcellvoltage: {
type: Number,
default: 1,
},
vbatwarningcellvoltage: {
type: Number,
default: 1,
},
},
computed: {
nbCells() {
let nbCells = Math.floor(this.voltage / this.vbatmaxcellvoltage) + 1;
if (this.voltage === 0) {
nbCells = 1;
}
return nbCells;
},
min() {
return this.vbatmincellvoltage * this.nbCells;
},
max() {
return this.vbatmaxcellvoltage * this.nbCells;
},
warn() {
return this.vbatwarningcellvoltage * this.nbCells;
},
isEmpty() {
return (
this.voltage < this.min && this.voltage > NO_BATTERY_VOLTAGE_MAXIMUM
);
},
classes() {
if (this.batteryState) {
return {
"state-ok": this.batteryState === 0,
"state-warning": this.batteryState === 1,
"state-empty": this.batteryState === 2,
// TODO: BATTERY_NOT_PRESENT
// TODO: BATTERY_INIT
};
}
const isWarning = this.voltage < this.warn;
return {
"state-empty": this.isEmpty,
"state-warning": isWarning,
"state-ok": !this.isEmpty && !isWarning,
};
},
batteryWidth() {
return this.isEmpty
? 100
: ((this.voltage - this.min) / (this.max - this.min)) * 100;
},
},
};
</script>
<style>
.quad-status-contents {
display: inline-block;
margin-top: 10px;
margin-left: 14px;
height: 10px;
width: 31px;
}
.quad-status-contents progress::-webkit-progress-bar {
height: 12px;
background-color: #eee;
}
.quad-status-contents progress::-webkit-progress-value {
background-color: #bcf;
}
.battery-icon {
background-image: url(../../images/icons/cf_icon_bat_grey.svg);
background-size: contain;
background-position: center;
display: inline-block;
height: 30px;
width: 60px;
transition: none;
margin-top: 4px;
margin-left: -4px;
background-repeat: no-repeat;
}
.battery-status {
height: 11px;
}
@keyframes error-blinker {
0% {
background-color: transparent;
}
50% {
background-color: var(--error);
}
}
.battery-status.state-ok {
background-color: #59aa29;
}
.battery-status.state-warning {
background-color: var(--error);
}
.battery-status.state-empty {
animation: error-blinker 1s linear infinite;
}
</style>

View File

@ -1,8 +1,8 @@
import BatteryLegend from './BatteryLegend.vue'; import BatteryLegend from "./BatteryLegend.vue";
// More on default export: https://storybook.js.org/docs/vue/writing-stories/introduction#default-export // More on default export: https://storybook.js.org/docs/vue/writing-stories/introduction#default-export
export default { export default {
title: 'Battery Legend', title: "Battery Legend",
component: BatteryLegend, component: BatteryLegend,
}; };

View File

@ -1,37 +1,3 @@
@keyframes error-blinker {
0% {
background-color: transparent;
}
50% {
background-color: var(--error);
}
}
&:root {
--accent: #ffbb00;
--error: red;
--subtleAccent: silver;
--quietText: #ffffff;
--quietHeader: #828885;
--defaultText: #000000;
--subtleText: #c0c0c0;
--mutedText: #616161;
--linkText: #2e2ebb;
--boxBackground: #ffffff;
--alternativeBackground: #f9f9f9;
--sideBackground: #ffffff;
--ledAccent: #adadad;
--ledBackground: #e9e9e9;
--gimbalBackground: #eee;
--gimbalCrosshair: var(--subtleAccent);
--switcherysecond: #c4c4c4;
--pushedButton-background: #c4c4c4;
--pushedButton-fontColor: #000000;
--hoverButton-background: #ffcc3e;
--superSubtleAccent: #CCCCCC;
--accentBorder: #ffbb00;
}
* { * {
margin: 0; margin: 0;
padding: 0; padding: 0;
@ -621,46 +587,7 @@ input[type="number"] {
text-shadow: 0 1px rgba(0, 0, 0, 1.0); text-shadow: 0 1px rgba(0, 0, 0, 1.0);
white-space: nowrap; white-space: nowrap;
} }
.quad-status-contents {
display: none;
margin-top: 10px;
margin-left: 14px;
height: 10px;
width: 31px;
progress {
&::-webkit-progress-bar {
height: 12px;
background-color: #eee;
}
&::-webkit-progress-value {
background-color: #bcf;
}
}
}
.battery-status {
height: 11px;
}
.battery-status.state-ok {
background-color: #59AA29;
}
.battery-status.state-warning {
background-color: var(--error);
}
.battery-status.state-empty {
animation: error-blinker 1s linear infinite;
}
.battery-icon {
background-image: url(../images/icons/cf_icon_bat_grey.svg);
background-size: contain;
background-position: center;
display: inline-block;
height: 30px;
width: 60px;
transition: none;
margin-top: 4px;
margin-left: -4px;
background-repeat: no-repeat;
}
.armedicon { .armedicon {
margin-left: 8px; margin-left: 8px;
margin-right: 8px; margin-right: 8px;

29
src/css/theme.css Normal file
View File

@ -0,0 +1,29 @@
/**
* Main theme colors
* This file is left as css on purpose to make it easier to work without
* the need to compile the less files and still can be use in storybook
*/
:root {
--accent: #ffbb00;
--error: red;
--subtleAccent: silver;
--quietText: #ffffff;
--quietHeader: #828885;
--defaultText: #000000;
--subtleText: #c0c0c0;
--mutedText: #616161;
--linkText: #2e2ebb;
--boxBackground: #ffffff;
--alternativeBackground: #f9f9f9;
--sideBackground: #ffffff;
--ledAccent: #adadad;
--ledBackground: #e9e9e9;
--gimbalBackground: #eee;
--gimbalCrosshair: var(--subtleAccent);
--switcherysecond: #c4c4c4;
--pushedButton-background: #c4c4c4;
--pushedButton-fontColor: #000000;
--hoverButton-background: #ffcc3e;
--superSubtleAccent: #cccccc;
--accentBorder: #ffbb00;
}

View File

@ -659,10 +659,6 @@ async function getStatus() {
async function update_live_status() { async function update_live_status() {
const statuswrapper = $('#quad-status_wrapper'); const statuswrapper = $('#quad-status_wrapper');
$(".quad-status-contents").css({
display: 'inline-block',
});
if (GUI.active_tab !== 'cli' && GUI.active_tab !== 'presets') { if (GUI.active_tab !== 'cli' && GUI.active_tab !== 'presets') {
await MSP.promise(MSPCodes.MSP_BOXNAMES); await MSP.promise(MSPCodes.MSP_BOXNAMES);
await getStatus(); await getStatus();

View File

@ -7,6 +7,7 @@
<link type="text/css" rel="stylesheet" href="./js/libraries/jquery.nouislider.min.css"/> <link type="text/css" rel="stylesheet" href="./js/libraries/jquery.nouislider.min.css"/>
<link type="text/css" rel="stylesheet" href="./js/libraries/jquery.nouislider.pips.min.css"/> <link type="text/css" rel="stylesheet" href="./js/libraries/jquery.nouislider.pips.min.css"/>
<link type="text/css" rel="stylesheet" href="./js/libraries/flightindicators.css"/> <link type="text/css" rel="stylesheet" href="./js/libraries/flightindicators.css"/>
<link type="text/css" rel="stylesheet" href="./css/theme.css" media="all"/>
<link type="text/css" rel="stylesheet" href="./css/main.css" media="all"/> <link type="text/css" rel="stylesheet" href="./css/main.css" media="all"/>
<link type="text/css" rel="stylesheet" href="./css/tabs/static_tab.css" media="all"/> <link type="text/css" rel="stylesheet" href="./css/tabs/static_tab.css" media="all"/>
<link type="text/css" rel="stylesheet" href="./css/tabs/landing.css" media="all"/> <link type="text/css" rel="stylesheet" href="./css/tabs/landing.css" media="all"/>
@ -184,12 +185,15 @@
</div> </div>
<div class="header-wrapper"> <div class="header-wrapper">
<div id="quad-status_wrapper"> <div id="quad-status_wrapper">
<div class="battery-icon"> <battery-icon
<div class="quad-status-contents"> :voltage="FC.ANALOG.voltage"
<div class="battery-status"></div> :vbatmincellvoltage="FC.BATTERY_CONFIG.vbatmincellvoltage"
</div> :vbatmaxcellvoltage="FC.BATTERY_CONFIG.vbatmaxcellvoltage"
</div> :vbatwarningcellvoltage="FC.BATTERY_CONFIG.vbatwarningcellvoltage"
<battery-legend :batteryState="FC.BATTERY_STATE?.batteryState"
>
</battery-icon>
<battery-legend
:voltage="FC.ANALOG.voltage" :voltage="FC.ANALOG.voltage"
:vbatmaxcellvoltage="FC.BATTERY_CONFIG.vbatmaxcellvoltage" :vbatmaxcellvoltage="FC.BATTERY_CONFIG.vbatmaxcellvoltage"
></battery-legend> ></battery-legend>