From ec9af3782f7dc647c2ce97d4e03070a210f28080 Mon Sep 17 00:00:00 2001 From: Michael Geers Date: Mon, 17 Mar 2025 20:46:36 +0100 Subject: [PATCH] chore: Storybook migration (#19780) --- .gitignore | 2 + .storybook/main.js | 14 + .storybook/preview.js | 42 + CONTRIBUTING.md | 8 + assets/histoire.setup.js | 14 - .../ChargingPlans/ChargingPlan.stories.js | 55 + .../ChargingPlans/ChargingPlan.story.vue | 22 - .../ChargingPlans/Preview.stories.js | 114 + .../ChargingPlans/Preview.story.vue | 100 - .../Energyflow/BatteryIcon.stories.js | 34 + .../Energyflow/BatteryIcon.story.vue | 20 - .../Energyflow/Energyflow.stories.js | 215 + .../Energyflow/Energyflow.story.vue | 209 - assets/js/components/Footer/Footer.stories.js | 58 + assets/js/components/Footer/Footer.story.vue | 31 - .../Footer/OfflineIndicator.stories.js | 32 + .../Footer/OfflineIndicator.story.vue | 20 - .../js/components/Footer/Version.stories.js | 61 + assets/js/components/Footer/Version.story.vue | 32 - .../Loadpoints/Loadpoint.stories.js | 74 + .../components/Loadpoints/Loadpoint.story.vue | 67 - .../Loadpoints/Loadpoints.stories.js | 53 + .../Loadpoints/Loadpoints.story.vue | 47 - .../js/components/Loadpoints/Mode.stories.js | 44 + .../js/components/Loadpoints/Mode.story.vue | 28 - .../components/Loadpoints/Phases.stories.js | 94 + .../js/components/Loadpoints/Phases.story.vue | 71 - .../components/MultiIcon/MultiIcon.stories.js | 44 + .../components/MultiIcon/MultiIcon.story.vue | 15 - .../Savings/LiveCommunity.stories.js | 16 + .../Savings/LiveCommunity.story.vue | 9 - .../js/components/Savings/Savings.story.vue | 31 - .../js/components/Savings/Sponsor.stories.js | 59 + .../js/components/Savings/Sponsor.story.vue | 20 - .../Savings/SponsorTokenExpires.stories.js | 33 + .../Savings/SponsorTokenExpires.story.vue | 14 - assets/js/components/Savings/Tile.stories.js | 37 + assets/js/components/Savings/Tile.story.vue | 16 - .../js/components/Top/Navigation.stories.js | 57 + assets/js/components/Top/Navigation.story.vue | 42 - .../components/Top/Notifications.stories.js | 80 + .../js/components/Top/Notifications.story.vue | 70 - .../VehicleIcon/VehicleIcon.stories.js | 103 + .../VehicleIcon/VehicleIcon.story.vue | 58 - .../js/components/Vehicles/Vehicle.stories.js | 188 + .../js/components/Vehicles/Vehicle.story.vue | 119 - histoire.config.js | 21 - package-lock.json | 3698 ++++++++--------- package.json | 26 +- 49 files changed, 3384 insertions(+), 2933 deletions(-) create mode 100644 .storybook/main.js create mode 100644 .storybook/preview.js delete mode 100644 assets/histoire.setup.js create mode 100644 assets/js/components/ChargingPlans/ChargingPlan.stories.js delete mode 100644 assets/js/components/ChargingPlans/ChargingPlan.story.vue create mode 100644 assets/js/components/ChargingPlans/Preview.stories.js delete mode 100644 assets/js/components/ChargingPlans/Preview.story.vue create mode 100644 assets/js/components/Energyflow/BatteryIcon.stories.js delete mode 100644 assets/js/components/Energyflow/BatteryIcon.story.vue create mode 100644 assets/js/components/Energyflow/Energyflow.stories.js delete mode 100644 assets/js/components/Energyflow/Energyflow.story.vue create mode 100644 assets/js/components/Footer/Footer.stories.js delete mode 100644 assets/js/components/Footer/Footer.story.vue create mode 100644 assets/js/components/Footer/OfflineIndicator.stories.js delete mode 100644 assets/js/components/Footer/OfflineIndicator.story.vue create mode 100644 assets/js/components/Footer/Version.stories.js delete mode 100644 assets/js/components/Footer/Version.story.vue create mode 100644 assets/js/components/Loadpoints/Loadpoint.stories.js delete mode 100644 assets/js/components/Loadpoints/Loadpoint.story.vue create mode 100644 assets/js/components/Loadpoints/Loadpoints.stories.js delete mode 100644 assets/js/components/Loadpoints/Loadpoints.story.vue create mode 100644 assets/js/components/Loadpoints/Mode.stories.js delete mode 100644 assets/js/components/Loadpoints/Mode.story.vue create mode 100644 assets/js/components/Loadpoints/Phases.stories.js delete mode 100644 assets/js/components/Loadpoints/Phases.story.vue create mode 100644 assets/js/components/MultiIcon/MultiIcon.stories.js delete mode 100644 assets/js/components/MultiIcon/MultiIcon.story.vue create mode 100644 assets/js/components/Savings/LiveCommunity.stories.js delete mode 100644 assets/js/components/Savings/LiveCommunity.story.vue delete mode 100644 assets/js/components/Savings/Savings.story.vue create mode 100644 assets/js/components/Savings/Sponsor.stories.js delete mode 100644 assets/js/components/Savings/Sponsor.story.vue create mode 100644 assets/js/components/Savings/SponsorTokenExpires.stories.js delete mode 100644 assets/js/components/Savings/SponsorTokenExpires.story.vue create mode 100644 assets/js/components/Savings/Tile.stories.js delete mode 100644 assets/js/components/Savings/Tile.story.vue create mode 100644 assets/js/components/Top/Navigation.stories.js delete mode 100644 assets/js/components/Top/Navigation.story.vue create mode 100644 assets/js/components/Top/Notifications.stories.js delete mode 100644 assets/js/components/Top/Notifications.story.vue create mode 100644 assets/js/components/VehicleIcon/VehicleIcon.stories.js delete mode 100644 assets/js/components/VehicleIcon/VehicleIcon.story.vue create mode 100644 assets/js/components/Vehicles/Vehicle.stories.js delete mode 100644 assets/js/components/Vehicles/Vehicle.story.vue delete mode 100644 histoire.config.js diff --git a/.gitignore b/.gitignore index f9a1e6bdd..f63292a08 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,5 @@ asset-stats.html /playwright/.cache/ .gitpod.yml /evcc.db + +*storybook.log diff --git a/.storybook/main.js b/.storybook/main.js new file mode 100644 index 000000000..e02211965 --- /dev/null +++ b/.storybook/main.js @@ -0,0 +1,14 @@ +/** @type { import('@storybook/vue3-vite').StorybookConfig } */ +const config = { + stories: ["../assets/js/**/*.stories.@(js|ts)"], + addons: [ + "@storybook/addon-essentials", + "@chromatic-com/storybook", + "@storybook/addon-interactions", + ], + framework: { + name: "@storybook/vue3-vite", + options: {}, + }, +}; +export default config; diff --git a/.storybook/preview.js b/.storybook/preview.js new file mode 100644 index 000000000..00e8bab4a --- /dev/null +++ b/.storybook/preview.js @@ -0,0 +1,42 @@ +import { setup } from "@storybook/vue3"; +import "bootstrap/dist/css/bootstrap.min.css"; +import smoothscroll from "smoothscroll-polyfill"; +import setupI18n from "../assets/js/i18n"; +import "../assets/css/app.css"; +import { watchThemeChanges } from "../assets/js/theme"; + +smoothscroll.polyfill(); +watchThemeChanges(); + +// Setup global parameters +/** @type { import('@storybook/vue3').Preview } */ +const preview = { + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, + }, + backgrounds: { + default: "default", + values: [ + { + name: "default", + value: "var(--evcc-background)", + }, + { + name: "box", + value: "var(--evcc-box)", + }, + ], + }, + }, +}; + +setup((app) => { + app.config.globalProperties.$hiddenFeatures = () => true; + app.use(setupI18n()); +}); + +export default preview; diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4cfd19763..da259eee5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -89,6 +89,14 @@ npm install npm run dev ``` +#### Storybook + +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 +``` + #### Integration testing We use Playwright for end-to-end integration tests. They start a local evcc instance with different configuration yamls and prefilled databases. To run them, you have to do a local build first. diff --git a/assets/histoire.setup.js b/assets/histoire.setup.js deleted file mode 100644 index 7c469626e..000000000 --- a/assets/histoire.setup.js +++ /dev/null @@ -1,14 +0,0 @@ -import { defineSetupVue3 } from "@histoire/plugin-vue"; -import "bootstrap/dist/css/bootstrap.min.css"; -import smoothscroll from "smoothscroll-polyfill"; -import setupI18n from "./js/i18n"; -import "./css/app.css"; -import { watchThemeChanges } from "./js/theme"; - -smoothscroll.polyfill(); -watchThemeChanges(); - -export const setupVue3 = defineSetupVue3(({ app }) => { - app.config.globalProperties.$hiddenFeatures = () => true; - app.use(setupI18n()); -}); diff --git a/assets/js/components/ChargingPlans/ChargingPlan.stories.js b/assets/js/components/ChargingPlans/ChargingPlan.stories.js new file mode 100644 index 000000000..ed5452e8f --- /dev/null +++ b/assets/js/components/ChargingPlans/ChargingPlan.stories.js @@ -0,0 +1,55 @@ +import ChargingPlan from "./ChargingPlan.vue"; + +const hoursFromNow = (hours) => { + const now = new Date(); + now.setHours(now.getHours() + hours); + return now.toISOString(); +}; + +export default { + title: "ChargingPlans/ChargingPlan", + component: ChargingPlan, + parameters: { + layout: "centered", + }, + argTypes: { + id: { control: "text" }, + effectivePlanTime: { control: "text" }, + effectivePlanSoc: { control: "number" }, + planEnergy: { control: "number" }, + socBasedCharging: { control: "boolean" }, + socBasedPlanning: { control: "boolean" }, + vehicleSoc: { control: "number" }, + }, +}; + +const Template = (args) => ({ + components: { ChargingPlan }, + setup() { + return { args }; + }, + template: '', +}); + +export const None = Template.bind({}); +None.args = { + id: "1", +}; + +export const SocBasedCharging = Template.bind({}); +SocBasedCharging.args = { + id: "1", + effectivePlanTime: hoursFromNow(4), + effectivePlanSoc: 77, + socBasedCharging: true, + socBasedPlanning: true, +}; + +export const EnergyBasedCharging = Template.bind({}); +EnergyBasedCharging.args = { + id: "1", + effectivePlanTime: hoursFromNow(12), + planEnergy: 77, + socBasedCharging: false, + socBasedPlanning: false, +}; diff --git a/assets/js/components/ChargingPlans/ChargingPlan.story.vue b/assets/js/components/ChargingPlans/ChargingPlan.story.vue deleted file mode 100644 index 4f5f5b7b9..000000000 --- a/assets/js/components/ChargingPlans/ChargingPlan.story.vue +++ /dev/null @@ -1,22 +0,0 @@ - - diff --git a/assets/js/components/ChargingPlans/Preview.stories.js b/assets/js/components/ChargingPlans/Preview.stories.js new file mode 100644 index 000000000..2987aa7f6 --- /dev/null +++ b/assets/js/components/ChargingPlans/Preview.stories.js @@ -0,0 +1,114 @@ +import Preview from "./Preview.vue"; + +const now = new Date(); + +function createDate(hoursFromNow) { + const result = new Date(now.getTime()); + result.setHours(result.getHours() + hoursFromNow); + return result; +} + +function createRate(price, hoursFromNow, durationHours = 1) { + const start = new Date(now.getTime()); + start.setHours(start.getHours() + hoursFromNow); + start.setMinutes(0); + start.setSeconds(0); + start.setMilliseconds(0); + const end = new Date(start.getTime()); + end.setHours(start.getHours() + durationHours); + end.setMinutes(0); + end.setSeconds(0); + end.setMilliseconds(0); + return { start: start.toISOString(), end: end.toISOString(), price }; +} + +// Scenario data +const co2Data = { + rates: [ + 545, 518, 545, 518, 0, 545, 527, 527, 536, 518, 400, 336, 336, 339, 344, 336, 336, 336, 372, + 400, 555, 555, 545, 555, 564, 545, 555, 545, 536, 545, 527, 536, 518, 545, 509, 336, 336, 336, + ].map((price, i) => createRate(price, i)), + duration: 8695, + plan: [createRate(213, 4), createRate(336, 11), createRate(336, 12)], + smartCostType: "co2", + targetTime: createDate(14), +}; + +const fixedData = { + rates: [createRate(0.442, 0, 50)], + duration: 8695, + plan: [createRate(0.442, 12, 3)], + smartCostType: "price", + currency: "EUR", + targetTime: createDate(14), +}; + +const zonedData = { + rates: [ + createRate(3.72, 0, 4), + createRate(2.39, 4, 12), + createRate(3.72, 16, 12), + createRate(2.39, 28, 12), + createRate(3.72, 40, 12), + ], + duration: 8695, + plan: [createRate(2.39, 13, 3)], + smartCostType: "price", + currency: "DKK", + targetTime: createDate(17), +}; + +const unknownData = { + rates: co2Data.rates.slice(0, 16), + duration: 8695, + plan: [createRate(213, 4), createRate(336, 11), createRate(336, 12)], + smartCostType: "co2", + targetTime: createDate(14), +}; + +const dynamicData = { + rates: [0.12, 0.15, 0, -0.05, -0.11, -0.24, -0.08, 0.12, 0.25, 0.29, 0.22, 0.31, 0.31, 0.33].map( + (price, i) => createRate(price, i) + ), + duration: 8695, + plan: [createRate(0.23, 2, 5)], + smartCostType: "price", + currency: "EUR", + targetTime: createDate(13), +}; + +export default { + title: "ChargingPlans/Preview", + component: Preview, + argTypes: { + rates: { control: "object" }, + duration: { control: "number" }, + plan: { control: "object" }, + smartCostType: { control: "select", options: ["co2", "price"] }, + currency: { control: "text" }, + targetTime: { control: "text" }, + }, +}; + +const Template = (args) => ({ + components: { Preview }, + setup() { + return { args }; + }, + template: '', +}); + +export const Co2 = Template.bind({}); +Co2.args = co2Data; + +export const Fixed = Template.bind({}); +Fixed.args = fixedData; + +export const Zoned = Template.bind({}); +Zoned.args = zonedData; + +export const Unknown = Template.bind({}); +Unknown.args = unknownData; + +export const Dynamic = Template.bind({}); +Dynamic.args = dynamicData; diff --git a/assets/js/components/ChargingPlans/Preview.story.vue b/assets/js/components/ChargingPlans/Preview.story.vue deleted file mode 100644 index d50f242a0..000000000 --- a/assets/js/components/ChargingPlans/Preview.story.vue +++ /dev/null @@ -1,100 +0,0 @@ - - - diff --git a/assets/js/components/Energyflow/BatteryIcon.stories.js b/assets/js/components/Energyflow/BatteryIcon.stories.js new file mode 100644 index 000000000..4093f2bf6 --- /dev/null +++ b/assets/js/components/Energyflow/BatteryIcon.stories.js @@ -0,0 +1,34 @@ +import BatteryIcon from "./BatteryIcon.vue"; + +export default { + title: "Energyflow/BatteryIcon", + component: BatteryIcon, + parameters: { + layout: "centered", + }, +}; + +const createStory = (args) => { + const story = () => ({ + components: { BatteryIcon }, + setup() { + return { args }; + }, + template: '', + }); + story.args = args; + return story; +}; + +export const Empty = createStory({ soc: 0 }); +export const Soc10 = createStory({ soc: 10 }); +export const Soc20 = createStory({ soc: 20 }); +export const Soc30 = createStory({ soc: 30 }); +export const Soc40 = createStory({ soc: 40 }); +export const Soc50 = createStory({ soc: 50 }); +export const Soc60 = createStory({ soc: 60 }); +export const Soc70 = createStory({ soc: 70 }); +export const Soc80 = createStory({ soc: 80 }); +export const Soc90 = createStory({ soc: 90 }); +export const Hold = createStory({ hold: true }); +export const GridCharge = createStory({ gridCharge: true }); diff --git a/assets/js/components/Energyflow/BatteryIcon.story.vue b/assets/js/components/Energyflow/BatteryIcon.story.vue deleted file mode 100644 index 3dcc2367c..000000000 --- a/assets/js/components/Energyflow/BatteryIcon.story.vue +++ /dev/null @@ -1,20 +0,0 @@ - - - diff --git a/assets/js/components/Energyflow/Energyflow.stories.js b/assets/js/components/Energyflow/Energyflow.stories.js new file mode 100644 index 000000000..d7481728f --- /dev/null +++ b/assets/js/components/Energyflow/Energyflow.stories.js @@ -0,0 +1,215 @@ +import Energyflow from "./Energyflow.vue"; + +export default { + title: "Energyflow/Energyflow", + component: Energyflow, +}; + +const Template = (args) => ({ + components: { Energyflow }, + setup() { + return { args }; + }, + template: '', +}); + +export const GridAndPV = Template.bind({}); +GridAndPV.args = { + gridConfigured: true, + pvConfigured: true, + pvPower: 7300, + gridPower: -2300, + homePower: 800, + loadpointsCompact: [ + { power: 1000, icon: "car", charging: true }, + { power: 1000, icon: "bike", charging: true }, + { power: 2200, icon: "car", charging: true }, + ], + tariffGrid: 0.25, + tariffFeedIn: 0.08, + tariffEffectivePrice: 0.08, + smartCostType: "price", + smartCostAvailable: true, + currency: "EUR", + siteTitle: "Home", + pv: [{ power: 5000 }, { power: 2300 }], +}; + +export const BatteryAndGrid = Template.bind({}); +BatteryAndGrid.args = { + gridConfigured: true, + pvConfigured: true, + batteryConfigured: true, + pvPower: 0, + gridPower: 1200, + homePower: 2000, + batteryPower: 800, + batterySoc: 77, + tariffGrid: 0.25, + tariffFeedIn: 0.08, + tariffEffectivePrice: 0.08, + currency: "EUR", + siteTitle: "Home", + battery: [ + { soc: 44.999, capacity: 13.3, power: 350 }, + { soc: 82.3331, capacity: 21, power: 450 }, + ], +}; + +export const BatteryCharging = Template.bind({}); +BatteryCharging.args = { + gridConfigured: true, + pvConfigured: true, + batteryConfigured: true, + pvPower: 5000, + gridPower: -1300, + homePower: 800, + loadpointsCompact: [{ power: 1400, icon: "car", charging: true }], + batteryPower: -1500, + batterySoc: 75, + siteTitle: "Home", +}; + +export const GridPVAndBattery = Template.bind({}); +GridPVAndBattery.args = { + gridConfigured: true, + pvConfigured: true, + batteryConfigured: true, + pvPower: 1000, + gridPower: 700, + homePower: 3300, + batteryPower: 1500, + batterySoc: 30, + siteTitle: "Home", +}; + +export const BatteryThresholds = Template.bind({}); +BatteryThresholds.args = { + gridConfigured: true, + pvConfigured: true, + batteryConfigured: true, + pvPower: 8700, + gridPower: -500, + loadpointsCompact: [ + { power: 5000, icon: "car", charging: true }, + { power: 2500, icon: "bus", charging: true }, + ], + batteryPower: -700, + batterySoc: 95, + siteTitle: "Home", +}; + +export const PVThresholds = Template.bind({}); +PVThresholds.args = { + gridConfigured: true, + pvConfigured: true, + batteryConfigured: true, + pvPower: 300, + gridPower: 6500, + homePower: 1000, + loadpointsCompact: [ + { power: 5000, icon: "car", charging: true }, + { power: 1600, icon: "car", charging: true }, + ], + batteryPower: 800, + batterySoc: 76, + siteTitle: "Home", +}; + +export const GridOnly = Template.bind({}); +GridOnly.args = { + gridConfigured: true, + pvConfigured: true, + batteryConfigured: true, + pvPower: 0, + gridPower: 6500, + homePower: 1000, + loadpointsCompact: [ + { power: 5500, icon: "car", charging: true }, + { power: 0, icon: "car", charging: false }, + { power: 0, icon: "car", charging: false }, + { power: 0, icon: "car", charging: false }, + ], + batteryPower: 0, + batterySoc: 0, + siteTitle: "Home", +}; + +export const LowPower = Template.bind({}); +LowPower.args = { + gridConfigured: true, + pvConfigured: true, + batteryConfigured: true, + pvPower: 700, + gridPower: -300, + homePower: 300, + batteryPower: -100, + batterySoc: 55, + tariffGrid: 0.25, + tariffFeedIn: 0.08, + tariffEffectivePrice: 0.08, + currency: "EUR", + siteTitle: "Home", +}; + +export const CO2 = Template.bind({}); +CO2.args = { + gridConfigured: true, + pvConfigured: true, + pvPower: 7300, + gridPower: -2300, + homePower: 800, + loadpointsCompact: [ + { power: 1000, icon: "car", charging: true }, + { power: 1000, icon: "car", charging: true }, + { power: 2200, icon: "car", charging: true }, + ], + tariffGrid: 0.25, + tariffFeedIn: 0.08, + tariffEffectivePrice: 0.08, + tariffCo2: 723, + tariffEffectiveCo2: 0, + smartCostType: "co2", + smartCostAvailable: true, + currency: "EUR", + siteTitle: "Home", + pv: [{ power: 5000 }, { power: 2300 }], +}; + +export const UnknownInput = Template.bind({}); +UnknownInput.args = { + gridConfigured: true, + pvConfigured: true, + pvPower: 2000, + gridPower: -2000, + loadpointsCompact: [{ power: 1000, icon: "car", charging: true }], +}; + +export const UnknownInputFill = Template.bind({}); +UnknownInputFill.args = { + gridConfigured: true, + pvConfigured: true, + batteryConfigured: true, + pvPower: 500, + gridPower: 0, + batteryPower: -1000, + loadpointsCompact: [], +}; + +export const UnknownOutput = Template.bind({}); +UnknownOutput.args = { + gridConfigured: true, + pvConfigured: true, + pvPower: 3000, + gridPower: -1000, + loadpointsCompact: [{ power: 1700, icon: "car", charging: true }], +}; + +export const UnknownOutputLessThan10Percent = Template.bind({}); +UnknownOutputLessThan10Percent.args = { + gridConfigured: true, + pvConfigured: true, + pvPower: 3000, + gridPower: -1000, + loadpointsCompact: [{ power: 1800, icon: "car", charging: true }], +}; diff --git a/assets/js/components/Energyflow/Energyflow.story.vue b/assets/js/components/Energyflow/Energyflow.story.vue deleted file mode 100644 index cd06dd36e..000000000 --- a/assets/js/components/Energyflow/Energyflow.story.vue +++ /dev/null @@ -1,209 +0,0 @@ - - - diff --git a/assets/js/components/Footer/Footer.stories.js b/assets/js/components/Footer/Footer.stories.js new file mode 100644 index 000000000..874d2292d --- /dev/null +++ b/assets/js/components/Footer/Footer.stories.js @@ -0,0 +1,58 @@ +import Footer from "./Footer.vue"; + +export default { + title: "Footer/Footer", + component: Footer, + argTypes: { + version: { control: "object" }, + savings: { control: "object" }, + }, +}; + +const Template = (args) => ({ + components: { Footer }, + setup() { + return { args }; + }, + template: '