chore: use vite+ (#32297)
Co-authored-by: Michael Geers <michael@geers.tv>
This commit is contained in:
parent
828f4ab2f2
commit
d07a0741ec
55 changed files with 2176 additions and 2932 deletions
4
.github/actions/build-toolchain/action.yml
vendored
4
.github/actions/build-toolchain/action.yml
vendored
|
|
@ -18,10 +18,10 @@ runs:
|
|||
go-version-file: go.mod
|
||||
cache: false # managed explicitly below to keep the scoped namespaces
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: voidzero-dev/setup-vp@v1
|
||||
with:
|
||||
node-version: "26"
|
||||
cache: "npm"
|
||||
cache: true
|
||||
|
||||
# Writes are namespaced by cache-scope so a PR build cannot poison the
|
||||
# shared "main" cache; PRs still read it through the fallback restore-key.
|
||||
|
|
|
|||
7
.github/workflows/cli-docs.yml
vendored
7
.github/workflows/cli-docs.yml
vendored
|
|
@ -26,11 +26,16 @@ jobs:
|
|||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- uses: voidzero-dev/setup-vp@v1
|
||||
with:
|
||||
node-version: "26"
|
||||
cache: true
|
||||
|
||||
- name: Generate CLI docs
|
||||
run: mkdir dist && touch dist/empty && go run main.go gendoc ./cli-docs
|
||||
|
||||
- name: Format
|
||||
run: npx prettier@3.9.4 --no-config --write "./cli-docs/**/*.md"
|
||||
run: vp fmt "./cli-docs/**/*.md"
|
||||
|
||||
- name: Deploy CLI docs to docs repo
|
||||
uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4
|
||||
|
|
|
|||
16
.github/workflows/default.yml
vendored
16
.github/workflows/default.yml
vendored
|
|
@ -76,10 +76,10 @@ jobs:
|
|||
${{ runner.os }}-go-build-
|
||||
${{ runner.os }}-go-
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: voidzero-dev/setup-vp@v1
|
||||
with:
|
||||
node-version: "26"
|
||||
cache: "npm"
|
||||
cache: true
|
||||
|
||||
- run: mkdir dist && touch dist/empty
|
||||
|
||||
|
|
@ -168,10 +168,10 @@ jobs:
|
|||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: voidzero-dev/setup-vp@v1
|
||||
with:
|
||||
node-version: "26"
|
||||
cache: "npm"
|
||||
cache: true
|
||||
|
||||
- name: Install
|
||||
run: make install-ui
|
||||
|
|
@ -230,10 +230,10 @@ jobs:
|
|||
${{ runner.os }}-go-integration-
|
||||
${{ runner.os }}-go-
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: voidzero-dev/setup-vp@v1
|
||||
with:
|
||||
node-version: "26"
|
||||
cache: "npm"
|
||||
cache: true
|
||||
|
||||
- name: Build UI
|
||||
run: make install-ui ui
|
||||
|
|
@ -254,11 +254,11 @@ jobs:
|
|||
|
||||
- name: Install Playwright (browsers + deps)
|
||||
if: steps.playwright-cache.outputs.cache-hit != 'true'
|
||||
run: npx playwright install --with-deps chromium
|
||||
run: vpx playwright install --with-deps chromium
|
||||
timeout-minutes: 5
|
||||
|
||||
- name: Run tests
|
||||
run: npx playwright test
|
||||
run: vpx playwright test
|
||||
timeout-minutes: 20
|
||||
env:
|
||||
TZ: Europe/Berlin
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
tests/custom-css.css
|
||||
1
.vscode/extensions.json
vendored
1
.vscode/extensions.json
vendored
|
|
@ -5,5 +5,6 @@
|
|||
"golang.go",
|
||||
"esbenp.prettier-vscode",
|
||||
"yoavbls.pretty-ts-errors",
|
||||
"VoidZero.vite-plus-extension-pack"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
20
AGENTS.md
20
AGENTS.md
|
|
@ -14,13 +14,13 @@ This file provides guidance to AI coding agents when working with code in this r
|
|||
- `make build` - build Go binary only
|
||||
- `make ui` - build UI assets only
|
||||
- `make install` - install Go tools and dependencies
|
||||
- `make install-ui` - install Node.js dependencies (`npm ci`)
|
||||
- `make install-ui` - install Node.js dependencies (`vp install`)
|
||||
- `make test` - run Go tests
|
||||
- `make test-ui` - run frontend tests
|
||||
- `make lint` - run Go linting (golangci-lint)
|
||||
- `make lint-ui` - run frontend linting
|
||||
- `npm run dev` - start Vue dev server (http://127.0.0.1:7071)
|
||||
- `npm run playwright` - run integration tests
|
||||
- `vp run dev` - start Vue dev server (http://127.0.0.1:7071)
|
||||
- `vp run playwright` - run integration tests
|
||||
- `evcc --template-type [type] --template [file]` - test device templates
|
||||
- `make docs` - generate template documentation
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ This file provides guidance to AI coding agents when working with code in this r
|
|||
Deep documentation on specific subsystems is available in `docs/agents/`. Load what you need based on the task:
|
||||
|
||||
| File | When to load |
|
||||
|------|-------------|
|
||||
| ------------------------------------------------------------- | ---------------------------------------------------------------------------- |
|
||||
| [Core Domain](docs/agents/core-domain.md) | Control loop, loadpoint logic, PV surplus, charge modes, tariffs, interfaces |
|
||||
| [Hardware Integrations](docs/agents/hardware-integrations.md) | Charger/meter/vehicle implementations, adding new devices |
|
||||
| [Easee Architecture](docs/agents/easee-architecture.md) | Easee charger (REST+SignalR, async correlation, concurrency) |
|
||||
|
|
@ -98,7 +98,7 @@ Deep documentation on specific subsystems is available in `docs/agents/`. Load w
|
|||
|
||||
## Comment Style
|
||||
|
||||
- Prefer self-documenting code over comments; comment the *why*, not the *what*
|
||||
- Prefer self-documenting code over comments; comment the _why_, not the _what_
|
||||
- Default to no comment. Only add one for a non-obvious constraint, invariant, workaround, or surprising behavior. Keep it to one line, two if necessary
|
||||
- Skip refs to the current task, PR, issue, or caller ("added for X flow", "see #1234"). Git history covers that
|
||||
- Exception: Go exported identifiers follow godoc convention. Short `// FuncName does X` summary starting with the identifier name
|
||||
|
|
@ -122,7 +122,7 @@ Deep documentation on specific subsystems is available in `docs/agents/`. Load w
|
|||
- `_enumer.go` - generated enum code
|
||||
- `*_decorators.go` - generated decorator pattern implementations
|
||||
- Validate interface implementations: `var _ Interface = (*Type)(nil)`
|
||||
- Capabilities: register via `implement.Has`/`May` only when a capability is *conditional* (runtime/config detection, e.g. `if cp.PhaseSwitching { implement.Has(...) }`). For capabilities present on every code path, declare a plain exported method plus `var _ api.Interface = (*Type)(nil)` instead. `api.Cap` resolves static methods via direct type assertion, so unconditional `implement.Has` is redundant. A type with no conditional capabilities needs neither the `implement.Caps` embed nor `implement.New()`
|
||||
- Capabilities: register via `implement.Has`/`May` only when a capability is _conditional_ (runtime/config detection, e.g. `if cp.PhaseSwitching { implement.Has(...) }`). For capabilities present on every code path, declare a plain exported method plus `var _ api.Interface = (*Type)(nil)` instead. `api.Cap` resolves static methods via direct type assertion, so unconditional `implement.Has` is redundant. A type with no conditional capabilities needs neither the `implement.Caps` embed nor `implement.New()`
|
||||
|
||||
### Error Handling
|
||||
|
||||
|
|
@ -236,9 +236,9 @@ Deep documentation on specific subsystems is available in `docs/agents/`. Load w
|
|||
### Essential Commands
|
||||
|
||||
- Must build before testing executing playwright `make ui build` since it uses the binary. For manual testing assets are build and reloaded automatically (vite dev).
|
||||
- Run tests: `npm run playwright` or `npx playwright test`
|
||||
- Debug: `npx playwright test --debug`
|
||||
- Specific test: `npx playwright test tests/config-loadpoint.spec.ts`
|
||||
- Run tests: `vp run playwright` or `vpx playwright test`
|
||||
- Debug: `vpx playwright test --debug`
|
||||
- Specific test: `vpx playwright test tests/config-loadpoint.spec.ts`
|
||||
|
||||
### Selector Strategy
|
||||
|
||||
|
|
@ -270,7 +270,7 @@ Deep documentation on specific subsystems is available in `docs/agents/`. Load w
|
|||
- Device types: chargers, meters, vehicles, tariffs
|
||||
- Plugin protocols: Modbus, HTTP, MQTT, JavaScript, Go
|
||||
- Define device capabilities and configuration in templates at `templates/definition/[type]/`
|
||||
- Don't restate param properties that `util/templates/defaults.yaml` already defines for that param name. Properties (description, help, type, unit, default, example, required, advanced, mask, private, usages, …) are inherited from defaults; only specify a property in a template to give it a *different* value. Restating the same value is redundant duplication: reference the param by `name` alone.
|
||||
- Don't restate param properties that `util/templates/defaults.yaml` already defines for that param name. Properties (description, help, type, unit, default, example, required, advanced, mask, private, usages, …) are inherited from defaults; only specify a property in a template to give it a _different_ value. Restating the same value is redundant duplication: reference the param by `name` alone.
|
||||
- Test templates: `evcc --template-type [type] --template [file]`
|
||||
- Update docs after template changes: `make docs`
|
||||
- When implementing or debugging against a third-party device library (eebus-go/ship/spine-go, ocpp-go, modbus/SunSpec), consult the library's current upstream documentation before coding rather than relying on recalled API details
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
### Development environment
|
||||
|
||||
Developing evcc requires [Go][1] and [Node][2]. We recommend VSCode with the [Go](https://marketplace.visualstudio.com/items?itemName=golang.Go), [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) and [Vetur](https://marketplace.visualstudio.com/items?itemName=octref.vetur) extensions.
|
||||
Developing evcc requires [Go][1] [Node][2] and [Vite+][3]. We recommend VSCode with the [Go](https://marketplace.visualstudio.com/items?itemName=golang.Go), [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) and [Vetur](https://marketplace.visualstudio.com/items?itemName=octref.vetur) extensions.
|
||||
|
||||
Alternatively, if you use VS Code and [devcontainers](https://code.visualstudio.com/docs/devcontainers/containers), you can use the "Dev containers: Clone repository in container volume" action. This will create a devcontainer with the required toolchain and install the prerequisites as explained below. Wait until the startup log says "Done. Press any key to close the terminal." and check for any errors.
|
||||
|
||||
|
|
@ -98,8 +98,8 @@ You can adjust the referred configuration as needed to e.g. use your live config
|
|||
For frontend development start the Vue toolchain in dev-mode. Open http://127.0.0.1:7071/ to get to the live reloading development server. It pulls its data from port 7070 (see above).
|
||||
|
||||
```sh
|
||||
npm install
|
||||
npm run dev
|
||||
vp install
|
||||
vp run dev
|
||||
```
|
||||
|
||||
### Storybook
|
||||
|
|
@ -107,7 +107,7 @@ npm run dev
|
|||
We're using storybook to develop and visualize UI components in different states. Running the command below will open your browser at http://127.0.0.1:6006/.
|
||||
|
||||
```sh
|
||||
npm run storybook
|
||||
vp run storybook
|
||||
```
|
||||
|
||||
### Integration testing
|
||||
|
|
@ -116,7 +116,7 @@ We use Playwright for end-to-end integration tests. They start a local evcc inst
|
|||
|
||||
```sh
|
||||
make ui build
|
||||
npm run playwright
|
||||
vp run playwright
|
||||
```
|
||||
|
||||
### Simulating device state
|
||||
|
|
@ -124,7 +124,7 @@ npm run playwright
|
|||
Since we don't want to run tests against real devices or cloud services, we've build a simple simulator that lets you emulated meters, vehicles and loadpoints. The simulators web interface runs on http://localhost:7072.
|
||||
|
||||
```
|
||||
npm run simulator
|
||||
vp run simulator
|
||||
```
|
||||
|
||||
Run an evcc instance that uses simulator data. This configuration runs with a very high refresh interval to speed up testing.
|
||||
|
|
@ -185,6 +185,7 @@ Note: To ensure the build succeeds after creating new translations, make sure to
|
|||
|
||||
[1]: https://go.dev
|
||||
[2]: https://nodejs.org/
|
||||
[3]: https://viteplus.dev/guide/#install-vp
|
||||
|
||||
## Documentation, Website and iOS/Android App
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# STEP 1 build ui
|
||||
FROM --platform=$BUILDPLATFORM node:26-alpine AS node
|
||||
|
||||
RUN apk update && apk add --no-cache make
|
||||
RUN apk update && apk add --no-cache make && curl -fsSL https://vite.plus | bash
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
|
|
|
|||
10
Makefile
10
Makefile
|
|
@ -40,10 +40,10 @@ install::
|
|||
go install tool
|
||||
|
||||
install-ui::
|
||||
npm ci
|
||||
vp install
|
||||
|
||||
ui::
|
||||
npm run build
|
||||
vp run build
|
||||
|
||||
assets::
|
||||
go generate ./...
|
||||
|
|
@ -59,7 +59,7 @@ modernize:
|
|||
go tool modernize -test -fix -stringsbuilder=false -omitzero=false ./...
|
||||
|
||||
lint-ui::
|
||||
npm run lint
|
||||
vp run lint
|
||||
|
||||
license::
|
||||
go run github.com/google/go-licenses/v2@latest check \
|
||||
|
|
@ -74,10 +74,10 @@ license::
|
|||
./...
|
||||
|
||||
license-ui::
|
||||
npm run license
|
||||
vp run license
|
||||
|
||||
test-ui::
|
||||
npm test
|
||||
vp run test
|
||||
|
||||
test::
|
||||
@echo "Running testsuite"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { describe, it, expect } from "vitest";
|
||||
import { describe, it, expect } from "vite-plus/test";
|
||||
import colors, {
|
||||
dimColor,
|
||||
lighterColor,
|
||||
|
|
|
|||
|
|
@ -313,7 +313,7 @@ export default defineComponent({
|
|||
},
|
||||
batteryDetails() {
|
||||
if (!this.batteryDevices.length) {
|
||||
return;
|
||||
return "";
|
||||
}
|
||||
const multipleBatteries = this.batteryDevices.length > 1;
|
||||
return this.batteryDevices
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { describe, it, expect } from "vitest";
|
||||
import { describe, it, expect } from "vite-plus/test";
|
||||
import { historyToSeries, forecastToSeries, buildChartBatteries } from "./history";
|
||||
import type { BatteryMeter, EvOpt } from "@/types/evcc";
|
||||
import type { BatteryHistorySeries } from "./types";
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { mount, config } from "@vue/test-utils";
|
||||
import { beforeAll, describe, expect, test } from "vitest";
|
||||
import { beforeAll, describe, expect, test } from "vite-plus/test";
|
||||
import Preview from "./Preview.vue";
|
||||
import type { Slot } from "@/types/evcc";
|
||||
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ export default defineComponent({
|
|||
return null;
|
||||
},
|
||||
targetOffset(): number | undefined {
|
||||
if (!this.targetTime) return;
|
||||
if (!this.targetTime) return undefined;
|
||||
const start = new Date(this.startTime);
|
||||
start.setMinutes(start.getMinutes() - (start.getMinutes() % 15));
|
||||
start.setSeconds(0);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { describe, expect, it } from "vite-plus/test";
|
||||
import { createServiceEndpoints, type TemplateParam } from "./index";
|
||||
|
||||
const buildParam = (name: string, service?: string): TemplateParam => ({
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@
|
|||
import type { PropType } from "vue";
|
||||
import QRCode from "qrcode";
|
||||
import "@h2d2/shopicons/es/regular/trash";
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
// oxlint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import type { EebusConfig, EebusPairing, EebusStatus, YamlSource } from "@/types/evcc";
|
||||
import api from "@/api";
|
||||
import JsonModal from "./JsonModal.vue";
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ export default defineComponent({
|
|||
},
|
||||
{
|
||||
label: "primary",
|
||||
options: [...products.filter((p: Product) => p.group !== "generic")],
|
||||
options: products.filter((p: Product) => p.group !== "generic"),
|
||||
},
|
||||
];
|
||||
},
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
/* eslint-disable vue/prop-name-casing */
|
||||
/* oxlint-disable vue/prop-name-casing */
|
||||
import FormRow from "./FormRow.vue";
|
||||
import PropertyField from "./PropertyField.vue";
|
||||
|
||||
|
|
|
|||
|
|
@ -513,7 +513,7 @@ export default defineComponent({
|
|||
},
|
||||
batteryGridChargeLimitFmt() {
|
||||
if (!this.batteryGridChargeLimitSet) {
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
if (this.smartCostCo2) {
|
||||
return this.fmtCo2Short(this.batteryGridChargeLimit);
|
||||
|
|
|
|||
|
|
@ -246,7 +246,7 @@ export default defineComponent({
|
|||
return Math.round(Math.min(maxWidth, Math.max(minWidth, scale * maxWidth)));
|
||||
},
|
||||
options() {
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
// oxlint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const vThis = this;
|
||||
return {
|
||||
...commonOptions,
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ export default defineComponent({
|
|||
const priceColor = colors.price || "";
|
||||
const exportColor = colors.export || "";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
// oxlint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const vThis = this;
|
||||
return {
|
||||
animationDuration: 0,
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ export default defineComponent({
|
|||
chartOption(): Record<string, unknown> {
|
||||
const color = this.color;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
// oxlint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const vThis = this;
|
||||
return {
|
||||
animationDuration: 0,
|
||||
|
|
|
|||
|
|
@ -220,7 +220,7 @@ export default defineComponent({
|
|||
// Which category slots carry a bar, so hover can skip empty slots.
|
||||
slotsWithData(): boolean[] {
|
||||
const index = new Map(this.categoryKeys.map((k, i) => [k, i]));
|
||||
const has = new Array(this.categoryKeys.length).fill(false);
|
||||
const has = Array.from({ length: this.categoryKeys.length }, () => false);
|
||||
for (const s of this.visibleSeries) {
|
||||
for (const slot of s.data) {
|
||||
if (slot.energy <= 0 && slot.returnEnergy <= 0) continue;
|
||||
|
|
@ -264,7 +264,10 @@ export default defineComponent({
|
|||
|
||||
// Always render overlay slot (line series) so series structure is stable;
|
||||
// data is all-null when toggled off. Prepend so it renders BEHIND bars.
|
||||
const overlayValues: (number | null)[] = new Array(cats.length).fill(null);
|
||||
const overlayValues: (number | null)[] = Array.from(
|
||||
{ length: cats.length },
|
||||
() => null
|
||||
);
|
||||
if (this.showOverlay && this.overlay.length) {
|
||||
for (const s of this.overlay) {
|
||||
for (const slot of s.data) {
|
||||
|
|
@ -297,8 +300,14 @@ export default defineComponent({
|
|||
const energyByEntity: (number | null)[][] = [];
|
||||
const returnEnergyByEntity: (number | null)[][] = [];
|
||||
this.series.forEach((s, i) => {
|
||||
const energyValues: (number | null)[] = new Array(cats.length).fill(null);
|
||||
const returnEnergyValues: (number | null)[] = new Array(cats.length).fill(null);
|
||||
const energyValues: (number | null)[] = Array.from(
|
||||
{ length: cats.length },
|
||||
() => null
|
||||
);
|
||||
const returnEnergyValues: (number | null)[] = Array.from(
|
||||
{ length: cats.length },
|
||||
() => null
|
||||
);
|
||||
const hidden =
|
||||
this.focusedEntity !== null && this.focusedEntity !== (s.paletteIndex ?? i);
|
||||
if (!hidden) {
|
||||
|
|
@ -315,8 +324,8 @@ export default defineComponent({
|
|||
});
|
||||
// Per slot: index of the topmost (largest i) entity with a non-zero
|
||||
// value. -1 = no entity has data at that slot.
|
||||
const topEnergyPerSlot: number[] = new Array(cats.length).fill(-1);
|
||||
const topReturnEnergyPerSlot: number[] = new Array(cats.length).fill(-1);
|
||||
const topEnergyPerSlot: number[] = Array.from({ length: cats.length }, () => -1);
|
||||
const topReturnEnergyPerSlot: number[] = Array.from({ length: cats.length }, () => -1);
|
||||
for (let i = 0; i < this.series.length; i++) {
|
||||
for (let idx = 0; idx < cats.length; idx++) {
|
||||
if ((energyByEntity[i]![idx] ?? 0) > 0) topEnergyPerSlot[idx] = i;
|
||||
|
|
@ -350,7 +359,9 @@ export default defineComponent({
|
|||
// top entity doesn't drop the rounding; a focused entity is solo.
|
||||
const stableIdx = s.paletteIndex ?? i;
|
||||
const energyData: (
|
||||
number | null | { value: number; itemStyle: { borderRadius: number[] } }
|
||||
| number
|
||||
| null
|
||||
| { value: number; itemStyle: { borderRadius: number[] } }
|
||||
)[] = energyValues.map((v, idx) => {
|
||||
if (v == null) return v;
|
||||
const isTop =
|
||||
|
|
@ -361,7 +372,9 @@ export default defineComponent({
|
|||
return { value: v, itemStyle: { borderRadius: [radius, radius, 0, 0] } };
|
||||
});
|
||||
const returnEnergyData: (
|
||||
number | null | { value: number; itemStyle: { borderRadius: number[] } }
|
||||
| number
|
||||
| null
|
||||
| { value: number; itemStyle: { borderRadius: number[] } }
|
||||
)[] = returnEnergyValues.map((v, idx) => {
|
||||
if (v == null) return v;
|
||||
const isBottom =
|
||||
|
|
@ -486,7 +499,7 @@ export default defineComponent({
|
|||
if (p.value == null) continue;
|
||||
hasBar = true;
|
||||
if (typeof p.value === "number" && p.value > 0) {
|
||||
if (/-energy$/.test(p.seriesId)) sum += p.value;
|
||||
if (p.seriesId.endsWith("-energy")) sum += p.value;
|
||||
}
|
||||
}
|
||||
let x = point[0] - w / 2;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { describe, it, expect } from "vitest";
|
||||
import { describe, it, expect } from "vite-plus/test";
|
||||
import { formatJson } from "./format";
|
||||
|
||||
describe("formatJson", () => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { describe, it, expect } from "vitest";
|
||||
import { describe, it, expect } from "vite-plus/test";
|
||||
import { generateGitHubContent } from "./template";
|
||||
import type { IssueData, Sections } from "./types";
|
||||
|
||||
|
|
|
|||
|
|
@ -176,8 +176,13 @@ export default defineComponent({
|
|||
return this.loadpoints[index]?.id;
|
||||
},
|
||||
handleCarouselScroll() {
|
||||
const { scrollLeft } = this.$refs["carousel"] as HTMLElement;
|
||||
const { offsetWidth } = this.$refs["carousel"]?.children[0] as HTMLElement;
|
||||
const carousel = this.$refs["carousel"] as HTMLElement | undefined;
|
||||
if (!carousel || !carousel.children.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { scrollLeft } = carousel;
|
||||
const { offsetWidth } = carousel.children[0] as HTMLElement;
|
||||
this.highlightedIndex = Math.round((scrollLeft - 7.5) / offsetWidth);
|
||||
|
||||
// save scroll position to url if not changing for 2s
|
||||
|
|
@ -203,7 +208,11 @@ export default defineComponent({
|
|||
this.viewportHeight = window.innerHeight;
|
||||
},
|
||||
left(index: number) {
|
||||
return (this.$refs["carousel"]?.children[0] as HTMLElement).offsetWidth * index;
|
||||
const carousel = this.$refs["carousel"] as HTMLElement | undefined;
|
||||
if (!carousel || !carousel.children.length) {
|
||||
return 0;
|
||||
}
|
||||
return (carousel.children[0] as HTMLElement).offsetWidth * index;
|
||||
},
|
||||
scrollTo(index: number) {
|
||||
this.highlightedIndex = index;
|
||||
|
|
@ -224,12 +233,14 @@ export default defineComponent({
|
|||
},
|
||||
openChargingPlanModal(loadpointId: string) {
|
||||
const modal = this.$refs["chargingPlanModal"] as
|
||||
InstanceType<typeof ChargingPlanModal> | undefined;
|
||||
| InstanceType<typeof ChargingPlanModal>
|
||||
| undefined;
|
||||
modal?.open(loadpointId);
|
||||
},
|
||||
openSettingsModal(loadpointId: string) {
|
||||
const modal = this.$refs["settingsModal"] as
|
||||
InstanceType<typeof SettingsModal> | undefined;
|
||||
| InstanceType<typeof SettingsModal>
|
||||
| undefined;
|
||||
modal?.open(loadpointId);
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -115,9 +115,7 @@
|
|||
moneySaved,
|
||||
currency,
|
||||
false
|
||||
)} ${fmtCurrencySymbol(currency)} ${$t(
|
||||
'footer.savings.moneySaved'
|
||||
)}`
|
||||
)} ${fmtCurrencySymbol(currency)} ${$t('footer.savings.moneySaved')}`
|
||||
: ''
|
||||
"
|
||||
/>
|
||||
|
|
@ -131,11 +129,7 @@
|
|||
unit="g/kWh"
|
||||
:sub1="
|
||||
region && co2Configured
|
||||
? `${fmtNumber(
|
||||
co2Saved,
|
||||
0,
|
||||
'kilogram'
|
||||
)} ${$t('footer.savings.co2Saved')}`
|
||||
? `${fmtNumber(co2Saved, 0, 'kilogram')} ${$t('footer.savings.co2Saved')}`
|
||||
: ''
|
||||
"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -249,7 +249,7 @@ export default defineComponent({
|
|||
},
|
||||
options() {
|
||||
// capture vue component this to be used in chartjs callbacks
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
// oxlint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const vThis = this;
|
||||
return {
|
||||
...commonOptions,
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ export default defineComponent({
|
|||
},
|
||||
options() {
|
||||
// capture vue component this to be used in chartjs callbacks
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
// oxlint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const vThis = this;
|
||||
return {
|
||||
...commonOptions,
|
||||
|
|
|
|||
|
|
@ -27,7 +27,8 @@ export default defineComponent({
|
|||
methods: {
|
||||
requestAuthProvider(providerId: string) {
|
||||
const navigationArea = this.$refs["navigationArea"] as
|
||||
InstanceType<typeof TopNavigationArea> | undefined;
|
||||
| InstanceType<typeof TopNavigationArea>
|
||||
| undefined;
|
||||
navigationArea?.requestAuthProvider(providerId);
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { mount, config } from "@vue/test-utils";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { describe, expect, test } from "vite-plus/test";
|
||||
import Status from "./Status.vue";
|
||||
import { CURRENCY } from "@/types/evcc";
|
||||
import en from "../../../../i18n/en.json";
|
||||
|
|
|
|||
|
|
@ -199,7 +199,8 @@ export default defineComponent({
|
|||
displayLimitSoc: this.effectiveLimitSoc,
|
||||
statusOverride: undefined as VehicleStatus | undefined,
|
||||
chargingPlanModal: this.$refs["chargingPlanModal"] as
|
||||
InstanceType<typeof ChargingPlanModal> | undefined,
|
||||
| InstanceType<typeof ChargingPlanModal>
|
||||
| undefined,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { describe, expect, test } from "vitest";
|
||||
import { describe, expect, test } from "vite-plus/test";
|
||||
import { parseKey, parseQueryString, buildQuery, extractQueryString } from "./configModal";
|
||||
|
||||
describe("parseKey", () => {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ export default {
|
|||
self.updateBreakpoint();
|
||||
window.addEventListener("resize", self.updateBreakpoint);
|
||||
},
|
||||
beforeDestroy() {
|
||||
beforeUnmount() {
|
||||
const self = this as any;
|
||||
window.removeEventListener("resize", self.updateBreakpoint);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { mount, config } from "@vue/test-utils";
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
import { describe, expect, test, vi } from "vite-plus/test";
|
||||
import formatter, { POWER_UNIT } from "./formatter";
|
||||
import * as units from "../units";
|
||||
import { defineComponent } from "vue";
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { describe, expect, test } from "vitest";
|
||||
import { describe, expect, test } from "vite-plus/test";
|
||||
import { stringifyQuery } from "./router";
|
||||
|
||||
describe("stringifyQuery", () => {
|
||||
|
|
|
|||
|
|
@ -764,7 +764,13 @@ export interface SelectOption<T> {
|
|||
}
|
||||
|
||||
export type DeviceType =
|
||||
"charger" | "meter" | "vehicle" | "loadpoint" | "messenger" | "tariff" | "hems";
|
||||
| "charger"
|
||||
| "meter"
|
||||
| "vehicle"
|
||||
| "loadpoint"
|
||||
| "messenger"
|
||||
| "tariff"
|
||||
| "hems";
|
||||
export type MeterType = "grid" | "pv" | "battery" | "charge" | "aux" | "ext" | "consumer";
|
||||
export type MeterTemplateUsage = "grid" | "pv" | "battery" | "charge" | "aux";
|
||||
export type TariffType = "grid" | "feedIn" | "co2" | "planner" | "solar" | "temperature";
|
||||
|
|
|
|||
2
assets/js/types/vue.d.ts
vendored
2
assets/js/types/vue.d.ts
vendored
|
|
@ -1,4 +1,4 @@
|
|||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
// oxlint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import type { ComponentCustomProperties } from "vue";
|
||||
|
||||
declare module "vue" {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { describe, expect, test } from "vitest";
|
||||
import { describe, expect, test } from "vite-plus/test";
|
||||
import { circuitTree } from "./circuits";
|
||||
|
||||
describe("circuitTree", () => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { describe, expect, test } from "vitest";
|
||||
import { describe, expect, test } from "vite-plus/test";
|
||||
import { cleanYaml } from "./cleanYaml";
|
||||
|
||||
describe("cleanYaml", () => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { describe, expect, test } from "vitest";
|
||||
import { describe, expect, test } from "vite-plus/test";
|
||||
import { extractDomain } from "./extractDomain";
|
||||
|
||||
describe("extractDomain", () => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { describe, expect, test } from "vitest";
|
||||
import { describe, expect, test } from "vite-plus/test";
|
||||
import { findLowestSumSlotIndex, isStaticTariff } from "./forecast";
|
||||
|
||||
describe("findLowestSumSlotIndex", () => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { describe, it, expect } from "vitest";
|
||||
import { describe, it, expect } from "vite-plus/test";
|
||||
import { extractPlaceholders as extract, replacePlaceholders as replace } from "./placeholder";
|
||||
|
||||
describe("placeholder", () => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { describe, expect, test } from "vitest";
|
||||
import { describe, expect, test } from "vite-plus/test";
|
||||
import { calculateCostRange, findRateInRange, generateRateSlots } from "./tariffSlots";
|
||||
import type { Rate, Slot } from "../types/evcc";
|
||||
|
||||
|
|
|
|||
6
env.d.ts
vendored
6
env.d.ts
vendored
|
|
@ -1 +1,7 @@
|
|||
/// <reference types="vite/client" />
|
||||
|
||||
declare module "*.vue" {
|
||||
import type { DefineComponent } from "vue";
|
||||
const component: DefineComponent<{}, {}, unknown>;
|
||||
export default component;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
import globals from "globals";
|
||||
import js from "@eslint/js";
|
||||
import pluginVue from "eslint-plugin-vue";
|
||||
import { defineConfigWithVueTs, vueTsConfigs } from "@vue/eslint-config-typescript";
|
||||
import skipFormattingConfig from "@vue/eslint-config-prettier/skip-formatting";
|
||||
|
||||
export default [
|
||||
...defineConfigWithVueTs(
|
||||
js.configs.recommended,
|
||||
...pluginVue.configs["flat/recommended"],
|
||||
vueTsConfigs.recommended,
|
||||
skipFormattingConfig,
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.node,
|
||||
},
|
||||
|
||||
ecmaVersion: "latest",
|
||||
sourceType: "module",
|
||||
},
|
||||
|
||||
rules: {
|
||||
"vue/require-default-prop": "off",
|
||||
"vue/attribute-hyphenation": "off",
|
||||
"vue/multi-word-component-names": "off",
|
||||
"vue/no-reserved-component-names": "off",
|
||||
/*"vue/no-undef-properties": "warn",*/
|
||||
"no-param-reassign": "error",
|
||||
"vue/block-lang": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
},
|
||||
}
|
||||
).map((config) => ({
|
||||
...config,
|
||||
files: ["assets/**/*.{ts,js,vue}", "tests/**/*.ts"],
|
||||
})),
|
||||
];
|
||||
6
oxfmt.config.ts
Normal file
6
oxfmt.config.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { defineConfig } from "oxfmt";
|
||||
|
||||
export default defineConfig({
|
||||
ignorePatterns: ["tests/custom-css.css"],
|
||||
overrides: [{ files: ["**/*.vue"], options: { tabWidth: 4 } }],
|
||||
});
|
||||
4712
package-lock.json
generated
4712
package-lock.json
generated
File diff suppressed because it is too large
Load diff
46
package.json
46
package.json
|
|
@ -3,17 +3,17 @@
|
|||
"description": "evcc UI",
|
||||
"author": "evcc-io",
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"test": "cross-env TZ=Europe/Berlin NODE_OPTIONS=--no-experimental-webstorage vitest",
|
||||
"lint": "npm run lint:prettier && npm run lint:eslint && npm run lint:tsc && npm run lint:i18n",
|
||||
"lint:prettier": "prettier assets tests **/*.{yaml,sh} i18n/*.json --write",
|
||||
"lint:eslint": "eslint --fix --max-warnings=0",
|
||||
"build": "vp build",
|
||||
"test": "cross-env TZ=Europe/Berlin NODE_OPTIONS=--no-experimental-webstorage vp test",
|
||||
"lint": "vp run lint:format && vp run lint:lint && vp run lint:tsc && vp run lint:i18n",
|
||||
"lint:format": "vp fmt assets tests **/*.{yaml,sh} i18n/*.json --write",
|
||||
"lint:lint": "vp lint --fix --max-warnings=0",
|
||||
"lint:tsc": "vue-tsc --noEmit",
|
||||
"lint:i18n": "tsx i18n/check.ts",
|
||||
"dev": "vite",
|
||||
"playwright": "playwright test --ui",
|
||||
"dev": "vp dev",
|
||||
"playwright": "playwright install && playwright test --ui",
|
||||
"playwright:ci": "cross-env CI=true playwright test",
|
||||
"simulator": "vite tests/simulator",
|
||||
"simulator": "vp dev tests/simulator",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"license": "license-compliance --production --report detailed --allow \"MIT;Apache-2.0;ISC;BSD-2-Clause;BSD-3-Clause;0BSD\""
|
||||
},
|
||||
|
|
@ -51,11 +51,15 @@
|
|||
"npm": ">=10.0.0",
|
||||
"node": ">=26.0.0"
|
||||
},
|
||||
"devEngines": {
|
||||
"packageManager": {
|
||||
"name": "npm",
|
||||
"version": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"license": "MIT",
|
||||
"repository": "github:evcc-io/evcc",
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.3.6",
|
||||
"@eslint/js": "^9.39.4",
|
||||
"@jest/types": "^30.4.1",
|
||||
"@playwright/test": "^1.62.0",
|
||||
"@storybook/vue3": "^10.4.4",
|
||||
|
|
@ -71,40 +75,34 @@
|
|||
"@vitejs/plugin-legacy": "^8.2.2",
|
||||
"@vitejs/plugin-vue": "^6.0.8",
|
||||
"@vue/compiler-sfc": "^3.5.38",
|
||||
"@vue/eslint-config-prettier": "^10.2.0",
|
||||
"@vue/eslint-config-typescript": "^14.9.0",
|
||||
"@vue/test-utils": "^2.4.11",
|
||||
"@vue/tsconfig": "^0.9.1",
|
||||
"body-parser": "^2.3.0",
|
||||
"cross-env": "^10.1.0",
|
||||
"eslint": "^9.39.5",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-prettier": "^5.5.6",
|
||||
"eslint-plugin-promise": "^7.3.0",
|
||||
"eslint-plugin-vue": "^10.10.0",
|
||||
"globals": "^17.8.0",
|
||||
"happy-dom": "^20.11.1",
|
||||
"jiti": "^2.7.0",
|
||||
"kill-port": "^2.0.1",
|
||||
"license-compliance": "^3.0.1",
|
||||
"mqtt": "^5.15.2",
|
||||
"prettier": "^3.9.6",
|
||||
"prettier-plugin-sh": "^0.18.1",
|
||||
"prettier-plugin-sort-json": "^4.2.0",
|
||||
"rollup-plugin-visualizer": "^7.0.1",
|
||||
"storybook": "^10.5.5",
|
||||
"terser": "^5.49.0",
|
||||
"tsx": "^4.23.1",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint-language-service": "^5.0.5",
|
||||
"vite": "^8.1.5",
|
||||
"vitest": "^4.1.10",
|
||||
"vite": "npm:@voidzero-dev/vite-plus-core@0.2.6",
|
||||
"vitest": "4.1.10",
|
||||
"vue-eslint-parser": "^10.4.1",
|
||||
"vue-hot-reload-api": "^2.3.4",
|
||||
"vue-tsc": "^3.3.8",
|
||||
"wait-on": "^9.1.0",
|
||||
"ws": "^8.21.1"
|
||||
"ws": "^8.21.1",
|
||||
"vite-plus": "0.2.6"
|
||||
},
|
||||
"overrides": {
|
||||
"vite": "npm:@voidzero-dev/vite-plus-core@0.2.6",
|
||||
"vitest": "4.1.10"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
/** @type {import("prettier").Config} */
|
||||
export default {
|
||||
printWidth: 100,
|
||||
trailingComma: "es5",
|
||||
plugins: ["prettier-plugin-sh"],
|
||||
// mirror .editorconfig for editors that resolve prettier config without it (e.g. Zed)
|
||||
overrides: [
|
||||
{ files: ["*.vue", "*.sh"], options: { useTabs: true, tabWidth: 4 } },
|
||||
{ files: "*.css", options: { useTabs: true, tabWidth: 2 } },
|
||||
],
|
||||
};
|
||||
|
|
@ -62,7 +62,7 @@ export async function startSimulator() {
|
|||
log(`wait until port ${port} is available`);
|
||||
await waitOn({ resources: [`tcp:${port}`], reverse: true, log: LOG_ENABLED });
|
||||
|
||||
const instance = spawn("npm", ["run", "simulator", "--", "--port", port.toString()]);
|
||||
const instance = spawn("vp", ["run", "simulator", "--port", port.toString()]);
|
||||
|
||||
const steamLog = createSteamLog();
|
||||
instance.stdout.pipe(steamLog);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { defineConfig } from "vite";
|
||||
import { defineConfig } from "vite-plus";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import api from "./api";
|
||||
|
||||
|
|
|
|||
|
|
@ -9,11 +9,9 @@
|
|||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"composite": true,
|
||||
"allowJs": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["assets/js/*"]
|
||||
"@/*": ["./assets/js/*"]
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { defineConfig } from "vite";
|
||||
import { defineConfig, lazyPlugins } from "vite-plus";
|
||||
import vuePlugin from "@vitejs/plugin-vue";
|
||||
import legacy from "@vitejs/plugin-legacy";
|
||||
import { browserslistToTargets } from "lightningcss";
|
||||
|
|
@ -7,6 +7,52 @@ import { visualizer } from "rollup-plugin-visualizer";
|
|||
import path from "path";
|
||||
|
||||
export default defineConfig({
|
||||
staged: {
|
||||
"*": "vp check --fix",
|
||||
},
|
||||
lint: {
|
||||
plugins: ["oxc", "typescript", "unicorn", "react"],
|
||||
categories: {
|
||||
correctness: "warn",
|
||||
},
|
||||
env: {
|
||||
builtin: true,
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ["assets/**/*.{ts,js,vue}", "tests/**/*.ts"],
|
||||
rules: {
|
||||
"no-param-reassign": "error",
|
||||
"vue/require-default-prop": "off",
|
||||
"vue/no-reserved-component-names": "off",
|
||||
"typescript/no-explicit-any": "off",
|
||||
},
|
||||
plugins: ["vue"],
|
||||
env: {
|
||||
es2026: true,
|
||||
browser: true,
|
||||
node: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
options: {},
|
||||
jsPlugins: [
|
||||
{
|
||||
name: "vite-plus",
|
||||
specifier: "vite-plus/oxlint-plugin",
|
||||
},
|
||||
],
|
||||
rules: {
|
||||
"vite-plus/prefer-vite-plus-imports": "error",
|
||||
},
|
||||
},
|
||||
fmt: {
|
||||
printWidth: 100,
|
||||
trailingComma: "es5",
|
||||
sortPackageJson: false,
|
||||
ignorePatterns: ["tests/custom-css.css"],
|
||||
overrides: [{ files: ["**/*.vue"], options: { tabWidth: 4 } }],
|
||||
},
|
||||
root: "./assets",
|
||||
publicDir: "public",
|
||||
base: "./",
|
||||
|
|
@ -41,7 +87,7 @@ export default defineConfig({
|
|||
},
|
||||
};
|
||||
})(),
|
||||
plugins: [
|
||||
plugins: lazyPlugins(() => [
|
||||
legacy({
|
||||
modernPolyfills: ["es.promise.all-settled"],
|
||||
}),
|
||||
|
|
@ -53,5 +99,5 @@ export default defineConfig({
|
|||
},
|
||||
}),
|
||||
visualizer({ filename: "asset-stats.html" }),
|
||||
],
|
||||
]),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { mergeConfig } from "vite";
|
||||
import { defineConfig } from "vitest/config";
|
||||
import { mergeConfig } from "vite-plus";
|
||||
import { defineConfig } from "vite-plus";
|
||||
import viteConfig from "./vite.config";
|
||||
|
||||
export default mergeConfig(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue