All Downloads are FREE. Search and download functionalities are using the official Maven repository.

utils.theme.ts Maven / Gradle / Ivy

import { LocaleConfig } from 'contexts/LocaleContext';
import merge from 'deepmerge';
import globalTheme from 'theme/theme';
import {
    GearsTheme, GearsThemeConfig, GearsThemeKey, GearsThemeVariables, PartialGearsMuiTheme,
    ThemeIcons
} from 'types/theming';
import Types from 'types/types';

import { createTheme, darken, GearsMuiTheme, lighten, Theme } from '@mui/material';

import { Runtime } from '../helpers/runtime';
import xlritTheme from '../themes/xlrit.json';

const localThemes = loadThemes()

class ThemeBuilder {
  themes: GearsTheme[]
  locale: LocaleConfig 
  config: GearsThemeConfig | undefined
  
  constructor(locale: LocaleConfig, themeConfig: GearsThemeConfig | undefined) {
    this.themes = []
    this.locale = locale
    this.config = themeConfig
  }

  createThemes(): ThemeBuilder {
    this.themes = [...(this.config?.themes || []), ...localThemes]
    return this
  }

  addIcons(): ThemeBuilder {
    this.themes = this.themes.map((theme: GearsTheme) => {
      const themeIcons  = theme.icons || {}
      const globalIcons = this.config?.icons || {}
      const icons       = merge.all([globalIcons, themeIcons]) as ThemeIcons

      if (Object.keys(icons).length > 0)
        return { ...theme, icons: icons }
      else
        return theme
    })

    return this
  }

  addStyling(): ThemeBuilder {
    this.themes = toMuiThemes(this.locale, this.themes)
    
    return this
  }


  getThemes(): GearsTheme[] {
    return this.themes
  }

};

export function createCustomThemes(locale: LocaleConfig, themeConfig: GearsThemeConfig | undefined): GearsTheme[] {
  return (new ThemeBuilder(locale, themeConfig))
    .createThemes()
    .addIcons()
    .addStyling()
    .getThemes()
}

function toMuiThemes(locale: LocaleConfig, themes: GearsTheme[]): GearsTheme[] {
  const defaultTheme = createDefaultTheme(locale)

  return themes.map(theme => {
    const logoTheme    = toMuiLogoTheme(theme)
    const variables    = theme?.variables || {}
    const userTheme    = toResolvedUserTheme(theme.theme, variables)
    const colorTheme   = toColorTheme(defaultTheme, userTheme)
    const headingTheme = toHeadingTheme(colorTheme, userTheme)
    const muiTheme     = merge.all([defaultTheme, colorTheme, headingTheme, userTheme, logoTheme]) as GearsMuiTheme

    return {
      ...theme,
      theme: muiTheme
    }
  })
}


export function createDefaultTheme(locale: LocaleConfig): GearsMuiTheme {
  const theme = toLocalizedMuiTheme(globalTheme, locale)

  const amendment = { 
    palette: {
      contrast: theme.palette.grey[100],
      background: {
        default: "#ffffff",
      }
    },
    components: {
      title: {
        fontWeight: 'bold',
        fontStyle: 'italic',
        fontSize: 28
      }, 
      banner : {
        backgroundColor : theme.palette.primary.main,
        color : "#ffffff",
        elevation: 15,
        height: "48px"
      },
      toolbar : {
        paddingRight: "24px",
        height: "48px !important",
        minHeight: "0 !important"
      },
      sidebar: {
        width: "256px",
        //background: "#f1f1f1"
        backgroundColor: "#fbfafd"
      },
      badge : {
        color : theme.palette.secondary.main,
        backgroundColor : theme.palette.secondary.light
      },
      logo: {},
      heading : {
        color: "#203e60"
      }
    }
  }

  return merge.all([theme, amendment]) as GearsMuiTheme
}

export const toLocalizedMuiTheme = (theme: Theme, locale: LocaleConfig): Theme => {
  // @ts-ignore
  return createTheme(theme, locale.muiLocale) 
}

export function isGearsThemes(themes: any) {
  return themes && Types.isObject(themes) &&
    themes.hasOwnProperty('themes') &&
    Array.isArray(themes.themes)
}

export function isGearsTheme(theme: any) {
  return theme && Types.isObject(theme) &&
    theme.hasOwnProperty('theme') &&
    theme.hasOwnProperty('key')   &&
    theme.hasOwnProperty('label') &&
    theme.hasOwnProperty('logo')
}

function toMuiLogoTheme(theme: GearsTheme) {
  const adjustSrc = (src: string | undefined,filename: string | undefined) => src ? src : filename ? Runtime.logo(filename) : undefined

  const logoSmall = theme?.logoSmall
    ? { logoSmall: {
          ...theme?.logoSmall, 
          src: adjustSrc(theme?.logoSmall?.src, theme?.logoSmall?.filename)
        }
      }
    : undefined

  const logo = { logo: {
    ...theme?.logo,
    src: adjustSrc(theme?.logo?.src, theme?.logo?.filename)
    }
  }

  if (!logo.logo.src)
    console.error("No logo url provided for theme: %o", theme?.key)

  return {
    "components": {
      ...logo,
      ...logoSmall
    }
  }
}

// a resolver for when variables are used in the theme, with the format: $variable_name
function toResolvedUserTheme(theme: PartialGearsMuiTheme, variables: GearsThemeVariables): PartialGearsMuiTheme {
  function resolve(value: any): any {
    switch (true) {
      case Types.isObject(value):
        const entries = Object.entries(value as any)
          .map(([key, value]) => [key, resolve(value)])
        return Object.fromEntries(entries)

      case typeof value == "string" && value.startsWith("$"):
        const variable = value.substring(1)
        if (variables.hasOwnProperty(variable))
          return variables[variable]
        else {
          console.error("Could not resolve theme variable: %o", variable)
          return value
        }

      case Array.isArray(value):
        return value.map((value: any) => resolve(value))

      default: 
        return value
    }
  }

  return resolve(theme)
}

function toHeadingTheme(colorTheme: PartialGearsMuiTheme, userTheme: PartialGearsMuiTheme): PartialGearsMuiTheme {
  const primary             = colorTheme?.palette?.primary
  const defaultHeadingColor = primary?.dark || "black"
  const defaultHeadingTheme = {
    components: {
      heading: {
        color: defaultHeadingColor
      }
    }
  }

  const userHeadingTheme = {
    components: {
      heading: userTheme.components?.heading || {}
    }
  }

  const headingTheme     = merge.all([defaultHeadingTheme, userHeadingTheme]) as GearsMuiTheme
  const headingStyle     = headingTheme.components.heading

  return {
    ...headingTheme,
    typography: {
      h1: headingStyle,
      h2: headingStyle,
      h3: headingStyle,
      h4: headingStyle,
      h5: headingStyle,
      h6: headingStyle
    }
  }
}

// This results in the primary and secondary color palettes, in an MUI theme structure
function toColorTheme(defaultTheme: GearsMuiTheme, userTheme: PartialGearsMuiTheme): PartialGearsMuiTheme {
  return merge.all([
    toPaletteColorTheme(defaultTheme, userTheme, 'primary'), 
    toPaletteColorTheme(defaultTheme, userTheme, 'secondary'),
  ])
}

// This results in a Mui theme color palette, where it's type is 'primary' or 'secondary'
function toPaletteColorTheme(defaultTheme: GearsMuiTheme, theme: PartialGearsMuiTheme, attribute: string): PartialGearsMuiTheme {
  // @ts-ignore
  const getColor = (theme: PartialGearsMuiTheme) => theme?.palette?.[attribute]?.main || theme?.components?.[attribute]
  // @ts-ignore
  const hasColor = (theme: PartialGearsMuiTheme) => !!theme?.palette?.[attribute]?.main 

  if (hasColor(theme)) {
    const color        = getColor(theme)
    const defaultColor = {
      palette: { 
        [attribute]: {
          light: lighten(color, 0.7),
          main: color,
          dark: darken(color, 0.7)
        }
      }
    }
    const userColor = {
      palette: {
        // @ts-ignore
        [attribute]: theme?.palette?.[attribute] || {}
      }
    }

    return merge.all([defaultColor, userColor]) 
  } else {
    return {
      palette: {
        // @ts-ignore
        [attribute]: defaultTheme.palette[attribute] 
      }
    }
  }
}


export function selectThemeKey(themes: GearsTheme[], themeKey: GearsThemeKey, gearsTheme: GearsThemeConfig): string {
  if (hasTheme(themes, themeKey))
    return themeKey

  return 'default'
}


export function hasTheme(themes: GearsTheme[], key: GearsThemeKey) {
  return key && themes.some(theme => theme.key == key)
}

export function selectTheme(themes: GearsTheme[], key: GearsThemeKey): GearsTheme {
  if (!Array.isArray(themes) || themes.length == 0)
    throw new Error("Could not load any theme")

  const theme = themes.find(theme => theme.key == key)
  if (!theme) 
    throw new Error("Could not load theme: " + key)

  return theme
}

function loadThemes() {
  //const requireContext = require.context('../themes', false, /\.json$/);
  //const themes = [];
  //requireContext.keys().forEach((key) => {
  //  const obj = requireContext(key);
  //  themes.push(obj)
  //});
  //return themes;

  return [xlritTheme]
}


export const checkboxCellStyle = {
  width: "1%",
  padding: 0,
  paddingLeft: "6px"
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy