Introduce Storybook and refactor Vue components (#524)
This commit is contained in:
parent
95cfe455b8
commit
2d3bf1bec9
31 changed files with 17513 additions and 8161 deletions
|
|
@ -1,3 +1,5 @@
|
|||
.cache
|
||||
.storybook
|
||||
.vscode
|
||||
*.conf
|
||||
*.Dockerfile
|
||||
|
|
@ -5,7 +7,6 @@
|
|||
*.sh
|
||||
*.yaml
|
||||
Dockerfile
|
||||
.cache
|
||||
dist
|
||||
node_modules
|
||||
!entrypoint.sh
|
||||
|
|
|
|||
4
.storybook/main.js
Normal file
4
.storybook/main.js
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
module.exports = {
|
||||
stories: ["../assets/js/**/*.stories.mdx", "../assets/js/**/*.stories.@(js|jsx|ts|tsx)"],
|
||||
addons: ["@storybook/addon-links", "@storybook/addon-essentials"],
|
||||
};
|
||||
2
.storybook/preview-head.html
Normal file
2
.storybook/preview-head.html
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<link href="/css/bootstrap.min.css" rel="stylesheet" />
|
||||
<link href="/css/app.css" rel="stylesheet" />
|
||||
4
.storybook/preview.js
Normal file
4
.storybook/preview.js
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
export const parameters = {
|
||||
actions: { argTypesRegex: "^on[A-Z].*" },
|
||||
}
|
||||
|
|
@ -2,40 +2,11 @@ import "popper.js";
|
|||
import "bootstrap";
|
||||
import Vue from "vue";
|
||||
import axios from "axios";
|
||||
import { library } from "@fortawesome/fontawesome-svg-core";
|
||||
import { faSun } from "@fortawesome/free-solid-svg-icons/faSun";
|
||||
import { faArrowUp } from "@fortawesome/free-solid-svg-icons/faArrowUp";
|
||||
import { faArrowDown } from "@fortawesome/free-solid-svg-icons/faArrowDown";
|
||||
import { faBatteryThreeQuarters } from "@fortawesome/free-solid-svg-icons/faBatteryThreeQuarters";
|
||||
import { faTemperatureLow } from "@fortawesome/free-solid-svg-icons/faTemperatureLow";
|
||||
import { faTemperatureHigh } from "@fortawesome/free-solid-svg-icons/faTemperatureHigh";
|
||||
import { faThermometerHalf } from "@fortawesome/free-solid-svg-icons/faThermometerHalf";
|
||||
import { faLeaf } from "@fortawesome/free-solid-svg-icons/faLeaf";
|
||||
import { faChevronUp } from "@fortawesome/free-solid-svg-icons/faChevronUp";
|
||||
import { faChevronDown } from "@fortawesome/free-solid-svg-icons/faChevronDown";
|
||||
import { faExclamationTriangle } from "@fortawesome/free-solid-svg-icons/faExclamationTriangle";
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
import App from "./views/App";
|
||||
import Toasts from "./components/Toasts";
|
||||
import router from "./router";
|
||||
import store from "./store";
|
||||
|
||||
library.add(
|
||||
faSun,
|
||||
faArrowUp,
|
||||
faArrowDown,
|
||||
faBatteryThreeQuarters,
|
||||
faTemperatureLow,
|
||||
faTemperatureHigh,
|
||||
faThermometerHalf,
|
||||
faLeaf,
|
||||
faChevronUp,
|
||||
faChevronDown,
|
||||
faExclamationTriangle
|
||||
);
|
||||
|
||||
Vue.component("fa-icon", FontAwesomeIcon);
|
||||
|
||||
const loc = window.location;
|
||||
axios.defaults.baseURL =
|
||||
loc.protocol + "//" + loc.hostname + (loc.port ? ":" + loc.port : "") + loc.pathname + "api";
|
||||
|
|
|
|||
29
assets/js/components/Loadpoint.stories.js
Normal file
29
assets/js/components/Loadpoint.stories.js
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import Loadpoint from "./Loadpoint.vue";
|
||||
|
||||
export default {
|
||||
title: "Main/Loadpoint",
|
||||
component: Loadpoint,
|
||||
argTypes: {
|
||||
mode: { control: { type: "inline-radio", options: ["off", "now", "minpv", "pv"] } },
|
||||
climater: { control: { type: "inline-radio", options: ["on", "heating", "cooling"] } },
|
||||
},
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: { Loadpoint },
|
||||
template: '<Loadpoint v-bind="$props"></Loadpoint>',
|
||||
});
|
||||
|
||||
export const Base = Template.bind({});
|
||||
Base.args = {
|
||||
id: 0,
|
||||
pvConfigured: true,
|
||||
};
|
||||
|
||||
export const WithLevels = Template.bind({});
|
||||
WithLevels.args = {
|
||||
id: 0,
|
||||
pvConfigured: true,
|
||||
socLevels: [20, 50, 80, 100],
|
||||
};
|
||||
|
|
@ -2,11 +2,11 @@
|
|||
<div>
|
||||
<div class="row" v-if="multi">
|
||||
<div class="col-12 col-md-4 d-md-flex mt-3 mt-md-5 align-items-end">
|
||||
<span class="h1 align-bottom">{{ state.title || "Ladepunkt" }}</span>
|
||||
<span class="h1 align-bottom">{{ title || "Ladepunkt" }}</span>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-8 d-none d-md-block mt-3 mt-md-5">
|
||||
<LoadpointDetails v-bind:state="state"></LoadpointDetails>
|
||||
<LoadpointDetails v-bind="details"> </LoadpointDetails>
|
||||
</div>
|
||||
|
||||
<div class="col-12 d-md-none">
|
||||
|
|
@ -14,17 +14,17 @@
|
|||
<div class="col-12 mt-3">
|
||||
<Mode
|
||||
class="w-100"
|
||||
v-bind:mode="state.mode"
|
||||
:pv="pv"
|
||||
v-on:updated="targetMode"
|
||||
:mode="mode"
|
||||
:pvConfigured="pvConfigured"
|
||||
v-on:updated="setTargetMode"
|
||||
></Mode>
|
||||
</div>
|
||||
<div class="col-12 mt-3" v-if="hasTargetSoC">
|
||||
<Soc
|
||||
class="w-100"
|
||||
v-bind:soc="state.targetSoC"
|
||||
:levels="state.socLevels"
|
||||
v-on:updated="targetSoC"
|
||||
:soc="targetSoC"
|
||||
:levels="socLevels"
|
||||
v-on:updated="setTargetSoC"
|
||||
></Soc>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -32,23 +32,20 @@
|
|||
</div>
|
||||
|
||||
<div class="row d-none d-md-flex mt-5 py-3 pb-4 text-center bg-light" v-if="!multi">
|
||||
<div
|
||||
class="mt-3"
|
||||
v-bind:class="{ 'col-md-6': hasTargetSoC, 'col-md-12': !hasTargetSoC }"
|
||||
>
|
||||
<div class="mt-3" :class="{ 'col-md-6': hasTargetSoC, 'col-md-12': !hasTargetSoC }">
|
||||
<Mode
|
||||
v-bind:mode="state.mode"
|
||||
:pv="pv"
|
||||
:mode="mode"
|
||||
:pvConfigured="pvConfigured"
|
||||
:caption="true"
|
||||
v-on:updated="targetMode"
|
||||
v-on:updated="setTargetMode"
|
||||
></Mode>
|
||||
</div>
|
||||
<div class="col-md-6 mt-3" v-if="hasTargetSoC">
|
||||
<Soc
|
||||
v-bind:soc="state.targetSoC"
|
||||
:levels="state.socLevels"
|
||||
:soc="targetSoC"
|
||||
:levels="socLevels"
|
||||
:caption="true"
|
||||
v-on:updated="targetSoC"
|
||||
v-on:updated="setTargetSoC"
|
||||
></Soc>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -57,35 +54,32 @@
|
|||
<div class="col-12 mt-3">
|
||||
<Mode
|
||||
class="w-100"
|
||||
v-bind:mode="state.mode"
|
||||
:pv="pv"
|
||||
v-on:updated="targetMode"
|
||||
:mode="mode"
|
||||
:pvConfigured="pvConfigured"
|
||||
v-on:updated="setTargetMode"
|
||||
></Mode>
|
||||
</div>
|
||||
<div class="col-12 mt-3" v-if="hasTargetSoC">
|
||||
<Soc
|
||||
class="w-100"
|
||||
v-bind:soc="state.targetSoC"
|
||||
:levels="state.socLevels"
|
||||
v-on:updated="targetSoC"
|
||||
:soc="targetSoC"
|
||||
:levels="socLevels"
|
||||
v-on:updated="setTargetSoC"
|
||||
></Soc>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" v-if="!multi">
|
||||
<div class="col-12 col-md-4 d-none d-md-flex mt-3 mt-md-5">
|
||||
<span class="h1">{{ state.title || "Ladepunkt" }}</span>
|
||||
<span class="h1">{{ title || "Ladepunkt" }}</span>
|
||||
</div>
|
||||
<div
|
||||
class="col-12 col-md-8 d-flex d-md-flex mt-3 mt-md-5 pt-3"
|
||||
v-if="state.remoteDisabled"
|
||||
>
|
||||
<div class="col-12 col-md-8 d-flex d-md-flex mt-3 mt-md-5 pt-3" v-if="remoteDisabled">
|
||||
<h5 class="w-100">
|
||||
<span class="badge badge-warning w-100" v-if="state.remoteDisabled == 'soft'">
|
||||
{{ state.remoteDisabledSource }}: Adaptives PV-Laden deaktiviert
|
||||
<span class="badge badge-warning w-100" v-if="remoteDisabled == 'soft'">
|
||||
{{ remoteDisabledSource }}: Adaptives PV-Laden deaktiviert
|
||||
</span>
|
||||
<span class="badge badge-danger w-100" v-if="state.remoteDisabled == 'hard'">
|
||||
{{ state.remoteDisabledSource }}: Deaktiviert
|
||||
<span class="badge badge-danger w-100" v-if="remoteDisabled == 'hard'">
|
||||
{{ remoteDisabledSource }}: Deaktiviert
|
||||
</span>
|
||||
</h5>
|
||||
</div>
|
||||
|
|
@ -95,34 +89,41 @@
|
|||
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-4 mt-3 mb-3 mb-md-0">
|
||||
<Vehicle v-bind:state="state"></Vehicle>
|
||||
<Vehicle
|
||||
:charging="charging"
|
||||
:connected="connected"
|
||||
:minSoC="minSoC"
|
||||
:soc="soc"
|
||||
:socCharge="socCharge"
|
||||
:socTitle="socTitle"
|
||||
></Vehicle>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-4 d-none d-md-block mt-3" v-if="multi">
|
||||
<div class="mb-2">Modus</div>
|
||||
<Mode
|
||||
class="btn-group-sm"
|
||||
v-bind:mode="state.mode"
|
||||
:pv="pv"
|
||||
v-on:updated="targetMode"
|
||||
:mode="mode"
|
||||
:pvConfigured="pvConfigured"
|
||||
v-on:updated="setTargetMode"
|
||||
></Mode>
|
||||
</div>
|
||||
<div class="col-12 col-md-4 d-none d-md-block mt-3" v-if="multi && hasTargetSoC">
|
||||
<div class="mb-2">Ladeziel</div>
|
||||
<Soc
|
||||
class="btn-group-sm"
|
||||
v-bind:soc="state.targetSoC"
|
||||
:levels="state.socLevels"
|
||||
v-on:updated="targetSoC"
|
||||
:soc="targetSoC"
|
||||
:levels="socLevels"
|
||||
v-on:updated="setTargetSoC"
|
||||
></Soc>
|
||||
</div>
|
||||
|
||||
<div class="col-md-8 d-none d-md-block" v-if="!multi">
|
||||
<LoadpointDetails v-bind:state="state"></LoadpointDetails>
|
||||
<LoadpointDetails v-bind="details"></LoadpointDetails>
|
||||
</div>
|
||||
|
||||
<div class="col-12 d-md-none">
|
||||
<LoadpointDetails v-bind:state="state"></LoadpointDetails>
|
||||
<LoadpointDetails v-bind="details"></LoadpointDetails>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -138,28 +139,72 @@ import formatter from "../mixins/formatter";
|
|||
|
||||
export default {
|
||||
name: "Loadpoint",
|
||||
props: ["state", "id", "pv", "multi"],
|
||||
props: {
|
||||
id: Number,
|
||||
multi: Boolean,
|
||||
pvConfigured: Boolean,
|
||||
|
||||
// main
|
||||
title: String,
|
||||
mode: String,
|
||||
targetSoC: Number,
|
||||
socLevels: Array,
|
||||
remoteDisabled: Boolean,
|
||||
remoteDisabledSource: String,
|
||||
chargeDuration: Number,
|
||||
charging: Boolean,
|
||||
|
||||
// vehicle
|
||||
connected: Boolean,
|
||||
// charging: Boolean,
|
||||
socTitle: String,
|
||||
socCharge: Number,
|
||||
minSoC: Number,
|
||||
|
||||
// details
|
||||
chargePower: Number,
|
||||
chargedEnergy: Number,
|
||||
// chargeDuration: Number,
|
||||
soc: Boolean,
|
||||
climater: String,
|
||||
range: Number,
|
||||
chargeEstimate: Number,
|
||||
},
|
||||
components: { LoadpointDetails, Soc, Mode, Vehicle },
|
||||
mixins: [formatter],
|
||||
data: function () {
|
||||
return {
|
||||
tickerHandle: null,
|
||||
chargeDurationDisplayed: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
details: function () {
|
||||
return {
|
||||
connected: this.connected,
|
||||
minSoC: this.minSoC,
|
||||
socCharge: this.socCharge,
|
||||
climater: this.climater,
|
||||
chargePower: this.chargePower,
|
||||
chargedEnergy: this.chargedEnergy,
|
||||
range: this.range,
|
||||
chargeDuration: this.chargeDurationDisplayed,
|
||||
chargeEstimate: this.chargeEstimate,
|
||||
};
|
||||
},
|
||||
hasTargetSoC: function () {
|
||||
return this.state.socLevels != null && this.state.socLevels.length > 0;
|
||||
return this.socLevels != null && this.socLevels.length > 0;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
"state.chargeDuration": function () {
|
||||
chargeDuration: function () {
|
||||
window.clearInterval(this.tickerHandle);
|
||||
// only ticker if actually charging
|
||||
if (this.state.charging && this.state.chargeDuration >= 0) {
|
||||
if (this.charging && this.chargeDuration >= 0) {
|
||||
this.chargeDurationDisplayed = this.chargeDuration;
|
||||
this.tickerHandle = window.setInterval(
|
||||
function () {
|
||||
// eslint-disable-next-line vue/no-mutating-props
|
||||
this.state.chargeDuration += 1;
|
||||
this.chargeDurationDisplayed += 1;
|
||||
}.bind(this),
|
||||
1000
|
||||
);
|
||||
|
|
@ -170,24 +215,24 @@ export default {
|
|||
api: function (func) {
|
||||
return "loadpoints/" + this.id + "/" + func;
|
||||
},
|
||||
targetMode: function (mode) {
|
||||
setTargetMode: function (mode) {
|
||||
axios
|
||||
.post(this.api("mode") + "/" + mode)
|
||||
.then(
|
||||
function (response) {
|
||||
// eslint-disable-next-line vue/no-mutating-props
|
||||
this.state.mode = response.data.mode;
|
||||
this.mode = response.data.mode;
|
||||
}.bind(this)
|
||||
)
|
||||
.catch(window.toasts.error);
|
||||
},
|
||||
targetSoC: function (soc) {
|
||||
setTargetSoC: function (soc) {
|
||||
axios
|
||||
.post(this.api("targetsoc") + "/" + soc)
|
||||
.then(
|
||||
function (response) {
|
||||
// eslint-disable-next-line vue/no-mutating-props
|
||||
this.state.targetSoC = response.data.targetSoC;
|
||||
this.targetSoC = response.data.targetSoC;
|
||||
}.bind(this)
|
||||
)
|
||||
.catch(window.toasts.error);
|
||||
|
|
|
|||
55
assets/js/components/LoadpointDetails.stories.js
Normal file
55
assets/js/components/LoadpointDetails.stories.js
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import LoadpointDetails from "./LoadpointDetails.vue";
|
||||
|
||||
export default {
|
||||
title: "Main/LoadpointDetails",
|
||||
component: LoadpointDetails,
|
||||
argTypes: {
|
||||
climater: { control: { type: "inline-radio", options: ["on", "heating", "cooling"] } },
|
||||
},
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: { LoadpointDetails },
|
||||
template: '<LoadpointDetails v-bind="$props"></LoadpointDetails>',
|
||||
});
|
||||
|
||||
export const Base = Template.bind({});
|
||||
Base.args = {};
|
||||
|
||||
export const Charging = Template.bind({});
|
||||
Charging.args = {
|
||||
chargePower: 2800,
|
||||
chargedEnergy: 11e3,
|
||||
chargeDuration: 95 * 60,
|
||||
};
|
||||
|
||||
export const Vehicle = Template.bind({});
|
||||
Vehicle.args = {
|
||||
chargePower: 2800,
|
||||
chargedEnergy: 11e3,
|
||||
chargeDuration: 95 * 60,
|
||||
soc: true,
|
||||
chargeEstimate: 5 * 3600,
|
||||
};
|
||||
|
||||
export const VehicleRange = Template.bind({});
|
||||
VehicleRange.args = {
|
||||
chargePower: 2800,
|
||||
chargedEnergy: 11e3,
|
||||
chargeDuration: 95 * 60,
|
||||
soc: true,
|
||||
range: 240.123,
|
||||
chargeEstimate: 5 * 3600,
|
||||
};
|
||||
|
||||
export const VehicleClimater = Template.bind({});
|
||||
VehicleClimater.args = {
|
||||
chargePower: 2800,
|
||||
chargedEnergy: 11e3,
|
||||
chargeDuration: 95 * 60,
|
||||
soc: true,
|
||||
range: 240.123,
|
||||
chargeEstimate: 5 * 3600,
|
||||
climater: "on",
|
||||
};
|
||||
|
|
@ -6,36 +6,36 @@
|
|||
<fa-icon
|
||||
class="text-primary ml-1"
|
||||
icon="temperature-low"
|
||||
v-if="state.climater == 'heating'"
|
||||
v-if="climater == 'heating'"
|
||||
></fa-icon>
|
||||
<fa-icon
|
||||
class="text-primary ml-1"
|
||||
icon="temperature-high"
|
||||
v-if="state.climater == 'cooling'"
|
||||
v-if="climater == 'cooling'"
|
||||
></fa-icon>
|
||||
<fa-icon
|
||||
class="text-primary ml-1"
|
||||
icon="thermometer-half"
|
||||
v-if="state.climater == 'on'"
|
||||
v-if="climater == 'on'"
|
||||
></fa-icon>
|
||||
</div>
|
||||
<h2 class="value">
|
||||
{{ fmt(state.chargePower) }}
|
||||
<small class="text-muted">{{ fmtUnit(state.chargePower) }}W</small>
|
||||
{{ fmt(chargePower) }}
|
||||
<small class="text-muted">{{ fmtUnit(chargePower) }}W</small>
|
||||
</h2>
|
||||
</div>
|
||||
<div class="col-6 col-md-3 mt-3">
|
||||
<div class="mb-2 value">Geladen</div>
|
||||
<h2 class="value">
|
||||
{{ fmt(state.chargedEnergy) }}
|
||||
<small class="text-muted">{{ fmtUnit(state.chargedEnergy) }}Wh</small>
|
||||
{{ fmt(chargedEnergy) }}
|
||||
<small class="text-muted">{{ fmtUnit(chargedEnergy) }}Wh</small>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="col-6 col-md-3 mt-3" v-if="state.range >= 0">
|
||||
<div class="col-6 col-md-3 mt-3" v-if="range >= 0">
|
||||
<div class="mb-2 value">Reichweite</div>
|
||||
<h2 class="value">
|
||||
{{ state.range }}
|
||||
{{ Math.round(range) }}
|
||||
<small class="text-muted">km</small>
|
||||
</h2>
|
||||
</div>
|
||||
|
|
@ -43,36 +43,36 @@
|
|||
<div class="col-6 col-md-3 mt-3" v-else>
|
||||
<div class="mb-2 value">Dauer</div>
|
||||
<h2 class="value">
|
||||
{{ fmtShortDuration(state.chargeDuration) }}
|
||||
<small class="text-muted">{{ fmtShortDurationUnit(state.chargeDuration) }}</small>
|
||||
{{ fmtShortDuration(chargeDuration) }}
|
||||
<small class="text-muted">{{ fmtShortDurationUnit(chargeDuration) }}</small>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="col-6 col-md-3 mt-3" v-if="state.soc">
|
||||
<div class="col-6 col-md-3 mt-3" v-if="soc">
|
||||
<div class="mb-2 value">Restzeit</div>
|
||||
<h2 class="value">
|
||||
{{ fmtShortDuration(state.chargeEstimate) }}
|
||||
<small class="text-muted">{{ fmtShortDurationUnit(state.chargeEstimate) }}</small>
|
||||
{{ fmtShortDuration(chargeEstimate) }}
|
||||
<small class="text-muted">{{ fmtShortDurationUnit(chargeEstimate) }}</small>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import "../icons";
|
||||
import formatter from "../mixins/formatter";
|
||||
|
||||
export default {
|
||||
name: "LoadpointDetails",
|
||||
props: ["state"],
|
||||
mixins: [formatter],
|
||||
computed: {
|
||||
minSoCActive: function () {
|
||||
return (
|
||||
this.state.connected &&
|
||||
this.state.minSoC > 0 &&
|
||||
this.state.socCharge < this.state.minSoC
|
||||
);
|
||||
},
|
||||
props: {
|
||||
chargePower: Number,
|
||||
chargedEnergy: Number,
|
||||
chargeDuration: Number,
|
||||
soc: Boolean,
|
||||
climater: String,
|
||||
range: Number,
|
||||
chargeEstimate: Number,
|
||||
},
|
||||
mixins: [formatter],
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
25
assets/js/components/Mode.stories.js
Normal file
25
assets/js/components/Mode.stories.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import Mode from "./Mode.vue";
|
||||
|
||||
export default {
|
||||
title: "Main/Mode",
|
||||
component: Mode,
|
||||
argTypes: {
|
||||
mode: { control: { type: "inline-radio", options: ["off", "now", "minpv", "pv"] } },
|
||||
},
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: { Mode },
|
||||
template: '<Mode v-bind="$props"></Mode>',
|
||||
});
|
||||
|
||||
export const Base = Template.bind({});
|
||||
Base.args = {};
|
||||
|
||||
export const CaptionAndPV = Template.bind({});
|
||||
CaptionAndPV.args = {
|
||||
caption: true,
|
||||
pvConfigured: true,
|
||||
mode: "pv",
|
||||
};
|
||||
|
|
@ -6,22 +6,27 @@
|
|||
>
|
||||
Modus
|
||||
</label>
|
||||
<label class="btn btn-outline-primary" :class="{ active: mode == 'off', first: !caption }">
|
||||
<input type="radio" value="off" v-on:click="setTargetMode('off')" />Stop
|
||||
</label>
|
||||
<label class="btn btn-outline-primary" :class="{ active: mode == 'now' }">
|
||||
<input type="radio" value="now" v-on:click="setTargetMode('now')" />Sofort
|
||||
</label>
|
||||
<label
|
||||
class="btn btn-outline-primary"
|
||||
v-bind:class="{ active: mode == 'off', first: !caption }"
|
||||
:class="{ active: mode == 'minpv' }"
|
||||
v-if="pvConfigured"
|
||||
>
|
||||
<input type="radio" value="off" v-on:click="targetMode('off')" />Stop
|
||||
</label>
|
||||
<label class="btn btn-outline-primary" v-bind:class="{ active: mode == 'now' }">
|
||||
<input type="radio" value="now" v-on:click="targetMode('now')" />Sofort
|
||||
</label>
|
||||
<label class="btn btn-outline-primary" v-bind:class="{ active: mode == 'minpv' }" v-if="pv">
|
||||
<input type="radio" value="minpv" v-on:click="targetMode('minpv')" />
|
||||
<input type="radio" value="minpv" v-on:click="setTargetMode('minpv')" />
|
||||
<span class="d-inline d-lg-none">Min</span>
|
||||
<span class="d-none d-lg-inline">Min + PV</span>
|
||||
</label>
|
||||
<label class="btn btn-outline-primary" v-bind:class="{ active: mode == 'pv' }" v-if="pv">
|
||||
<input type="radio" value="pv" v-on:click="targetMode('pv')" />
|
||||
<label
|
||||
class="btn btn-outline-primary"
|
||||
:class="{ active: mode == 'pv' }"
|
||||
v-if="pvConfigured"
|
||||
>
|
||||
<input type="radio" value="pv" v-on:click="setTargetMode('pv')" />
|
||||
<span class="d-inline d-md-none">PV</span>
|
||||
<span class="d-none d-md-inline">Nur PV</span>
|
||||
</label>
|
||||
|
|
@ -31,9 +36,13 @@
|
|||
<script>
|
||||
export default {
|
||||
name: "Mode",
|
||||
props: ["mode", "pv", "caption"],
|
||||
props: {
|
||||
mode: String,
|
||||
pvConfigured: Boolean,
|
||||
caption: Boolean,
|
||||
},
|
||||
methods: {
|
||||
targetMode: function (mode) {
|
||||
setTargetMode: function (mode) {
|
||||
this.$emit("updated", mode);
|
||||
},
|
||||
},
|
||||
|
|
|
|||
62
assets/js/components/Site.stories.js
Normal file
62
assets/js/components/Site.stories.js
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import Site from "./Site.vue";
|
||||
|
||||
export default {
|
||||
title: "Main/Site",
|
||||
component: Site,
|
||||
argTypes: {},
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: { Site },
|
||||
template: '<Site v-bind="$props"></Site>',
|
||||
});
|
||||
|
||||
export const Base = Template.bind({});
|
||||
Base.args = {
|
||||
gridConfigured: true,
|
||||
pvConfigured: true,
|
||||
gridPower: 100,
|
||||
pvPower: 100,
|
||||
batteryConfigured: true,
|
||||
batteryPower: 100,
|
||||
batterySoC: 0,
|
||||
loadpoints: [],
|
||||
};
|
||||
|
||||
export const Single = Template.bind({});
|
||||
Single.args = {
|
||||
gridConfigured: true,
|
||||
pvConfigured: true,
|
||||
gridPower: 100,
|
||||
pvPower: 100,
|
||||
batteryConfigured: true,
|
||||
batteryPower: 100,
|
||||
batterySoC: 0,
|
||||
loadpoints: [
|
||||
{
|
||||
title: "Ladepunkt 1",
|
||||
socLevels: [20, 50, 80, 100],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const Multi = Template.bind({});
|
||||
Multi.args = {
|
||||
gridConfigured: true,
|
||||
pvConfigured: true,
|
||||
gridPower: 100,
|
||||
pvPower: 100,
|
||||
batteryConfigured: true,
|
||||
batteryPower: 100,
|
||||
batterySoC: 0,
|
||||
loadpoints: [
|
||||
{
|
||||
title: "Ladepunkt 1",
|
||||
socLevels: [20, 50, 80, 100],
|
||||
},
|
||||
{
|
||||
title: "Ladepunkt 2",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
@ -2,10 +2,18 @@
|
|||
<div>
|
||||
<div class="row">
|
||||
<div class="d-none d-md-flex col-12 col-md-4 mt-md-4 align-items-end">
|
||||
<p class="h1">{{ state.title || "Home" }}</p>
|
||||
<p class="h1">{{ title || "Home" }}</p>
|
||||
</div>
|
||||
<div class="col-12 col-md-8 mt-md-4" v-if="multi">
|
||||
<SiteDetails v-bind:state="state"></SiteDetails>
|
||||
<SiteDetails
|
||||
:gridConfigured="gridConfigured"
|
||||
:gridPower="gridPower"
|
||||
:pvConfigured="pvConfigured"
|
||||
:pvPower="pvPower"
|
||||
:batteryConfigured="batteryConfigured"
|
||||
:batteryPower="batteryPower"
|
||||
:batterySoC="batterySoC"
|
||||
></SiteDetails>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -14,17 +22,25 @@
|
|||
<div class="row" v-if="!multi">
|
||||
<div class="d-none d-md-block col-md-4"></div>
|
||||
<div class="col-12 col-md-8">
|
||||
<SiteDetails v-bind:state="state"></SiteDetails>
|
||||
<SiteDetails
|
||||
:gridConfigured="gridConfigured"
|
||||
:gridPower="gridPower"
|
||||
:pvConfigured="pvConfigured"
|
||||
:pvPower="pvPower"
|
||||
:batteryConfigured="batteryConfigured"
|
||||
:batteryPower="batteryPower"
|
||||
:batterySoC="batterySoC"
|
||||
></SiteDetails>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Loadpoint
|
||||
v-for="(loadpoint, id) in state.loadpoints"
|
||||
v-bind:id="id"
|
||||
v-for="(loadpoint, id) in loadpoints"
|
||||
v-bind="loadpoint"
|
||||
:id="id"
|
||||
:key="id"
|
||||
:state="loadpoint"
|
||||
:pv="state.gridConfigured"
|
||||
:multi="multi"
|
||||
:pvConfigured="pvConfigured"
|
||||
>
|
||||
</Loadpoint>
|
||||
</div>
|
||||
|
|
@ -37,13 +53,25 @@ import formatter from "../mixins/formatter";
|
|||
|
||||
export default {
|
||||
name: "Site",
|
||||
props: ["state"],
|
||||
props: {
|
||||
title: String,
|
||||
loadpoints: Array,
|
||||
|
||||
// details
|
||||
gridConfigured: Boolean,
|
||||
gridPower: Number,
|
||||
pvConfigured: Boolean,
|
||||
pvPower: Number,
|
||||
batteryConfigured: Boolean,
|
||||
batteryPower: Number,
|
||||
batterySoC: Number,
|
||||
},
|
||||
components: { SiteDetails, Loadpoint },
|
||||
mixins: [formatter],
|
||||
computed: {
|
||||
multi: function () {
|
||||
// TODO fix compact
|
||||
return this.state.loadpoints.length > 1 /* || app.compact*/;
|
||||
return this.loadpoints.length > 1 /* || app.compact*/;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
32
assets/js/components/SiteDetails.stories.js
Normal file
32
assets/js/components/SiteDetails.stories.js
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import SiteDetails from "./SiteDetails.vue";
|
||||
|
||||
export default {
|
||||
title: "Main/SiteDetails",
|
||||
component: SiteDetails,
|
||||
argTypes: {},
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: { SiteDetails },
|
||||
template: '<SiteDetails v-bind="$props"></SiteDetails>',
|
||||
});
|
||||
|
||||
export const GridAndPV = Template.bind({});
|
||||
GridAndPV.args = {
|
||||
gridConfigured: true,
|
||||
pvConfigured: true,
|
||||
gridPower: 100,
|
||||
pvPower: 100,
|
||||
};
|
||||
|
||||
export const WithBattery = Template.bind({});
|
||||
WithBattery.args = {
|
||||
gridConfigured: true,
|
||||
pvConfigured: true,
|
||||
batteryConfigured: true,
|
||||
gridPower: 100,
|
||||
pvPower: 100,
|
||||
batteryPower: 100,
|
||||
batterySoC: 0,
|
||||
};
|
||||
|
|
@ -1,74 +1,75 @@
|
|||
<template>
|
||||
<div class="row">
|
||||
<div class="col-6 col-md-3 mt-3" v-if="state.gridConfigured">
|
||||
<div class="mb-2 value" v-if="state.gridPower > 0">
|
||||
Bezug <fa-icon icon="arrow-down" class="text-primary"></fa-icon>
|
||||
<div class="col-6 col-md-3 mt-3" v-if="gridConfigured">
|
||||
<div class="mb-2 value" v-if="gridPower > 0">
|
||||
Bezug <fa-icon icon="arrow-down" class="text-primary" />
|
||||
</div>
|
||||
<div class="mb-2 value" v-else>
|
||||
Einspeisung <fa-icon icon="arrow-up" class="text-primary"></fa-icon>
|
||||
</div>
|
||||
<h2 class="value">
|
||||
{{ fmt(state.gridPower) }}
|
||||
<small class="text-muted">{{ fmtUnit(state.gridPower) }}W</small>
|
||||
{{ fmt(gridPower) }}
|
||||
<small class="text-muted">{{ fmtUnit(gridPower) }}W</small>
|
||||
</h2>
|
||||
</div>
|
||||
<div class="col-6 col-md-3 mt-3" v-if="state.pvConfigured">
|
||||
<div class="col-6 col-md-3 mt-3" v-if="pvConfigured">
|
||||
<div class="mb-2 value">
|
||||
Erzeugung
|
||||
<fa-icon
|
||||
icon="sun"
|
||||
v-bind:class="{
|
||||
'text-primary': state.pvPower < 0,
|
||||
'text-muted': state.pvPower >= 0,
|
||||
:class="{
|
||||
'text-primary': pvPower < 0,
|
||||
'text-muted': pvPower >= 0,
|
||||
}"
|
||||
></fa-icon>
|
||||
</div>
|
||||
<h2 class="value">
|
||||
{{ fmt(state.pvPower) }}
|
||||
<small class="text-muted">{{ fmtUnit(state.pvPower) }}W</small>
|
||||
{{ fmt(pvPower) }}
|
||||
<small class="text-muted">{{ fmtUnit(pvPower) }}W</small>
|
||||
</h2>
|
||||
</div>
|
||||
<div class="d-md-block col-6 col-md-3 mt-3" v-if="state.batteryConfigured">
|
||||
<div class="d-md-block col-6 col-md-3 mt-3" v-if="batteryConfigured">
|
||||
<div class="mb-2 value">
|
||||
Batterie
|
||||
<fa-icon
|
||||
class="text-primary"
|
||||
icon="arrow-down"
|
||||
v-if="state.batteryPower < 0"
|
||||
></fa-icon>
|
||||
<fa-icon
|
||||
class="text-primary"
|
||||
icon="arrow-up"
|
||||
v-if="state.batteryPower > 0"
|
||||
></fa-icon>
|
||||
<fa-icon class="text-primary" icon="arrow-down" v-if="batteryPower < 0"></fa-icon>
|
||||
<fa-icon class="text-primary" icon="arrow-up" v-if="batteryPower > 0"></fa-icon>
|
||||
</div>
|
||||
<h2 class="value">
|
||||
{{ fmt(state.batteryPower) }}
|
||||
<small class="text-muted">{{ fmtUnit(state.batteryPower) }}W</small>
|
||||
{{ fmt(batteryPower) }}
|
||||
<small class="text-muted">{{ fmtUnit(batteryPower) }}W</small>
|
||||
</h2>
|
||||
</div>
|
||||
<div class="col-6 col-md-3 mt-3" v-if="state.batteryConfigured">
|
||||
<div class="col-6 col-md-3 mt-3" v-if="batteryConfigured">
|
||||
<div class="mb-2 value">
|
||||
SoC
|
||||
<fa-icon
|
||||
icon="battery-three-quarters"
|
||||
v-bind:class="{
|
||||
'text-primary': state.batteryPower > 0,
|
||||
'text-muted': state.batteryPower < 0,
|
||||
:class="{
|
||||
'text-primary': batteryPower > 0,
|
||||
'text-muted': batteryPower < 0,
|
||||
}"
|
||||
></fa-icon>
|
||||
</div>
|
||||
<h2 class="value">{{ state.batterySoC || 0 }} <small class="text-muted">%</small></h2>
|
||||
<h2 class="value">{{ batterySoC }} <small class="text-muted">%</small></h2>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import "../icons";
|
||||
import formatter from "../mixins/formatter";
|
||||
|
||||
export default {
|
||||
name: "SiteDetails",
|
||||
props: ["state"],
|
||||
props: {
|
||||
gridConfigured: Boolean,
|
||||
gridPower: Number,
|
||||
pvConfigured: Boolean,
|
||||
pvPower: Number,
|
||||
batteryConfigured: Boolean,
|
||||
batteryPower: Number,
|
||||
batterySoC: Number,
|
||||
},
|
||||
mixins: [formatter],
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
26
assets/js/components/Soc.stories.js
Normal file
26
assets/js/components/Soc.stories.js
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import Soc from "./Soc.vue";
|
||||
|
||||
export default {
|
||||
title: "Main/Soc",
|
||||
component: Soc,
|
||||
argTypes: {},
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: { Soc },
|
||||
template: '<Soc v-bind="$props"></Soc>',
|
||||
});
|
||||
|
||||
export const Base = Template.bind({});
|
||||
Base.args = {
|
||||
soc: 80,
|
||||
levels: [20, 50, 80, 100],
|
||||
};
|
||||
|
||||
export const Caption = Template.bind({});
|
||||
Caption.args = {
|
||||
caption: true,
|
||||
soc: 80,
|
||||
levels: [20, 50, 80, 100],
|
||||
};
|
||||
|
|
@ -7,11 +7,11 @@
|
|||
class="btn btn-outline-primary"
|
||||
v-for="(level, id) in levelsOrDefault"
|
||||
:key="id"
|
||||
v-bind:level="level"
|
||||
:level="level"
|
||||
:id="id"
|
||||
:class="{ active: soc == level, first: !caption && id == 0 }"
|
||||
>
|
||||
<input type="radio" v-bind:value="level" v-on:click="targetSoC(level)" />{{ level }}%
|
||||
<input type="radio" :value="level" v-on:click="setTargetSoC(level)" />{{ level }}%
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -19,7 +19,11 @@
|
|||
<script>
|
||||
export default {
|
||||
name: "Soc",
|
||||
props: ["soc", "caption", "levels"],
|
||||
props: {
|
||||
soc: Number,
|
||||
caption: Boolean,
|
||||
levels: Array,
|
||||
},
|
||||
computed: {
|
||||
levelsOrDefault: function () {
|
||||
if (this.levels == null || this.levels.length == 0) {
|
||||
|
|
@ -29,7 +33,7 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
targetSoC: function (mode) {
|
||||
setTargetSoC: function (mode) {
|
||||
this.$emit("updated", mode);
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="toast" data-delay="10000" v-bind:data-autohide="true">
|
||||
<div class="toast" data-delay="10000" :data-autohide="true">
|
||||
<div class="toast-header">
|
||||
<strong class="mr-auto" v-if="item.type != 'warn'"
|
||||
><fa-icon class="text-danger" icon="exclamation-triangle"></fa-icon> Error</strong
|
||||
|
|
@ -18,10 +18,11 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import "../icons";
|
||||
import $ from "jquery";
|
||||
|
||||
export default {
|
||||
name: "MessageToast",
|
||||
name: "ToastMessage",
|
||||
props: ["item"],
|
||||
mounted: function () {
|
||||
const id = "#message-id-" + this.item.id;
|
||||
22
assets/js/components/Toasts.stories.js
Normal file
22
assets/js/components/Toasts.stories.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import Toasts from "./Toasts.vue";
|
||||
|
||||
export default {
|
||||
title: "Main/Toasts",
|
||||
component: Toasts,
|
||||
argTypes: {},
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: { Toasts },
|
||||
template: '<Toasts v-bind="$props"></Toasts>',
|
||||
});
|
||||
|
||||
export const Base = Template.bind({});
|
||||
Base.args = {
|
||||
items: {
|
||||
id: 1,
|
||||
message: "Evil warning",
|
||||
type: "warn",
|
||||
},
|
||||
};
|
||||
|
|
@ -1,22 +1,25 @@
|
|||
<template>
|
||||
<div aria-atomic="true" style="position: absolute; top: 4rem; right: 0.5rem">
|
||||
<!-- Position it -->
|
||||
<MessageToast
|
||||
<ToastMessage
|
||||
v-for="item in items"
|
||||
v-bind:item="item"
|
||||
:item="item"
|
||||
:id="'message-id-' + item.id"
|
||||
:key="item.id"
|
||||
>
|
||||
</MessageToast>
|
||||
</ToastMessage>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MessageToast from "./MessageToast";
|
||||
import "../icons";
|
||||
import ToastMessage from "./ToastMessage";
|
||||
|
||||
export default {
|
||||
name: "Toasts",
|
||||
components: { MessageToast },
|
||||
props: ["items", "count"],
|
||||
components: { ToastMessage },
|
||||
props: {
|
||||
items: Object,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
37
assets/js/components/Vehicle.stories.js
Normal file
37
assets/js/components/Vehicle.stories.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import Vehicle from "./Vehicle.vue";
|
||||
|
||||
export default {
|
||||
title: "Main/Vehicle",
|
||||
component: Vehicle,
|
||||
argTypes: {},
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: { Vehicle },
|
||||
template: '<Vehicle v-bind="$props"></Vehicle>',
|
||||
});
|
||||
|
||||
export const Base = Template.bind({});
|
||||
Base.args = {
|
||||
soc: false,
|
||||
socTitle: "Mein Auto",
|
||||
connected: true,
|
||||
};
|
||||
|
||||
export const Car = Template.bind({});
|
||||
Car.args = {
|
||||
soc: true,
|
||||
socTitle: "Mein Auto",
|
||||
connected: true,
|
||||
socCharge: 15,
|
||||
};
|
||||
|
||||
export const CarWithMin = Template.bind({});
|
||||
CarWithMin.args = {
|
||||
soc: true,
|
||||
socTitle: "Mein Auto",
|
||||
connected: true,
|
||||
socCharge: 15,
|
||||
minSoC: 30,
|
||||
};
|
||||
|
|
@ -1,31 +1,31 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="mb-2">{{ state.socTitle || "Fahrzeug" }}</div>
|
||||
<div class="mb-2">{{ socTitle || "Fahrzeug" }}</div>
|
||||
<div class="progress" style="height: 24px; font-size: 100%; margin-top: 16px">
|
||||
<div
|
||||
class="progress-bar"
|
||||
role="progressbar"
|
||||
v-bind:class="{
|
||||
'progress-bar-striped': state.charging,
|
||||
'progress-bar-animated': state.charging,
|
||||
'bg-light': !state.connected,
|
||||
'text-secondary': !state.connected,
|
||||
'bg-warning': state.connected && minSoCActive,
|
||||
:class="{
|
||||
'progress-bar-striped': charging,
|
||||
'progress-bar-animated': charging,
|
||||
'bg-light': !connected,
|
||||
'text-secondary': !connected,
|
||||
'bg-warning': connected && minSoCActive,
|
||||
}"
|
||||
v-bind:style="{ width: socChargeDisplayWidth + '%' }"
|
||||
:style="{ width: socChargeDisplayWidth + '%' }"
|
||||
>
|
||||
{{ socChargeDisplayValue }}
|
||||
</div>
|
||||
<div
|
||||
class="progress-bar"
|
||||
role="progressbar"
|
||||
v-bind:class="{
|
||||
'progress-bar-striped': state.charging,
|
||||
'progress-bar-animated': state.charging,
|
||||
:class="{
|
||||
'progress-bar-striped': charging,
|
||||
'progress-bar-animated': charging,
|
||||
'bg-warning': true,
|
||||
'bg-muted': true,
|
||||
}"
|
||||
v-bind:style="{ width: minSoCRemainingDisplayWidth + '%' }"
|
||||
:style="{ width: minSoCRemainingDisplayWidth + '%' }"
|
||||
v-if="minSoCActive && socChargeDisplayWidth < 100"
|
||||
></div>
|
||||
</div>
|
||||
|
|
@ -35,38 +35,45 @@
|
|||
<script>
|
||||
export default {
|
||||
name: "Vehicle",
|
||||
props: ["state"],
|
||||
props: {
|
||||
socTitle: String,
|
||||
connected: Boolean,
|
||||
charging: Boolean,
|
||||
soc: Boolean,
|
||||
socCharge: Number,
|
||||
minSoC: Number,
|
||||
},
|
||||
computed: {
|
||||
socChargeDisplayWidth: function () {
|
||||
if (this.state.soc && this.state.socCharge >= 0) {
|
||||
return this.state.socCharge;
|
||||
if (this.soc && this.socCharge >= 0) {
|
||||
return this.socCharge;
|
||||
}
|
||||
return 100;
|
||||
},
|
||||
socChargeDisplayValue: function () {
|
||||
// no soc or no soc value
|
||||
if (!this.state.soc || this.state.socCharge < 0) {
|
||||
if (!this.soc || this.socCharge < 0) {
|
||||
let chargeStatus = "getrennt";
|
||||
if (this.state.charging) {
|
||||
if (this.charging) {
|
||||
chargeStatus = "laden";
|
||||
} else if (this.state.connected) {
|
||||
} else if (this.connected) {
|
||||
chargeStatus = "verbunden";
|
||||
}
|
||||
return chargeStatus;
|
||||
}
|
||||
|
||||
// percent value if enough space
|
||||
let socCharge = this.state.socCharge;
|
||||
let socCharge = this.socCharge;
|
||||
if (socCharge >= 10) {
|
||||
socCharge += "%";
|
||||
}
|
||||
return socCharge;
|
||||
},
|
||||
minSoCActive: function () {
|
||||
return this.state.minSoC > 0 && this.state.socCharge < this.state.minSoC;
|
||||
return this.minSoC > 0 && this.socCharge < this.minSoC;
|
||||
},
|
||||
minSoCRemainingDisplayWidth: function () {
|
||||
return this.state.minSoC - this.state.socCharge;
|
||||
return this.minSoC - this.socCharge;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
22
assets/js/components/Version.stories.js
Normal file
22
assets/js/components/Version.stories.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import Version from "./Version.vue";
|
||||
|
||||
export default {
|
||||
title: "Main/Version",
|
||||
component: Version,
|
||||
argTypes: {},
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: { Version },
|
||||
template: '<Version v-bind="$props"></Version>',
|
||||
});
|
||||
|
||||
export const Base = Template.bind({});
|
||||
Base.args = {
|
||||
installed: "0.36",
|
||||
available: "0.40",
|
||||
// soc: false,
|
||||
// socTitle: "Mein Auto",
|
||||
// connected: true,
|
||||
};
|
||||
|
|
@ -2,13 +2,12 @@
|
|||
<div class="collapse" ref="bar">
|
||||
<div class="row p-3 bg-warning">
|
||||
<div class="col-12">
|
||||
Neue Version verfügbar! Installiert: {{ installed }}. Verfügbar:
|
||||
{{ state.availableVersion }}.
|
||||
Neue Version verfügbar! Installiert: {{ installed }}. Verfügbar: {{ available }}.
|
||||
<b
|
||||
class="px-3"
|
||||
data-toggle="collapse"
|
||||
data-target="#release-notes"
|
||||
v-if="state.releaseNotes"
|
||||
v-if="releaseNotes"
|
||||
>
|
||||
<a href="#" class="text-body">
|
||||
Release notes
|
||||
|
|
@ -18,9 +17,7 @@
|
|||
</b>
|
||||
<b class="px-3">
|
||||
<a
|
||||
v-bind:href="
|
||||
'https://github.com/andig/evcc/releases/tag/' + state.availableVersion
|
||||
"
|
||||
:href="'https://github.com/andig/evcc/releases/tag/' + available"
|
||||
class="text-body"
|
||||
>
|
||||
Download <fa-icon icon="chevron-down"></fa-icon>
|
||||
|
|
@ -39,20 +36,24 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="row p-3 bg-light collapse" id="release-notes" ref="notes">
|
||||
<div class="col-12" v-html="state.releaseNotes"></div>
|
||||
<div class="col-12" v-html="releaseNotes"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import "../icons";
|
||||
import $ from "jquery";
|
||||
|
||||
export default {
|
||||
name: "Version",
|
||||
props: ["installed"],
|
||||
props: {
|
||||
installed: String,
|
||||
available: String,
|
||||
releaseNotes: String,
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
state: this.$root.$data.store.state,
|
||||
notesShown: false,
|
||||
};
|
||||
},
|
||||
|
|
@ -72,11 +73,11 @@ export default {
|
|||
);
|
||||
},
|
||||
watch: {
|
||||
"state.availableVersion": function () {
|
||||
available: function () {
|
||||
if (
|
||||
this.installed != window.evcc.version && // go template parsed?
|
||||
this.installed != "[[.Version]]" && // go template parsed?
|
||||
this.installed != "0.0.1-alpha" && // make used?
|
||||
this.state.availableVersion != this.installed
|
||||
this.available != this.installed
|
||||
) {
|
||||
$(this.$refs.bar).collapse("show");
|
||||
}
|
||||
|
|
|
|||
30
assets/js/icons.js
Normal file
30
assets/js/icons.js
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import Vue from "vue";
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
import { library } from "@fortawesome/fontawesome-svg-core";
|
||||
import { faSun } from "@fortawesome/free-solid-svg-icons/faSun";
|
||||
import { faArrowUp } from "@fortawesome/free-solid-svg-icons/faArrowUp";
|
||||
import { faArrowDown } from "@fortawesome/free-solid-svg-icons/faArrowDown";
|
||||
import { faBatteryThreeQuarters } from "@fortawesome/free-solid-svg-icons/faBatteryThreeQuarters";
|
||||
import { faTemperatureLow } from "@fortawesome/free-solid-svg-icons/faTemperatureLow";
|
||||
import { faTemperatureHigh } from "@fortawesome/free-solid-svg-icons/faTemperatureHigh";
|
||||
import { faThermometerHalf } from "@fortawesome/free-solid-svg-icons/faThermometerHalf";
|
||||
import { faLeaf } from "@fortawesome/free-solid-svg-icons/faLeaf";
|
||||
import { faChevronUp } from "@fortawesome/free-solid-svg-icons/faChevronUp";
|
||||
import { faChevronDown } from "@fortawesome/free-solid-svg-icons/faChevronDown";
|
||||
import { faExclamationTriangle } from "@fortawesome/free-solid-svg-icons/faExclamationTriangle";
|
||||
|
||||
library.add(
|
||||
faSun,
|
||||
faArrowUp,
|
||||
faArrowDown,
|
||||
faBatteryThreeQuarters,
|
||||
faTemperatureLow,
|
||||
faTemperatureHigh,
|
||||
faThermometerHalf,
|
||||
faLeaf,
|
||||
faChevronUp,
|
||||
faChevronDown,
|
||||
faExclamationTriangle
|
||||
);
|
||||
|
||||
Vue.component("fa-icon", FontAwesomeIcon);
|
||||
|
|
@ -31,13 +31,19 @@
|
|||
</div>
|
||||
</nav>
|
||||
|
||||
<Version :installed="installed" id="version-bar"></Version>
|
||||
<Version
|
||||
id="version-bar"
|
||||
:installed="installedVersion"
|
||||
:available="store.state.availableVersion"
|
||||
:releaseNotes="store.state.releaseNotes"
|
||||
></Version>
|
||||
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import "../icons";
|
||||
import Version from "../components/Version";
|
||||
import store from "../store";
|
||||
|
||||
|
|
@ -48,7 +54,7 @@ export default {
|
|||
return {
|
||||
compact: false,
|
||||
store: this.$root.$data.store,
|
||||
installed: window.evcc.version,
|
||||
installedVersion: window.evcc.version,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<!-- <h1 class="display-4 pt-3 mx-auto text-center">Konfiguration</h1>
|
||||
<p class="lead mx-auto text-center">Details der Fahrzeug-, Wallbox- und Zählerkonfiguration.</p> -->
|
||||
<p class="lead mx-auto text-center">Details der Fahrzeug-, Wallbox- und Zählerkonfiguration.</p> -->
|
||||
|
||||
<div class="row mt-4 border-bottom">
|
||||
<div class="col-12">
|
||||
<p class="h1">{{ state.title || "Home" }}</p>
|
||||
<p class="h1">{{ title || "Home" }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -13,24 +13,24 @@
|
|||
<div class="col-md-4"></div>
|
||||
<div class="col-6 col-md-2 py-3">
|
||||
Netzzähler:
|
||||
<span class="text-primary" v-if="state.gridConfigured">✓</span>
|
||||
<span class="text-primary" v-if="gridConfigured">✓</span>
|
||||
<span class="text-primary" v-else>—</span>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 py-3">
|
||||
PV Zähler:
|
||||
<span class="text-primary" v-if="state.pvConfigured">✓</span>
|
||||
<span class="text-primary" v-if="pvConfigured">✓</span>
|
||||
<span class="text-primary" v-else>—</span>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 py-3">
|
||||
Batteriezähler:
|
||||
<span class="text-primary" v-if="state.batteryConfigured">✓</span>
|
||||
<span class="text-primary" v-if="batteryConfigured">✓</span>
|
||||
<span class="text-primary" v-else>—</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="(loadpoint, id) in state.loadpoints"
|
||||
v-bind:loadpoint="loadpoint"
|
||||
v-for="(loadpoint, id) in loadpoints"
|
||||
:loadpoint="loadpoint"
|
||||
:key="id"
|
||||
:id="'loadpoint-' + id"
|
||||
>
|
||||
|
|
@ -87,9 +87,7 @@
|
|||
export default {
|
||||
name: "Config",
|
||||
data: function () {
|
||||
return {
|
||||
state: this.$root.$data.store.state, // global state
|
||||
};
|
||||
return this.$root.$data.store.state; // global state
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<Site v-bind:state="state" v-if="configured"></Site>
|
||||
<Site v-bind="state" v-if="configured"></Site>
|
||||
<div v-else>
|
||||
<div class="row py-5">
|
||||
<div class="col12">
|
||||
|
|
@ -86,9 +86,7 @@ export default {
|
|||
name: "Main",
|
||||
components: { Site },
|
||||
data: function () {
|
||||
return {
|
||||
state: this.$root.$data.store.state, // global state
|
||||
};
|
||||
return this.$root.$data.store; // global state
|
||||
},
|
||||
computed: {
|
||||
configured: function () {
|
||||
|
|
|
|||
10188
package-lock.json
generated
10188
package-lock.json
generated
File diff suppressed because it is too large
Load diff
24
package.json
24
package.json
|
|
@ -7,13 +7,21 @@
|
|||
"build": "rimraf dist; parcel build assets/* --public-url ./dist/",
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"lint": "prettier assets --write && eslint assets/**/*.js assets/**/*.vue --fix",
|
||||
"storybook": "start-storybook -p 6006 -s assets,node_modules/bootstrap/dist",
|
||||
"build-storybook": "build-storybook",
|
||||
"fix-audit": "npm-force-resolutions"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.9",
|
||||
"@storybook/addon-actions": "^6.1.10",
|
||||
"@storybook/addon-essentials": "^6.1.10",
|
||||
"@storybook/addon-links": "^6.1.10",
|
||||
"@storybook/vue": "^6.1.10",
|
||||
"@vue/component-compiler-utils": "^3.2.0",
|
||||
"@vue/eslint-config-standard": "^5.1.2",
|
||||
"babel-loader": "^8.2.2",
|
||||
"eslint": "^7.14.0",
|
||||
"eslint-config-prettier": "^6.15.0",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
|
|
@ -22,12 +30,11 @@
|
|||
"eslint-plugin-promise": "^4.2.1",
|
||||
"eslint-plugin-standard": "^4.1.0",
|
||||
"eslint-plugin-vue": "^7.1.0",
|
||||
"husky": "^4.3.0",
|
||||
"lint-staged": "^10.5.1",
|
||||
"npm-force-resolutions": "0.0.3",
|
||||
"parcel-bundler": "^1.12.4",
|
||||
"prettier": "^2.2.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"vue-loader": "^15.9.5",
|
||||
"vue-template-compiler": "^2.6.12"
|
||||
},
|
||||
"engines": {
|
||||
|
|
@ -52,18 +59,5 @@
|
|||
},
|
||||
"resolutions": {
|
||||
"node-forge": "0.10.0"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"assets/**/*.{js,vue,json}": [
|
||||
"eslint --fix"
|
||||
],
|
||||
"assets/**/*.{html,js,vue,json,css}": [
|
||||
"prettier --write"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
14603
server/assets.go
14603
server/assets.go
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue