diff --git a/apps/web-graphpolaris/src/app/app.tsx b/apps/web-graphpolaris/src/app/app.tsx index 7230795259a44bfb926a5cb496e3639ba73e264c..c33f6c28dfe1585435a0388de4f5f5e9c1a1dac4 100644 --- a/apps/web-graphpolaris/src/app/app.tsx +++ b/apps/web-graphpolaris/src/app/app.tsx @@ -1,42 +1,20 @@ // eslint-disable-next-line @typescript-eslint/no-unused-vars -import * as React from 'react'; -import { createTheme, ThemeProvider } from '@mui/material/styles'; import { assignNewGraphQueryResult, + changeDataPointColors, useAppDispatch, - useColorPalette, } from '@graphpolaris/shared/data-access/store'; import GridLayout from 'react-grid-layout'; import Panel from '../components/panels/panel'; import { RawJSONVis } from '../components/visualisations/rawjsonvis/rawjsonvis'; import SemanticSubstrates from '../components/visualisations/semanticsubstrates/semanticsubstrates'; -import { Theme, Palette } from '@mui/material/styles'; - -declare module '@mui/material/styles' { - interface PaletteOptions { - dataPointColors: React.CSSProperties['color'][]; - } -} +import { OurThemeProvider } from '@graphpolaris/shared/data-access/theme'; export function App() { const dispatch = useAppDispatch(); - const colorPalette = useColorPalette(); - - // Create a new theme when our custom color palette in redux changed - const theme = React.useMemo( - () => - // Mapping our palette slice data to the mui theme could be a usecase - createTheme({ - palette: { - primary: { main: colorPalette.primary.main }, - dataPointColors: colorPalette.dataPointColors, - }, - }), - [colorPalette] - ); return ( - <ThemeProvider theme={theme}> + <OurThemeProvider> <GridLayout className="layout" cols={10} @@ -98,7 +76,7 @@ export function App() { <Panel content="History Channel" color="purple"></Panel> </div> </GridLayout> - </ThemeProvider> + </OurThemeProvider> ); } diff --git a/apps/web-graphpolaris/src/components/panels/panel.tsx b/apps/web-graphpolaris/src/components/panels/panel.tsx index bf43cca74d91330d0c8a92d807445b21c0035a8d..5189e4d410f05b865992f6a272fd371c550f91e3 100644 --- a/apps/web-graphpolaris/src/components/panels/panel.tsx +++ b/apps/web-graphpolaris/src/components/panels/panel.tsx @@ -1,5 +1,6 @@ import styled from 'styled-components'; import { ReactNode } from 'react'; +import { useTheme } from '@mui/material/styles'; interface Props { content: string; color: string; @@ -27,10 +28,14 @@ const Content = styled.div` `; const Panel = (props: Props) => { + const theme = useTheme(); + return ( <Wrapper color={props.color}> <Content> - <h1>{props.content}</h1> + <h1 style={{ backgroundColor: theme.palette.dataPointColors[1] }}> + {props.content} + </h1> {props.children} </Content> </Wrapper> diff --git a/apps/web-graphpolaris/src/components/visualisations/semanticsubstrates/semanticsubstrates.tsx b/apps/web-graphpolaris/src/components/visualisations/semanticsubstrates/semanticsubstrates.tsx index 9c2609a70cc189898b711c6d5fece7e4b1308a02..04a068232a2697841d2e12361904999a8da2f036 100644 --- a/apps/web-graphpolaris/src/components/visualisations/semanticsubstrates/semanticsubstrates.tsx +++ b/apps/web-graphpolaris/src/components/visualisations/semanticsubstrates/semanticsubstrates.tsx @@ -1,10 +1,9 @@ -import { styled, Theme } from '@mui/material/styles'; +import { styled, Theme, useTheme } from '@mui/material/styles'; import Box from '@mui/material/Box'; import { useDispatch } from 'react-redux'; import { changeDataPointColors, changePrimary, - useColorPalette, } from '@graphpolaris/shared/data-access/store'; // Basically a styled-component @@ -14,7 +13,7 @@ const Div = styled('div')(({ theme }) => ({ const SemanticSubstrates = () => { const dispatch = useDispatch(); - const colorPalette = useColorPalette(); + const theme = useTheme(); return ( <> @@ -24,7 +23,7 @@ const SemanticSubstrates = () => { <Box sx={{ // Using our custom palette dataPointColors property, cast theme to any because Theme doesn't know of this custom property - backgroundColor: (theme: any) => theme.palette.dataPointColors[0], + backgroundColor: (theme: Theme) => theme.palette.dataPointColors[0], }} > <h1>semantic substrates</h1> @@ -36,21 +35,36 @@ const SemanticSubstrates = () => { type="color" id="head" name="head" - value={colorPalette.primary.main} + value={theme.palette.primary.main} /> <input onChange={(v) => dispatch( changeDataPointColors([ v.currentTarget.value, - ...colorPalette.dataPointColors.slice(1), + ...theme.palette.dataPointColors.slice(1), ]) ) } type="color" id="head" name="head" - value={colorPalette.dataPointColors[0]} + value={theme.palette.dataPointColors[0]} + /> + <input + onChange={(v) => + dispatch( + changeDataPointColors([ + theme.palette.dataPointColors[0], + v.currentTarget.value, + ...theme.palette.dataPointColors.slice(2), + ]) + ) + } + type="color" + id="head" + name="head" + value={theme.palette.dataPointColors[1]} /> </> ); diff --git a/libs/shared/data-access/store/src/index.ts b/libs/shared/data-access/store/src/index.ts index b798d7f530615da227699bbe3004696ab390cc83..d5a3cbf88459585818d0a4ff2c3e3ad2bd9ade65 100644 --- a/libs/shared/data-access/store/src/index.ts +++ b/libs/shared/data-access/store/src/index.ts @@ -6,7 +6,15 @@ export { selectGraphQueryResultNodes, assignNewGraphQueryResult, } from './lib/graphQueryResultSlice'; -export { changePrimary, changeDataPointColors } from './lib/colorPaletteSlice'; +export { + changePrimary, + changeDataPointColors, + selectColorPaletteConfig, +} from './lib/colorPaletteConfigSlice'; // Exported types export type { Node, Edge, GraphQueryResult } from './lib/graphQueryResultSlice'; +export type { + ColorPaletteConfig, + ExtraColorsForMui5, +} from './lib/colorPaletteConfigSlice'; diff --git a/libs/shared/data-access/store/src/lib/colorPaletteSlice.ts b/libs/shared/data-access/store/src/lib/colorPaletteConfigSlice.ts similarity index 50% rename from libs/shared/data-access/store/src/lib/colorPaletteSlice.ts rename to libs/shared/data-access/store/src/lib/colorPaletteConfigSlice.ts index 753a4b7d6f8fdcf29cc9ebced56fef2b03ba3dc2..b311ccee957853b4e5b1a57b7b80f8d2f15c6d13 100644 --- a/libs/shared/data-access/store/src/lib/colorPaletteSlice.ts +++ b/libs/shared/data-access/store/src/lib/colorPaletteConfigSlice.ts @@ -1,17 +1,30 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import type { RootState } from './store'; -// This looks very much like the mui Palette, +/** Extra properties that are not present in the default PaletteOptions of mui. */ +export interface ExtraColorsForMui5 { + /** Colors that can be used for data visualisation, e.g. nodes, edges */ + dataPointColors: string[]; +} + +/** Our custom color palette config. */ +export interface ColorPaletteConfig extends ExtraColorsForMui5 { + primary: { + main: string; + }; +} + +// This looks very much like the mui Palette interface, // But we don't reference that type directly here to stay decoupled -export const initialState = { +export const initialState: ColorPaletteConfig = { dataPointColors: ['#ff0000', '#00ff00', '#0000ff'], primary: { main: '#9999ff', }, }; -export const colorPaletteSlice = createSlice({ - name: 'colorPalette', +export const colorPaletteConfigSlice = createSlice({ + name: 'colorPaletteConfig', // `createSlice` will infer the state type from the `initialState` argument initialState, reducers: { @@ -25,9 +38,10 @@ export const colorPaletteSlice = createSlice({ }); export const { changePrimary, changeDataPointColors } = - colorPaletteSlice.actions; + colorPaletteConfigSlice.actions; // Select the schema and convert it to a graphology object -export const selectColorPalette = (state: RootState) => state.colorPalette; +export const selectColorPaletteConfig = (state: RootState) => + state.colorPaletteConfig; -export default colorPaletteSlice.reducer; +export default colorPaletteConfigSlice.reducer; diff --git a/libs/shared/data-access/store/src/lib/hooks.ts b/libs/shared/data-access/store/src/lib/hooks.ts index cc8af8bf1caacfea364e4a878b4c80adf5cf6879..df0faef46e155463bea444790b3ba91dcc076b76 100644 --- a/libs/shared/data-access/store/src/lib/hooks.ts +++ b/libs/shared/data-access/store/src/lib/hooks.ts @@ -1,5 +1,4 @@ import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; -import { selectColorPalette } from './colorPaletteSlice'; import { selectGraphQueryResult } from './graphQueryResultSlice'; import { selectSchema } from './schemaSlice'; import type { RootState, AppDispatch } from './store'; @@ -13,5 +12,3 @@ export const useGraphQueryResult = () => useAppSelector(selectGraphQueryResult); // Gives the schema form the store (as a graphology object) export const useSchema = () => useAppSelector(selectSchema); - -export const useColorPalette = () => useAppSelector(selectColorPalette); diff --git a/libs/shared/data-access/store/src/lib/store.ts b/libs/shared/data-access/store/src/lib/store.ts index 3ecb3ec20ebd75c7412904f46c192d23fb217538..7198337bde3e8b98b11d6336552743c71d8e413a 100644 --- a/libs/shared/data-access/store/src/lib/store.ts +++ b/libs/shared/data-access/store/src/lib/store.ts @@ -1,5 +1,5 @@ import { configureStore } from '@reduxjs/toolkit'; -import colorPaletteSlice from './colorPaletteSlice'; +import colorPaletteConfigSlice from './colorPaletteConfigSlice'; import graphQueryResultSlice from './graphQueryResultSlice'; import schemaSlice from './schemaSlice'; @@ -7,7 +7,7 @@ export const store = configureStore({ reducer: { graphQueryResult: graphQueryResultSlice, schema: schemaSlice, - colorPalette: colorPaletteSlice, + colorPaletteConfig: colorPaletteConfigSlice, }, }); diff --git a/libs/shared/data-access/theme/.babelrc b/libs/shared/data-access/theme/.babelrc new file mode 100644 index 0000000000000000000000000000000000000000..ccae900be42788285eb6e1b3a2af4b81f56f14fe --- /dev/null +++ b/libs/shared/data-access/theme/.babelrc @@ -0,0 +1,12 @@ +{ + "presets": [ + [ + "@nrwl/react/babel", + { + "runtime": "automatic", + "useBuiltIns": "usage" + } + ] + ], + "plugins": [] +} diff --git a/libs/shared/data-access/theme/.eslintrc.json b/libs/shared/data-access/theme/.eslintrc.json new file mode 100644 index 0000000000000000000000000000000000000000..3cd64217595c25dc61c2fd055ec4dbe21195a3e3 --- /dev/null +++ b/libs/shared/data-access/theme/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["plugin:@nrwl/nx/react", "../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/libs/shared/data-access/theme/README.md b/libs/shared/data-access/theme/README.md new file mode 100644 index 0000000000000000000000000000000000000000..bbf79085703dc9357fa05de431cfbba189046124 --- /dev/null +++ b/libs/shared/data-access/theme/README.md @@ -0,0 +1,7 @@ +# shared-data-access-theme + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test shared-data-access-theme` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/libs/shared/data-access/theme/jest.config.js b/libs/shared/data-access/theme/jest.config.js new file mode 100644 index 0000000000000000000000000000000000000000..0fca299e2f20e17cf1b441337ee26e44e06b25c5 --- /dev/null +++ b/libs/shared/data-access/theme/jest.config.js @@ -0,0 +1,9 @@ +module.exports = { + displayName: 'shared-data-access-theme', + preset: '../../../../jest.preset.js', + transform: { + '^.+\\.[tj]sx?$': 'babel-jest', + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + coverageDirectory: '../../../../coverage/libs/shared/data-access/theme', +}; diff --git a/libs/shared/data-access/theme/project.json b/libs/shared/data-access/theme/project.json new file mode 100644 index 0000000000000000000000000000000000000000..18f65df2f249ed94da565e21e9b9d9223f6307cd --- /dev/null +++ b/libs/shared/data-access/theme/project.json @@ -0,0 +1,25 @@ +{ + "root": "libs/shared/data-access/theme", + "sourceRoot": "libs/shared/data-access/theme/src", + "projectType": "library", + "tags": [], + "targets": { + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": [ + "libs/shared/data-access/theme/**/*.{ts,tsx,js,jsx}" + ] + } + }, + "test": { + "executor": "@nrwl/jest:jest", + "outputs": ["coverage/libs/shared/data-access/theme"], + "options": { + "jestConfig": "libs/shared/data-access/theme/jest.config.js", + "passWithNoTests": true + } + } + } +} diff --git a/libs/shared/data-access/theme/src/index.ts b/libs/shared/data-access/theme/src/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..fae1f6948cf3d9f4fd40ea0c309bf39ea9f07f89 --- /dev/null +++ b/libs/shared/data-access/theme/src/index.ts @@ -0,0 +1 @@ +export * from './lib/ourThemeProvider'; diff --git a/libs/shared/data-access/theme/src/lib/mapColorsConfigToMuiTheme.ts b/libs/shared/data-access/theme/src/lib/mapColorsConfigToMuiTheme.ts new file mode 100644 index 0000000000000000000000000000000000000000..f94556c8842f61c8588bf38aa89295ed3920bc2b --- /dev/null +++ b/libs/shared/data-access/theme/src/lib/mapColorsConfigToMuiTheme.ts @@ -0,0 +1,14 @@ +import { ColorPaletteConfig } from '@graphpolaris/shared/data-access/store'; +import { ThemeOptions } from '@mui/material/styles'; + +/** Maps our color palette config (stored in redux) to the mui 5 theme format */ +export default function MapColorsConfigToMuiTheme( + colorsConfig: ColorPaletteConfig +): ThemeOptions { + return { + palette: { + primary: { main: colorsConfig.primary.main }, + dataPointColors: colorsConfig.dataPointColors, + }, + }; +} diff --git a/libs/shared/data-access/theme/src/lib/ourThemeProvider.tsx b/libs/shared/data-access/theme/src/lib/ourThemeProvider.tsx new file mode 100644 index 0000000000000000000000000000000000000000..04f36156807eafc3dcf4d4f569da172f00792d38 --- /dev/null +++ b/libs/shared/data-access/theme/src/lib/ourThemeProvider.tsx @@ -0,0 +1,35 @@ +import React, { ReactNode } from 'react'; +import { + ExtraColorsForMui5, + selectColorPaletteConfig, + useAppSelector, +} from '@graphpolaris/shared/data-access/store'; +import { createTheme, ThemeProvider } from '@mui/material/styles'; +import MapColorsConfigToMuiTheme from './mapColorsConfigToMuiTheme'; + +// Add custom theme variables to the mui theme types +declare module '@mui/material/styles' { + interface Palette extends ExtraColorsForMui5 {} // eslint-disable-line + interface Theme { + palette: Palette; + } + interface PaletteOptions extends Partial<ExtraColorsForMui5> {} // eslint-disable-line + interface ThemeOptions { + palette?: PaletteOptions; + } +} + +export function OurThemeProvider({ children }: { children: ReactNode }) { + const colorPaletteConfig = useAppSelector(selectColorPaletteConfig); + + // Create a new theme when our custom color palette in redux changed + const theme = React.useMemo( + () => + // Map our color palette config (stored in redux) to the mui 5 format + createTheme(MapColorsConfigToMuiTheme(colorPaletteConfig)), + [colorPaletteConfig] + ); + + return <ThemeProvider theme={theme}>{children}</ThemeProvider>; +} +export default OurThemeProvider; diff --git a/libs/shared/data-access/theme/tsconfig.json b/libs/shared/data-access/theme/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..3512bf7afeea1bf2e5fdb72b7dea8114f6c9fbe1 --- /dev/null +++ b/libs/shared/data-access/theme/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "jsx": "react-jsx", + "allowJs": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/libs/shared/data-access/theme/tsconfig.lib.json b/libs/shared/data-access/theme/tsconfig.lib.json new file mode 100644 index 0000000000000000000000000000000000000000..1ab8e06a7111b168e84debca80bb4ac82f72f2aa --- /dev/null +++ b/libs/shared/data-access/theme/tsconfig.lib.json @@ -0,0 +1,22 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../../dist/out-tsc", + "types": ["node"] + }, + "files": [ + "../../../../node_modules/@nrwl/react/typings/cssmodule.d.ts", + "../../../../node_modules/@nrwl/react/typings/image.d.ts" + ], + "exclude": [ + "**/*.spec.ts", + "**/*.test.ts", + "**/*.spec.tsx", + "**/*.test.tsx", + "**/*.spec.js", + "**/*.test.js", + "**/*.spec.jsx", + "**/*.test.jsx" + ], + "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] +} diff --git a/libs/shared/data-access/theme/tsconfig.spec.json b/libs/shared/data-access/theme/tsconfig.spec.json new file mode 100644 index 0000000000000000000000000000000000000000..315a5b0bbebaca96617a8dd5353901287ebd8e68 --- /dev/null +++ b/libs/shared/data-access/theme/tsconfig.spec.json @@ -0,0 +1,19 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "**/*.test.ts", + "**/*.spec.ts", + "**/*.test.tsx", + "**/*.spec.tsx", + "**/*.test.js", + "**/*.spec.js", + "**/*.test.jsx", + "**/*.spec.jsx", + "**/*.d.ts" + ] +} diff --git a/tsconfig.base.json b/tsconfig.base.json index 5a2caf532de87019c51c475c16870bedb9fb4e9e..03fe56931d7eb9a85b6eb8aed1e97d26df900908 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -17,6 +17,9 @@ "paths": { "@graphpolaris/shared/data-access/store": [ "libs/shared/data-access/store/src/index.ts" + ], + "@graphpolaris/shared/data-access/theme": [ + "libs/shared/data-access/theme/src/index.ts" ] } }, diff --git a/workspace.json b/workspace.json index 1a59e05a31f38abe3f9085968e95163374b70590..93d09187b3a59f7fce4dcf30de996a7fd802b329 100644 --- a/workspace.json +++ b/workspace.json @@ -2,6 +2,7 @@ "version": 2, "projects": { "shared-data-access-store": "libs/shared/data-access/store", + "shared-data-access-theme": "libs/shared/data-access/theme", "web-graphpolaris": "apps/web-graphpolaris", "web-graphpolaris-e2e": "apps/web-graphpolaris-e2e" }