From b00d112c665efdf8f80fc04824ebdd247b158e2f Mon Sep 17 00:00:00 2001
From: Sivan Duijn <sivanduijn@gmail.com>
Date: Wed, 9 Feb 2022 11:30:27 +0100
Subject: [PATCH] feat(theme): added colorPaletteConfig slice

---
 apps/web-graphpolaris/src/app/app.tsx         | 30 +++-------------
 .../src/components/panels/panel.tsx           |  7 +++-
 .../semanticsubstrates/semanticsubstrates.tsx | 28 +++++++++++----
 libs/shared/data-access/store/src/index.ts    | 10 +++++-
 ...tteSlice.ts => colorPaletteConfigSlice.ts} | 28 +++++++++++----
 .../shared/data-access/store/src/lib/hooks.ts |  3 --
 .../shared/data-access/store/src/lib/store.ts |  4 +--
 libs/shared/data-access/theme/.babelrc        | 12 +++++++
 libs/shared/data-access/theme/.eslintrc.json  | 18 ++++++++++
 libs/shared/data-access/theme/README.md       |  7 ++++
 libs/shared/data-access/theme/jest.config.js  |  9 +++++
 libs/shared/data-access/theme/project.json    | 25 +++++++++++++
 libs/shared/data-access/theme/src/index.ts    |  1 +
 .../src/lib/mapColorsConfigToMuiTheme.ts      | 14 ++++++++
 .../theme/src/lib/ourThemeProvider.tsx        | 35 +++++++++++++++++++
 libs/shared/data-access/theme/tsconfig.json   | 25 +++++++++++++
 .../data-access/theme/tsconfig.lib.json       | 22 ++++++++++++
 .../data-access/theme/tsconfig.spec.json      | 19 ++++++++++
 tsconfig.base.json                            |  3 ++
 workspace.json                                |  1 +
 20 files changed, 254 insertions(+), 47 deletions(-)
 rename libs/shared/data-access/store/src/lib/{colorPaletteSlice.ts => colorPaletteConfigSlice.ts} (50%)
 create mode 100644 libs/shared/data-access/theme/.babelrc
 create mode 100644 libs/shared/data-access/theme/.eslintrc.json
 create mode 100644 libs/shared/data-access/theme/README.md
 create mode 100644 libs/shared/data-access/theme/jest.config.js
 create mode 100644 libs/shared/data-access/theme/project.json
 create mode 100644 libs/shared/data-access/theme/src/index.ts
 create mode 100644 libs/shared/data-access/theme/src/lib/mapColorsConfigToMuiTheme.ts
 create mode 100644 libs/shared/data-access/theme/src/lib/ourThemeProvider.tsx
 create mode 100644 libs/shared/data-access/theme/tsconfig.json
 create mode 100644 libs/shared/data-access/theme/tsconfig.lib.json
 create mode 100644 libs/shared/data-access/theme/tsconfig.spec.json

diff --git a/apps/web-graphpolaris/src/app/app.tsx b/apps/web-graphpolaris/src/app/app.tsx
index 723079525..c33f6c28d 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 bf43cca74..5189e4d41 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 9c2609a70..04a068232 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 b798d7f53..d5a3cbf88 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 753a4b7d6..b311ccee9 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 cc8af8bf1..df0faef46 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 3ecb3ec20..7198337bd 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 000000000..ccae900be
--- /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 000000000..3cd642175
--- /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 000000000..bbf790857
--- /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 000000000..0fca299e2
--- /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 000000000..18f65df2f
--- /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 000000000..fae1f6948
--- /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 000000000..f94556c88
--- /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 000000000..04f361568
--- /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 000000000..3512bf7af
--- /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 000000000..1ab8e06a7
--- /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 000000000..315a5b0bb
--- /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 5a2caf532..03fe56931 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 1a59e05a3..93d09187b 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"
   }
-- 
GitLab