Skip to content

Commit

Permalink
Merge pull request #69 from simonsobs/dev
Browse files Browse the repository at this point in the history
Make the seed color of Dynamic Color configurable
  • Loading branch information
TaiSakuma authored Aug 1, 2024
2 parents fb9cd58 + 4fef786 commit 047d113
Show file tree
Hide file tree
Showing 9 changed files with 63 additions and 49 deletions.
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion docker/config.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"apiName": "localhost",
"apiHttp": "http://localhost:8000"
"apiHttp": "http://localhost:8000",
"seedColor": "#607D8B"
}
3 changes: 2 additions & 1 deletion public/config.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"apiName": "localhost",
"apiHttp": "http://localhost:8000"
"apiHttp": "http://localhost:8000",
"seedColor": "#607D8B"
}
7 changes: 6 additions & 1 deletion src/app/AppMain.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@ stay hidden.
-->

<script setup lang="ts">
import { computed } from "vue";
import { useDisplay } from "vuetify";
import { useProvideClient } from "@/graphql/urql";
import { useConfig } from "@/utils/config";
import { useColorTheme } from "@/utils/color-theme";
import { useSetTitle } from "./set-title";
import { useDrawer } from "./drawer";
Expand All @@ -49,8 +51,11 @@ import Banner from "./Banner.vue";
import NavigationDrawer from "./NavigationDrawer.vue";
import AppBar from "./AppBar.vue";
const { config } = useConfig();
const sourceColorHex = computed(() => config.value.seedColor)
useProvideClient();
useColorTheme();
useColorTheme(sourceColorHex);
useSetTitle();
const { mobile } = useDisplay();
const { drawer, toggleDrawer } = useDrawer();
Expand Down
12 changes: 5 additions & 7 deletions src/utils/color-theme/color-theme.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ref } from "vue";
import type { MaybeRef } from "vue";

import { useDynamicColors } from "@/utils/dynamic-color";
import {
Expand All @@ -7,14 +8,11 @@ import {
} from "./monaco-editor";
import { useDynamicColorsOnVuetify, useDarkModeOnVuetify } from "./vuetify";

function useSourceColor() {
const sourceColor = ref("#607D8B"); // blue grey
// const sourceColor = ref("#E91E63"); // pink
return { sourceColor };
}
const DEFAULT_SOURCE_COLOR_HEX = "#607D8B"; // blue grey
// const DEFAULT_SOURCE_COLOR_HEX = "#E91E63"; // pink

export function useColorTheme() {
const { sourceColor: sourceColorHex } = useSourceColor();
export function useColorTheme(sourceColorHex?: MaybeRef<string>) {
sourceColorHex = ref(sourceColorHex ?? DEFAULT_SOURCE_COLOR_HEX);

const optionsLight = { sourceColorHex, dark: false };
const optionsDark = { sourceColorHex, dark: true };
Expand Down
26 changes: 3 additions & 23 deletions src/utils/config/ProvideConfig.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,8 @@
/**
* Load config asynchronously and provide it to the child components.
*/
import { ref, watchEffect, nextTick } from "vue";
import type { ComputedRef } from "vue";
import { useLoadConfig } from "@/utils/config";
import { useProvideConfig, Config } from "@/utils/config";
const error = ref<Error>();
const { error: loadError, config } = await useLoadConfig();
watchEffect(() => {
error.value = loadError.value;
});
if (!config.value) throw new Error("Config is null");
useProvideConfig(config as ComputedRef<Config>);
// For test reactivity of loading.
// await new Promise((resolve) => setTimeout(resolve, 1000));
// For test reactivity of error.
// error.value = new Error("Test");
// nextTick(async () => {
// await new Promise((resolve) => setTimeout(resolve, 1000));
// error.value = undefined;
// });
import { useProvideConfig } from "@/utils/config";
const { error, config } = await useLoadConfig();
useProvideConfig(config);
</script>
5 changes: 5 additions & 0 deletions src/utils/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export interface Config {
appName: string;
apiName: string;
apiHttp: string;
seedColor: string;
}

export const defaultConfig = {
Expand All @@ -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}`);
}
5 changes: 3 additions & 2 deletions src/utils/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ export const useConfig = () => useConfigT<Config>();
* Provide the config to the child components.
* In the child components, use `useConfig` to get the config.
*/
export const useProvideConfig = (config: MaybeRefOrGetter<Config>) =>
useProvideConfigT<Config>(config);
export const useProvideConfig = (
config: Exclude<MaybeRefOrGetter<Config | null>, null>
) => useProvideConfigT<Config>(config);

/**
* Read the config from the config file.
Expand Down
35 changes: 28 additions & 7 deletions src/utils/config/provide-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T | null>
* - 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<T>(config: MaybeRefOrGetter<T>) {
const configRef = ref(toValue(config)) as Ref<T>;
export function useProvideConfigT<T>(
config: Exclude<MaybeRefOrGetter<T | null>, null>
) {
const configRef = ref<T>() as Ref<T>;
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<Ref<T>>, configRef);
provided = true;
});
provide(injectionKey as InjectionKey<Ref<T>>, configRef);
}

0 comments on commit 047d113

Please sign in to comment.