Frontend GUI

Theming

The theme can be used to adapt the configurator to your CI to meet the design aspects. You can change the colors, the typography and much more. Under the hood, the configurator uses the Material-UI.

Theme provider

The ThemeProvider is located in the following directory:

src/Custom/App/Screens/Configurator/ThemeProvider.js

Note

This is optional and only needs to be done if the theme needs to be customized, the configurator is delivered with a default theme. If only the Theme Highlight Color, Theme Font or Client Logo needs to be adjusted this can be done in the adminarea Theme.

ThemeProvider structure

A theme provider always consists of a customTheme and an overrideTheme. These two definitions are passed to the ConnectedThemeProvider.

import * as React from 'react';
import ConnectedThemeProvider from '@configuratorware/configurator-frontendgui/App/Screens/Configurator/ThemeProvider';
import merge from 'lodash/merge';

const customTheme = {
};

const overrideTheme = (theme) => {
    merge(theme.overrides, {
    })
};

export default ({ children }) => <ConnectedThemeProvider
                                    customTheme={customTheme}
                                    withTheme={overrideTheme}>
                                        {children}
                                 </ConnectedThemeProvider>;

To use the ThemeProvider, it must be passed to the ConnectedConfiguratorScreen in the Screen.js.

import * as React from 'react';
import CustomThemeProvider from './ThemeProvider';
import { ConnectedConfiguratorScreen } from '@configuratorware/configurator-frontendgui/App/Screens/Configurator/Screen';

export default props => (
    <ConnectedConfiguratorScreen
        {...props}
        ThemeProvider={CustomThemeProvider}
    />
);

To use the ConnectedConfiguratorScreen, it must be passed to the src/Custom/CustomApplicationConfiguration.js:

import ConnectedConfiguratorScreen from '../Custom/App/Screens/Screen';

const customApplicationConfiguration = {
    //...

    const customApplicationConfiguration = {
    screens: {
        Configurator: ConnectedConfiguratorScreen,
    },
};
customTheme

In the customTheme, general stylings such as colors, fonts, etc. can be defined. This can be done either globally or separately for each rendered component. In the following example are global colors defined within the palette node:

const customTheme = {
    palette: {
        primary: {
            light: '#ffffff',
            main: '#005091',
            dark: '#009166',
        },
        secondary: {
            main: '#d9d9d9',
        }
    }
}

To override styling of components, this must be done inside the override node. In the following example we change the font color in the variant selection (drop down in the header):

const customTheme = {
    overrides: {
        VariantChooser: {
            VariantList: {
                '& span': {
                    color: '#ff0000'
                }
            }
        }
    }
}
../_images/dd_normal.PNG ../_images/dd_red.PNG

Note

The configurator components can be found in the node_modules, in the package: redhotmagma-configurator-frontendgui, under src/App/Screens/Configurator/Components. Each component contains defined classes, in const styles and all these classes can be adjusted in the custom theme.

overwriteTheme

In the overwriteTheme you can make stylings, which are not available initially when setting the customTheme. An example would be to set stylings dependent on CSS breakpoints to be able to adjust the styling for different display sizes.

import merge from 'lodash/merge';

const overrideTheme = (theme) => {
    merge(theme.overrides, {
        DefaultLayout: {
            visualization: {
                [theme.breakpoints.only('xs')]: {
                    top: 37,
                    bottom: 51,
                    height: 500,
                }
            }
        }
    }
}

Customizing

Find the configuration file under: src/Custom/CustomApplicationConfiguration.js. It contains several const to customize the configurator. If you do not have your own custom project and are using the bundled version of the frontend, you can customize all of these settings in /public/configuration.js.

const prod = process.env.NODE_ENV === 'production';

const envDomains = {
    test: '',
    int: '',
    local: '',
    prod: '',
};

const domain = envDomains[prod ? 'prod' : 'int'];

const query = new URLSearchParams(location.search);

const adminMode = query.get('_adminmode');

const customApplicationConfiguration = {
    network: { //... },

    openConfigurator: { method: WidgetAfterSaveMethods.OpenNewWindow },
    defaultLoadIdentifier: false,
    loadVariantsFirst: false,
    showInitialOptionclassification: false,
    showStartHere: true,
    showAddToCartButton: true,

    language: false,
    locales: [{ code: 'de_DE', preferred: true }, { code: 'en_GB' }],

    saveTypes: SaveTypes,
    customTitleSaveTypes: [SaveTypes.DESIGNTEMPLATE, SaveTypes.INSPIRATION],
    editCustomTitleBeforeSave: false,
    loadInspirations: false,

    availableTools: {
        fastConfiguration: false,
        save: true,
        load: true,
        share: true,
        print: true,
    },
    displayAcceptDesign: true,
    validateBeforeCTA: true,
    dock: false,
    addToCartMethod: 'postMessage',
    restrictAmountselectionToSelectedVariant: false,
    displayColorAmountWarnings: true,
    displayEditorRulers: true,
    switchViewOnComponentSelection: true,

    visualization: {
        designAreaHighlightBackgroundColor: 'transparent',
        designAreaHighlightBorderWidth: 2,
        dynamicDesignAreaHighlightBorderColor: false,
        dynamicDesignAreaHighlightBorderColorMode: 'contrast',
    },
    designer: { dpi: 300 },

    displaySwitchedOptions: false,
    checkOptionclassificationOnSelect: false,
    optionclassificationCarousel: true,
    disableBeforeunloadWarning: false,
    hideOptionListOnAwayClick: true,
    optionDetailsWithImage: false,
    fixedOptionlist: false,
    optionlistDialogPositioning: {
        left: [['left', 'right'], ['top', 'top']],
        bottom: [['center', 'center'], ['bottom', 'top']],
    },
    optionListFixedToLeft: true,
};

setConfiguration(customApplicationConfiguration);

export default customApplicationConfiguration;

envDomains

Specify different environments for the api here.

const envDomains = {
    test: 'test.redhotmagma.de',
    int: 'int.redhotmagma.de',
    local: '127.0.0.1:8080',
    prod: 'www.redhotmagma.de',
};

customApplicationConfiguration

Network

There are several sub paths that can be specified separately in the network section:

const customApplicationConfiguration = {
    network: {
        host: domain,
        baseURL: `${domain}/frontendapi/`,
        adminBaseURL: `${domain}/api/`,
        resourcesURL: `${domain}`,
        // request timeout in milliseconds
        timeout: 60000,
    },
};

Initialization

const customApplicationConfiguration = {
    // default options for opening the configurator (eg. after saved from the calculation widget)
    openConfigurator: {
        method: WidgetAfterSaveMethods.OpenNewWindow,
        // when the PostMessage mode is used the targetOrigin can be specified, otherwise '*' will be used
    },
    // override this to set a default item identifier to load if not defined in the route
    defaultLoadIdentifier: false,
    // set true to load available variants before loading the configuration
    loadVariantsFirst: false, // default: false
    // set true to open the first optionclassification instead of the "start here" text
    showInitialOptionclassification: false, // default: false
    // show the "start here" animation
    showStartHere: true,
    // show the "add to cart" button by default
    showAddToCartButton: true,
    // load the available configuration modes (might be set to false in older configurators)
    loadConfigurationModes: true,
};

Internationalization

const customApplicationConfiguration = {
    // the default language code (format: de-DE, en-GB, etc.), use false to select the browser language
    language: false,
    // the available locales
    locales: [{ code: 'de_DE', preferred: true }, { code: 'en_GB' }],
    // hyphenation patterns per language, used to break long words in the frontend.
    // A value is either an imported hyphenation module (e.g. `hyphenated-de`)
    // or the name of a pattern language that is already bundled, such as 'en-US'.
    hyphenations: {
        'de-DE': hyphensDeDE,
        'en-GB': 'en-US',
    },
};

Controls

const customApplicationConfiguration = {
    // available tools in the toolbar
    availableTools: {
        fastConfiguration: false,
        save: true,
        load: true,
        share: true,
        print: true,
    },
    // set true to show accept design checkbox approval
    displayAcceptDesign: true,
    // show the "add to cart" button by default
    showAddToCartButton: true,
    // set true to run server-side validation before the call to action (e.g. addToCart)
    validateBeforeCTA: true, // default: true
    // set false to decide by screen orientation, or can be fixed 'left' or 'bottom'
    dock: false,
    // define javascript method to call on addToCart
    // Second option: 'functionCall' will call window.parent.addToCart(configuration.code)
    addToCartMethod: 'postMessage', //  default: 'postMessage'
    // if set to false only the amount for the currently selected variant can be selected
    // when switching the variant the selected amount is transferred to the newly selected variant
    restrictAmountselectionToSelectedVariant: false,
    // show warning if on switch of production method color amount will not fit anymore
    displayColorAmountWarnings: true,
    // show rulers in edit mode
    displayEditorRulers: true,
    // switch to the previously selected view when selecting a component that has no view specified
    switchViewOnComponentSelection: true,
};

Visualization

const customApplicationConfiguration = {
    visualization: {
        useVisualizationScript: {
            // source of the visualization script in the public folder, e.g. 'visualization.js'
            src: '',
        },
        // background fill of a highlighted design area
        designAreaHighlightBackgroundColor: 'transparent',
        // width of the design area highlight border, in screen pixels. Stays the same at any
        // zoom level and for any design area size.
        designAreaHighlightBorderWidth: 2,
        // derive the design area highlight border color from the average color of the product
        // image underneath the border, instead of using the client theme's highlightColor.
        // This keeps the border visible on products whose color is close to the highlight color.
        // 2D visualization only, the 3D one always uses the static color.
        dynamicDesignAreaHighlightBorderColor: false, // default: false
        // how the dynamic border color is derived from the sampled product color:
        // 'contrast'      near black or near white, whichever reads better (most legible)
        // 'complementary' the inverted hue at a high saturation (more decorative, but a thin
        //                 line in a mid bright color is harder to see than a light or dark one)
        dynamicDesignAreaHighlightBorderColorMode: 'contrast', // default: 'contrast'
        options: {
            // when zoom = 1 use NearestFilter (for a sharper product image)
            sharperFullView: false,
            // upscale or downscale the product image to fit 2048 x 2048
            // Warning! not recommended, use better image resources instead
            optimizeProductImage: false,
        },
        options3D: {
            // create a ground to show a shadow under the model
            useShadowGround: true,
            // remove the shadow ground for screenshots
            removeGroundForScreenshot: true,
            // use an environment map
            useEnvironmentMap: true,
            // use a programmatically generated baked shadow (instead of a directional light shadow)
            useBakedShadows: false,
            // baked shadow parameters
            bakedShadow: {
                blurRadius: 6,
                shadowOpacity: 0.8,
            },
        },
    },
};

Note

dynamicDesignAreaHighlightBorderColor only affects the design area border drawn on the product view. The border of the opened graphics editor keeps using the highlight color of the client theme.

Designer

const customApplicationConfiguration = {
    designer: {
        dpi: 300, // the default canvas dpi as project specific option
        maxColorAmount: 10, // the default max number of colors in the colorizing feature

        // the image formats users can upload to designareas / designer items
        allowedImageUploadFormats: 'jpg,jpeg,svg,png,pdf,bmp,tif,tiff,eps,ai,gif',
        // set true to include the metric content size in the design data
        includeMetricContentSize: false,
        // set true to generate individual production svgs for each bulk name
        // (results in a production pdf per name in the zip download)
        bulkNamesPerNameDesignFiles: false,
    },
    // quality used when vectorizing uploaded images
    vectorizeImageQuality: 45000,
    // render the preview images that are handed over to the checkout
    renderPreviewImagesForCheckout: true,
    // automatically show the vectorization preview in the colorizing step
    autoPreviewVectorization: true,
};

Options

const customApplicationConfiguration = {
    // set true to display the selected option for each optionclassification
    displaySwitchedOptions: false, // default: false
    // set true to check the optionclassification immediately after selected
    checkOptionclassificationOnSelect: false,
    // use carousel for optionclassification list
    optionclassificationCarousel: true,
    // set true to disable beforeunload warning
    disableBeforeunloadWarning: false,
    // set true to hide optionlist when user clicks somewhere else
    hideOptionListOnAwayClick: true,
    // set true to force the option details dialog shown with an image
    optionDetailsWithImage: false,
    // set true to disable optionlist moving
    fixedOptionlist: false,
    // relative position of the option list dialog
    // [
    //   [<dialogSideH{left|center|right}>, <relToSideH{left|center|right}>],
    //   [<dialogSideV{top|center|bottom}>, <relToSideV{top|center|bottom}>]
    // ]
    optionlistDialogPositioning: {
        left: [['left', 'right'], ['top', 'top']],
        bottom: [['center', 'center'], ['bottom', 'top']],
    },
    // should the option list be fixed to the left side, or it can open up above the selected option classification
    optionListFixedToLeft: true,
};

Save Types

Save types are stored in the database in table configurationtype. They need to be aligned here in frontend for saving the configurations with the correct type.

const customApplicationConfiguration = {
    // which types need a custom title before saving?
    // array of configuration save types
    saveTypes: SaveTypes,
    customTitleSaveTypes: [SaveTypes.DESIGNTEMPLATE, SaveTypes.INSPIRATION],
    // show custom title input before saving the configuration
    editCustomTitleBeforeSave: false, // default: false
};

Extending functionality

If a handling in the configurator should be extended, you can define your own reducers for this purpose. These can listen to the events defined in node_module redhotmagma-configurator-frontendgui and extend them at the necessary places.

Note

To see what actions are performed, you can open your browser’s DevTools on the Configurator page and look into the console.
Open an action and its next state to see which reducers are available and can be extended. All first level states, such as configurator, can be extended.
Let us say that we want to read the number of characters entered in the visualization part and write them to the configurator state into the edited text object for further processing.
For this we first create a reducer to listen to an event from our node_module redhotmagma-configurator-frontendgui. Reducers can be created under the following directory: /src/Custom/App/Reducers/.
Because we want to make an extension within the configurator node in our example, we create a configurator reducer:
/src/Custom/App/Reducers/Configurator/Reducer.js

A reducer always contains the following basic structure:

import configuratorReducer
from 'redhotmagma-configurator-frontendgui/App/Reducers/Configurator/Reducer';

export default function (state, action) {
    return configuratorReducer(state, action);
}

To use the now created reducer, it must be defined in the CustomApplicationConfiguration.js in the customApplicationConfiguration configuration:

import customConfiguratorReducer from '../Custom/App/Reducers/Configurator/Reducer';

const customApplicationConfiguration = {
    // ...
    reducers: {
        configurator: customConfiguratorReducer
    },
// ...
};

Now we have set the base to extend the configurator Redux state.

First we have to find the right action we want to get into. We can find this out via the console of the browser, as already described in the note above. In our case we use the action DesignData/SET_CANVAS_DATA.

In the configurator reducer we can now define the following to listen to the requested action:

export default function (state, action) {

    switch (action.type) {
        case designDataActionTypes.SET_CANVAS_DATA: {
            // ...
        }
    }
    return configuratorReducer(state, action);
}

Within the case block we can now implement our custom handling and add a new node to the state. In our example we set the letterAmount in the text object:

import get from 'lodash/get';
import immutableSet from 'redhotmagma-configurator-frontendgui/Utils/Immutable/set';
import configuratorReducer
    from 'redhotmagma-configurator-frontendgui/App/Reducers/Configurator/Reducer';
import {actionTypes as designDataActionTypes} from 'redhotmagma-configurator-frontendgui/App/Reducers/DesignData/Actions';
import {letterAmount} from './Selectors';

function updateLetterAmount(state, action) {
    const { designAreaIdentifier } = action;

    const objectsPath = `configuration.designdata.${designAreaIdentifier}.canvasData.objects`;
    const objects = get(nextState, objectsPath, []);

    return immutableSet(
        state,
        objectsPath,
        objects.map(object => {
            if (object.type === 'Text') {
                return {
                    ...object,
                    letterAmount: letterAmount(object)
                };
            }
            return object;
        })
    );
}

export default function (state, action) {

    switch (action.type) {
        case designDataActionTypes.SET_CANVAS_DATA: {
            // get the original state first
            const nextState = configuratorReducer(state, action);
            // call the customizer function, which should always return the next state object
            return updateLetterAmount(nextState, action);
        }
    }
    return configuratorReducer(state, action);
}