diff --git a/README.md b/README.md index ed7dddb..a26befd 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ [![npm](https://img.shields.io/npm/v/nextline-web)](https://www.npmjs.com/package/nextline-web) [![Unit tests](https://github.com/simonsobs/nextline-web/actions/workflows/unit-test.yml/badge.svg)](https://github.com/simonsobs/nextline-web/actions/workflows/unit-test.yml) - The front-end web app of Nextline. _Nextline_ is a DAQ sequencer of the [Observatory Control System @@ -19,7 +18,6 @@ unless you need to refer to a specific package. [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.11451619.svg)](https://doi.org/10.5281/zenodo.11451619) - ## Screenshot ![Screenshot](screenshot.png) @@ -39,11 +37,15 @@ Docker images of the Nextline front-end web app are created as You can configure the web app in the container with these variables. -| Environment variable | Default value | Description | -| -------------------- | ----------------------- | ------------------------------------- | -| `PUBLIC_PATH` | `/` | Path in the URL of the web app | -| `API_HTTP` | `http://localhost:8000` | URL of the GraphQL API server | -| `API_NAME` | `localhost` | Text to be shown as part of the title | +| Environment variable | Default value | Description | +| -------------------- | ----------------------- | ------------------------------------------ | +| `PUBLIC_PATH` | `/` | Path in the URL of the web app | +| `API_HTTP` | `http://localhost:8000` | URL of the GraphQL API server | +| `API_NAME` | `localhost` | Text to be shown as part of the title | +| `SEED_COLOR` | `#607D8B` | The source color (hex) of dynamic colors\* | + +\*Accessible colors in light and dark modes are dynamically generated by +[Dynamic Color in Material Design](https://m3.material.io/styles/color/). For example, if you are to run the web app at the port `8080` with the path `/nextline/` and use the GraphQL API server at `http://localhost:5000/graphql` as the name `API 1`, you can do so with the following command. diff --git a/docker/config.json b/docker/config.json index 71c2296..06ac627 100644 --- a/docker/config.json +++ b/docker/config.json @@ -1,4 +1,5 @@ { "apiName": "localhost", - "apiHttp": "http://localhost:8000" + "apiHttp": "http://localhost:8000", + "seedColor": "#607D8B" } diff --git a/public/config.json b/public/config.json index 71c2296..06ac627 100644 --- a/public/config.json +++ b/public/config.json @@ -1,4 +1,5 @@ { "apiName": "localhost", - "apiHttp": "http://localhost:8000" + "apiHttp": "http://localhost:8000", + "seedColor": "#607D8B" } diff --git a/src/app/AppMain.vue b/src/app/AppMain.vue index 6a1c30f..9f65cc8 100644 --- a/src/app/AppMain.vue +++ b/src/app/AppMain.vue @@ -38,9 +38,11 @@ stay hidden. --> diff --git a/src/utils/config/config.ts b/src/utils/config/config.ts index 8ce5469..bc661da 100644 --- a/src/utils/config/config.ts +++ b/src/utils/config/config.ts @@ -2,6 +2,7 @@ export interface Config { appName: string; apiName: string; apiHttp: string; + seedColor: string; } export const defaultConfig = { @@ -18,4 +19,8 @@ export function validateConfig(config: Config) { if (typeof config.apiHttp !== "string") throw Error("apiHttp is not string"); if (config?.apiHttp === "") throw Error("apiHttp is empty"); + + if (typeof config.seedColor !== "string") throw Error("seedColor is not string"); + if (!/^#([0-9A-F]{3}|[0-9A-F]{6}|[0-9A-F]{8})$/i.test(config.seedColor)) + throw Error(`seedColor is not a valid hex color: ${config.seedColor}`); } diff --git a/src/utils/config/index.ts b/src/utils/config/index.ts index c4ee90f..5be54db 100644 --- a/src/utils/config/index.ts +++ b/src/utils/config/index.ts @@ -19,8 +19,9 @@ export const useConfig = () => useConfigT(); * Provide the config to the child components. * In the child components, use `useConfig` to get the config. */ -export const useProvideConfig = (config: MaybeRefOrGetter) => - useProvideConfigT(config); +export const useProvideConfig = ( + config: Exclude, null> +) => useProvideConfigT(config); /** * Read the config from the config file. diff --git a/src/utils/config/provide-config.ts b/src/utils/config/provide-config.ts index 7a2b987..f0e6bff 100644 --- a/src/utils/config/provide-config.ts +++ b/src/utils/config/provide-config.ts @@ -2,15 +2,36 @@ import { ref, provide, watchEffect, toValue } from "vue"; import type { MaybeRefOrGetter, InjectionKey, Ref } from "vue"; import { injectionKey } from "./key"; - /** - * Provide the config of type `T` to the child components. - * In the child components, use `useConfig` to get the config. + * Provides a configuration of type `T` to child components. + * + * This function creates a reactive reference to the config and provides it + * to child components using Vue's provide/inject system. + * + * @template T The type of the configuration object + * @param The configuration, which can be: + * - A value of type T + * - A Ref + * - A getter function returning T or null + * + * @remarks + * In child components, use `useConfig` to retrieve this provided config. + * The config will only be provided once it has a non-null value. + * Subsequent updates to the config (including to undefined) will be reflected + * in the provided reference, but will not trigger a new provide. */ -export function useProvideConfigT(config: MaybeRefOrGetter) { - const configRef = ref(toValue(config)) as Ref; +export function useProvideConfigT( + config: Exclude, null> +) { + const configRef = ref() as Ref; + let provided = false; + watchEffect(() => { - configRef.value = toValue(config); + const value = toValue(config); + if (value === null) return; + configRef.value = value; + if (provided) return; + provide(injectionKey as InjectionKey>, configRef); + provided = true; }); - provide(injectionKey as InjectionKey>, configRef); }