chore: remove dead code and unused abstractions (#31437)
This commit is contained in:
parent
f0bc3e4ddf
commit
67f7f286ac
16 changed files with 35 additions and 470 deletions
|
|
@ -79,7 +79,6 @@ import formatter from "@/mixins/formatter";
|
|||
import collector from "@/mixins/collector";
|
||||
import api from "@/api";
|
||||
import deepEqual from "@/utils/deepEqual";
|
||||
import convertRates from "@/utils/convertRates";
|
||||
import { debounceLeading } from "@/utils/debounceLeading";
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import type { Vehicle, CURRENCY, Forecast } from "@/types/evcc";
|
||||
|
|
@ -153,8 +152,11 @@ export default defineComponent({
|
|||
return this.collectProps(Warnings);
|
||||
},
|
||||
chargingPlanPreviewProps(): any {
|
||||
const forecastSlots = this.forecast?.planner || [];
|
||||
const rates = convertRates(forecastSlots);
|
||||
const rates = (this.forecast?.planner || []).map(({ start, end, value }) => ({
|
||||
start: new Date(start),
|
||||
end: new Date(end),
|
||||
value,
|
||||
}));
|
||||
const { duration, plan, power, planTime } = this.plan;
|
||||
const targetTime = planTime ? new Date(planTime) : null;
|
||||
const { currency, smartCostType } = this;
|
||||
|
|
|
|||
|
|
@ -1,47 +0,0 @@
|
|||
<template>
|
||||
<div v-if="isSlot" class="text-end tabular">
|
||||
<span class="text-nowrap">{{ day }} {{ start }}</span
|
||||
>{{ " " }}<span class="text-nowrap">– {{ end }}</span>
|
||||
</div>
|
||||
<div v-if="isTimeseries" class="text-end tabular">
|
||||
<span class="text-nowrap">{{ time }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import formatter from "@/mixins/formatter";
|
||||
import { isForecastSlot, type ForecastSlot, type TimeseriesEntry } from "./types";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ForecastActiveSlot",
|
||||
mixins: [formatter],
|
||||
props: {
|
||||
activeSlot: { type: Object as PropType<ForecastSlot | TimeseriesEntry | null> },
|
||||
},
|
||||
computed: {
|
||||
isSlot() {
|
||||
return this.activeSlot !== null && isForecastSlot(this.activeSlot);
|
||||
},
|
||||
isTimeseries() {
|
||||
return this.activeSlot !== null && !isForecastSlot(this.activeSlot);
|
||||
},
|
||||
day() {
|
||||
const startDate = new Date((this.activeSlot! as ForecastSlot).start);
|
||||
return this.weekdayShort(startDate);
|
||||
},
|
||||
start() {
|
||||
const startDate = new Date((this.activeSlot! as ForecastSlot).start);
|
||||
return this.fmtHourMinute(startDate);
|
||||
},
|
||||
end() {
|
||||
const endDate = new Date((this.activeSlot! as ForecastSlot).end);
|
||||
return this.fmtHourMinute(endDate);
|
||||
},
|
||||
time() {
|
||||
const time = new Date((this.activeSlot! as TimeseriesEntry).ts);
|
||||
return `${this.weekdayShort(time)} ${this.fmtHourMinute(time)}`;
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
@ -1,226 +0,0 @@
|
|||
<template>
|
||||
<div v-if="isSolar && solar" class="row">
|
||||
<div class="col-6 col-sm-4 mb-3 d-flex flex-column">
|
||||
<div class="label">{{ label("today") }}</div>
|
||||
<div class="value d-flex flex-column flex-lg-row gap-lg-2 align-items-lg-baseline">
|
||||
<div class="text-primary text-nowrap">
|
||||
<AnimatedNumber :to="solar.today?.energy" :format="fmtEnergy" />
|
||||
</div>
|
||||
<div class="extraValue text-nowrap">{{ label("remaining") }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-sm-4 mb-3 d-flex flex-column align-items-end align-items-sm-center">
|
||||
<div class="label">{{ label("tomorrow") }}</div>
|
||||
<div
|
||||
class="value d-flex flex-column flex-lg-row gap-lg-2 align-items-end align-items-sm-center align-items-lg-baseline"
|
||||
>
|
||||
<div class="text-primary text-nowrap">
|
||||
<AnimatedNumber :to="solar.tomorrow?.energy" :format="fmtEnergy" />
|
||||
</div>
|
||||
<div v-if="!solar.tomorrow?.complete" class="extraValue text-nowrap">
|
||||
{{ label("partly") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-sm-4 mb-3 d-flex flex-column align-items-start align-items-sm-end">
|
||||
<div class="label">{{ label("dayAfterTomorrow") }}</div>
|
||||
<div
|
||||
class="value d-flex flex-column flex-lg-row gap-lg-2 align-items-start align-items-sm-end align-items-lg-baseline"
|
||||
>
|
||||
<div class="text-primary text-nowrap">
|
||||
<AnimatedNumber :to="solar.dayAfterTomorrow?.energy" :format="fmtEnergy" />
|
||||
</div>
|
||||
<div v-if="!solar.dayAfterTomorrow?.complete" class="extraValue text-nowrap">
|
||||
{{ label("partly") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="isConstantValue" class="row">
|
||||
<div class="col-6 col-sm-4 mb-3 d-flex flex-column">
|
||||
<div class="label">{{ label("constant") }}</div>
|
||||
<div class="value text-price text-nowrap" :class="highlightColor">
|
||||
{{ constantValue }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="row">
|
||||
<div class="col-12 col-sm-6 col-lg-4 mb-3 d-flex flex-column">
|
||||
<div class="label">{{ label("range") }}</div>
|
||||
<div class="value text-price text-nowrap" :class="highlightColor">
|
||||
{{ priceRange }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="col-12 col-sm-6 col-lg-4 mb-3 d-flex flex-column align-items-sm-end align-items-lg-center"
|
||||
>
|
||||
<div class="label">{{ label("average") }}</div>
|
||||
<div class="value text-price text-nowrap" :class="highlightColor">
|
||||
{{ averagePrice }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="col-12 col-sm-6 col-lg-4 mb-3 d-flex flex-column align-items-sm-start align-items-lg-end"
|
||||
>
|
||||
<div class="label">{{ label("lowestHour") }}</div>
|
||||
<div class="value text-price text-nowrap" :class="highlightColor">
|
||||
{{ lowestPriceHour }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import formatter, { POWER_UNIT } from "@/mixins/formatter";
|
||||
import minuteTicker from "@/mixins/minuteTicker";
|
||||
import AnimatedNumber from "../Helper/AnimatedNumber.vue";
|
||||
import type { CURRENCY } from "@/types/evcc";
|
||||
import { ForecastType, findLowestSumSlotIndex } from "@/utils/forecast";
|
||||
import type { ForecastSlot, SolarDetails } from "./types";
|
||||
const LOCALES_WITHOUT_DAY_AFTER_TOMORROW = ["en", "tr"];
|
||||
|
||||
const FORECASTED_HOURS = 96;
|
||||
const SLOTS_PER_HOUR = 4;
|
||||
|
||||
export interface Energy {
|
||||
energy: string;
|
||||
incomplete: boolean;
|
||||
}
|
||||
|
||||
export interface EnergyByDay {
|
||||
today: Energy;
|
||||
tomorrow: Energy;
|
||||
dayAfterTomorrow: Energy;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: "ForecastDetails",
|
||||
components: {
|
||||
AnimatedNumber,
|
||||
},
|
||||
mixins: [formatter, minuteTicker],
|
||||
props: {
|
||||
type: { type: String as () => ForecastType, required: true },
|
||||
grid: { type: Array as PropType<ForecastSlot[]> },
|
||||
co2: { type: Array as PropType<ForecastSlot[]> },
|
||||
solar: { type: Object as PropType<SolarDetails> },
|
||||
currency: { type: String as PropType<CURRENCY> },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
now: new Date(),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isSolar() {
|
||||
return this.type === ForecastType.Solar;
|
||||
},
|
||||
isPrice() {
|
||||
return this.type === ForecastType.Price;
|
||||
},
|
||||
upcomingSlots(): ForecastSlot[] {
|
||||
const now = this.now;
|
||||
const slots = this.isPrice ? this.grid || [] : this.co2 || [];
|
||||
return slots
|
||||
.filter((slot) => new Date(slot.end) > now)
|
||||
.slice(0, FORECASTED_HOURS * SLOTS_PER_HOUR);
|
||||
},
|
||||
isConstantValue(): boolean {
|
||||
if (this.isSolar) return false;
|
||||
const slots = this.upcomingSlots;
|
||||
if (slots.length === 0) return false;
|
||||
|
||||
const firstValue = slots[0]?.value;
|
||||
return slots.every((slot) => slot.value === firstValue);
|
||||
},
|
||||
constantValue(): string {
|
||||
return this.fmtValue(this.upcomingSlots[0]!.value, true);
|
||||
},
|
||||
averagePrice() {
|
||||
if (this.isSolar) return "";
|
||||
const slots = this.upcomingSlots;
|
||||
const price = slots.reduce((acc, slot) => acc + slot.value, 0) / slots.length;
|
||||
return this.fmtValue(price, true);
|
||||
},
|
||||
priceRange() {
|
||||
if (this.isSolar) return "";
|
||||
const slots = this.upcomingSlots;
|
||||
const min = Math.min(...slots.map((slot) => slot.value));
|
||||
const max = Math.max(...slots.map((slot) => slot.value));
|
||||
return `${this.fmtValue(min, false)} – ${this.fmtValue(max, true)}`;
|
||||
},
|
||||
lowestPriceHour() {
|
||||
if (this.isSolar) return "";
|
||||
const slots = this.upcomingSlots;
|
||||
const index = findLowestSumSlotIndex(slots, SLOTS_PER_HOUR);
|
||||
if (index === -1) return "";
|
||||
const startSlot = slots[index];
|
||||
const endSlot = slots[index + SLOTS_PER_HOUR - 1];
|
||||
if (!startSlot || !endSlot) return "";
|
||||
const start = new Date(startSlot.start);
|
||||
const end = new Date(endSlot.end);
|
||||
|
||||
return `${this.weekdayShort(start)} ${this.fmtHourMinute(start)} – ${this.fmtHourMinute(end)}`;
|
||||
},
|
||||
highlightColor() {
|
||||
switch (this.type) {
|
||||
case ForecastType.Price:
|
||||
return "text-price";
|
||||
case ForecastType.Co2:
|
||||
return "text-co2";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
everyMinute(): void {
|
||||
this.now = new Date();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.now = new Date();
|
||||
},
|
||||
methods: {
|
||||
label(key: string) {
|
||||
// special case "day after tomorrow"
|
||||
if (
|
||||
key === "dayAfterTomorrow" &&
|
||||
LOCALES_WITHOUT_DAY_AFTER_TOMORROW.includes(this.$i18n.locale)
|
||||
) {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() + 2);
|
||||
return this.fmtDayMonth(date);
|
||||
}
|
||||
|
||||
return this.$t(`forecast.${this.type}.${key}`);
|
||||
},
|
||||
fmtValue(value: number, withUnit = true) {
|
||||
if (this.type === ForecastType.Price) {
|
||||
return this.fmtPricePerKWh(value, this.currency, false, withUnit);
|
||||
}
|
||||
return withUnit ? this.fmtCo2Medium(value) : this.fmtNumber(value, 0);
|
||||
},
|
||||
fmtEnergy(energy: number | undefined) {
|
||||
return !energy ? "-" : this.fmtWh(energy, POWER_UNIT.AUTO);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.value {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.extraValue {
|
||||
color: var(--evcc-gray);
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
}
|
||||
.label {
|
||||
color: var(--evcc-gray);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
<template>
|
||||
<IconSelectGroup>
|
||||
<IconSelectItem
|
||||
v-for="type in types"
|
||||
:key="type"
|
||||
:active="selectedType === type"
|
||||
:label="$t(`forecast.type.${type}`)"
|
||||
:disabled="!availableTypes[type]"
|
||||
hideLabelOnMobile
|
||||
@click="$emit('update:modelValue', type)"
|
||||
>
|
||||
<component :is="typeIcons[type]"></component>
|
||||
</IconSelectItem>
|
||||
</IconSelectGroup>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import "@h2d2/shopicons/es/regular/eco1";
|
||||
import "@h2d2/shopicons/es/regular/sun";
|
||||
import { defineComponent } from "vue";
|
||||
import IconSelectItem from "../Helper/IconSelectItem.vue";
|
||||
import IconSelectGroup from "../Helper/IconSelectGroup.vue";
|
||||
import DynamicPriceIcon from "../MaterialIcon/DynamicPrice.vue";
|
||||
import { ForecastType } from "@/utils/forecast";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ForecastTypeSelect",
|
||||
components: { IconSelectItem, IconSelectGroup },
|
||||
props: {
|
||||
modelValue: { type: String as () => ForecastType, required: true },
|
||||
forecast: { type: Object, required: true },
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
computed: {
|
||||
selectedType() {
|
||||
return this.modelValue;
|
||||
},
|
||||
types() {
|
||||
return Object.values(ForecastType);
|
||||
},
|
||||
availableTypes() {
|
||||
return {
|
||||
[ForecastType.Solar]: !!this.forecast["solar"],
|
||||
[ForecastType.Price]: !!this.forecast["grid"],
|
||||
[ForecastType.Co2]: !!this.forecast["co2"],
|
||||
};
|
||||
},
|
||||
typeIcons() {
|
||||
return {
|
||||
[ForecastType.Solar]: "shopicon-regular-sun",
|
||||
[ForecastType.Price]: DynamicPriceIcon,
|
||||
[ForecastType.Co2]: "shopicon-regular-eco1",
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import { copyWithFeedback } from "@/utils/clipboard";
|
||||
import "@h2d2/shopicons/es/regular/copy";
|
||||
import "@h2d2/shopicons/es/regular/checkmark";
|
||||
|
||||
|
|
@ -52,54 +53,7 @@ export default defineComponent({
|
|||
},
|
||||
methods: {
|
||||
handleCopy() {
|
||||
// Modern browser API, requires secure context (localhost or https)
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
navigator.clipboard
|
||||
.writeText(this.content)
|
||||
.then(() => {
|
||||
this.showCopiedFeedback();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Failed to copy to clipboard:", err);
|
||||
this.fallbackCopy();
|
||||
});
|
||||
} else {
|
||||
// Fallback method for HTTP/non-secure contexts
|
||||
this.fallbackCopy();
|
||||
}
|
||||
},
|
||||
fallbackCopy() {
|
||||
try {
|
||||
// Create a temporary textarea element
|
||||
const textarea = document.createElement("textarea");
|
||||
textarea.value = this.content;
|
||||
textarea.style.position = "fixed";
|
||||
textarea.style.left = "-999999px";
|
||||
textarea.style.top = "-999999px";
|
||||
document.body.appendChild(textarea);
|
||||
|
||||
// Select and copy the text
|
||||
textarea.focus();
|
||||
textarea.select();
|
||||
const successful = document.execCommand("copy");
|
||||
document.body.removeChild(textarea);
|
||||
|
||||
if (successful) {
|
||||
this.showCopiedFeedback();
|
||||
} else {
|
||||
alert("Copy failed. Please copy manually.");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Fallback copy failed:", err);
|
||||
// Show user feedback that copy failed
|
||||
alert("Copy failed. Please copy manually.");
|
||||
}
|
||||
},
|
||||
showCopiedFeedback() {
|
||||
this.copied = true;
|
||||
setTimeout(() => {
|
||||
this.copied = false;
|
||||
}, 2000);
|
||||
copyWithFeedback(this.content, (value) => (this.copied = value));
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,16 +2,36 @@ export function isClipboardSupported(): boolean {
|
|||
return !!(navigator.clipboard && window.isSecureContext);
|
||||
}
|
||||
|
||||
// textarea/execCommand fallback for HTTP/non-secure contexts
|
||||
function fallbackCopy(text: string): boolean {
|
||||
try {
|
||||
const textarea = document.createElement("textarea");
|
||||
textarea.value = text;
|
||||
textarea.style.position = "fixed";
|
||||
textarea.style.left = "-999999px";
|
||||
textarea.style.top = "-999999px";
|
||||
document.body.appendChild(textarea);
|
||||
textarea.focus();
|
||||
textarea.select();
|
||||
const successful = document.execCommand("copy");
|
||||
document.body.removeChild(textarea);
|
||||
return successful;
|
||||
} catch (err) {
|
||||
console.error("Fallback copy failed:", err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function copyToClipboard(text: string): Promise<boolean> {
|
||||
if (!isClipboardSupported()) {
|
||||
return false;
|
||||
return fallbackCopy(text);
|
||||
}
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error("Failed to copy to clipboard:", err);
|
||||
return false;
|
||||
return fallbackCopy(text);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
import type { Rate } from "../types/evcc";
|
||||
import type { ForecastSlot } from "../components/Forecast/types";
|
||||
|
||||
function convertRate(slot: ForecastSlot): Rate {
|
||||
return {
|
||||
start: new Date(slot.start),
|
||||
end: new Date(slot.end),
|
||||
value: slot.value,
|
||||
};
|
||||
}
|
||||
|
||||
export default function convertRates(slots: ForecastSlot[] | null): Rate[] {
|
||||
if (!slots) return [];
|
||||
return slots.map(convertRate);
|
||||
}
|
||||
|
|
@ -13,9 +13,6 @@ import (
|
|||
|
||||
var ErrLoadpointNotInitialized = errors.New("loadpoint not initialized")
|
||||
|
||||
// TODO remove when used
|
||||
var _ = ensureCharger
|
||||
|
||||
// ensureCharger extracts ID from list of IDs returned from `list` function
|
||||
func ensureCharger(id string, list func() ([]string, error)) (string, error) {
|
||||
id, err := ensureChargerEx(id, list, func(v string) (string, error) {
|
||||
|
|
|
|||
|
|
@ -15,20 +15,6 @@ type API interface {
|
|||
// Name returns the vehicle name
|
||||
Name() string
|
||||
|
||||
// // GetMode returns the charge mode
|
||||
// GetMode() api.ChargeMode
|
||||
// // SetMode sets the charge mode
|
||||
// SetMode(api.ChargeMode)
|
||||
// // GetPhases returns the phases
|
||||
// GetPhases() int
|
||||
// // SetPhases sets the phases
|
||||
// SetPhases(phases int) error
|
||||
|
||||
// // GetPriority returns the priority
|
||||
// GetPriority() int
|
||||
// // SetPriority sets the priority
|
||||
// SetPriority(priority int)
|
||||
|
||||
// GetMinSoc returns the min soc
|
||||
GetMinSoc() int
|
||||
// SetMinSoc sets the min soc
|
||||
|
|
@ -52,13 +38,4 @@ type API interface {
|
|||
GetPlanStrategy() api.PlanStrategy
|
||||
// SetPlanStrategy sets the plan strategy
|
||||
SetPlanStrategy(api.PlanStrategy) error
|
||||
|
||||
// // GetMinCurrent returns the min charging current
|
||||
// GetMinCurrent() float64
|
||||
// // SetMinCurrent sets the min charging current
|
||||
// SetMinCurrent(float64)
|
||||
// // GetMaxCurrent returns the max charging current
|
||||
// GetMaxCurrent() float64
|
||||
// // SetMaxCurrent sets the max charging current
|
||||
// SetMaxCurrent(float64)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ func (h *msgHandler) receive(payload string) {
|
|||
h.val.Set(payload)
|
||||
}
|
||||
|
||||
// hasValue returned the received and processed payload as string
|
||||
func (h *msgHandler) hasValue() (string, error) {
|
||||
// value returns the received and processed payload as string
|
||||
func (h *msgHandler) value() (string, error) {
|
||||
payload, err := h.val.GetContext(h.ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
|
@ -36,12 +36,3 @@ func (h *msgHandler) hasValue() (string, error) {
|
|||
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
func (h *msgHandler) value() (string, error) {
|
||||
v, err := h.hasValue()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,20 +39,6 @@ func (h *TimeoutHandler) FloatGetter(p FloatGetter) (func() (float64, error), er
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (h *TimeoutHandler) StringGetter(p StringGetter) (func() (string, error), error) {
|
||||
g, err := p.StringGetter()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return func() (val string, err error) {
|
||||
if val, err = g(); err == nil {
|
||||
_, err = h.ticker()
|
||||
}
|
||||
return val, err
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *TimeoutHandler) JsonGetter(p StringGetter) (func(any) error, error) {
|
||||
g, err := p.StringGetter()
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -30,13 +30,6 @@ func New(driver, dsn string) (*gorm.DB, error) {
|
|||
|
||||
switch driver {
|
||||
case "sqlite":
|
||||
|
||||
// Example DSNs:
|
||||
//"path/to/database.db"
|
||||
// "~/database.db",
|
||||
// "database.db?cache=shared&journal_mode=WAL"
|
||||
// ":memory:"
|
||||
|
||||
// Split database path and connection parameters
|
||||
dbPath, params, _ := strings.Cut(dsn, "?")
|
||||
|
||||
|
|
|
|||
|
|
@ -179,12 +179,6 @@ func (s *HTTPd) RegisterSiteHandlers(site site.API) {
|
|||
"plan2": {"DELETE", "/vehicles/{name:[a-zA-Z0-9_.:-]+}/plan/soc", planSocRemoveHandler(site)},
|
||||
"repeatingPlans": {"POST", "/vehicles/{name:[a-zA-Z0-9_.:-]+}/plan/repeating", addRepeatingPlansHandler(site)},
|
||||
"planStrategy": {"POST", "/vehicles/{name:[a-zA-Z0-9_.:-]+}/plan/strategy", updatePlanStrategyHandler(site)},
|
||||
|
||||
// config ui
|
||||
// "mode": {"POST", "/mode/{value:[a-z]+}", chargeModeHandler(v)},
|
||||
// "mincurrent": {"POST", "/mincurrent/{value:[0-9.]+}", floatHandler(pass(v.SetMinCurrent), v.GetMinCurrent)},
|
||||
// "maxcurrent": {"POST", "/maxcurrent/{value:[0-9.]+}", floatHandler(pass(v.SetMaxCurrent), v.GetMaxCurrent)},
|
||||
// "phases": {"POST", "/phases/{value:[0-9]+}", intHandler(pass(v.SetMinSoc), v.GetMinSoc)},
|
||||
}
|
||||
|
||||
for _, r := range vehicles {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ func WithLogger(ctx context.Context, log *Logger) context.Context {
|
|||
return context.WithValue(ctx, CtxLogger, log)
|
||||
}
|
||||
|
||||
func ContextLogger(ctx context.Context) *Logger {
|
||||
func contextLogger(ctx context.Context) *Logger {
|
||||
if ctx != nil {
|
||||
if l, ok := ctx.Value(CtxLogger).(*Logger); ok {
|
||||
return l
|
||||
|
|
@ -21,7 +21,7 @@ func ContextLogger(ctx context.Context) *Logger {
|
|||
}
|
||||
|
||||
func ContextLoggerWithDefault(ctx context.Context, log *Logger) *Logger {
|
||||
if log := ContextLogger(ctx); log != nil {
|
||||
if log := contextLogger(ctx); log != nil {
|
||||
return log
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,11 +6,6 @@ import (
|
|||
"github.com/evcc-io/evcc/api"
|
||||
)
|
||||
|
||||
// TeeAttacher allows attaching a listener to a tee
|
||||
type TeeAttacher interface {
|
||||
Attach() <-chan Param
|
||||
}
|
||||
|
||||
// Tee distributes parameters to subscribers
|
||||
type Tee struct {
|
||||
mu sync.Mutex
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ package urlvalues
|
|||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
|
@ -11,7 +12,7 @@ import (
|
|||
func Copy(q url.Values) url.Values {
|
||||
res := make(url.Values, len(q))
|
||||
for k, v := range q {
|
||||
res[k] = append([]string{}, v...)
|
||||
res[k] = slices.Clone(v)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue