Skip to content

Latest commit

 

History

History
 
 

settings-web

Web project for the Settings UI

Introduction

This project generates the web UI shown in the PowerToys Settings. It's a ReactJS project created using UI Fabric.

Build Commands

Here are the commands to build and test this project:

To start the development server

npm install
npm run start

Building and integrating into PowerToys settings project

npm run build

Updating the icons

Icons inside src/icons/ were generated from the Office UI Fabric Icons subset generation tool.

In case the subset needs to be changed, additional steps are needed to include the icon font in the built dist/bundle.js:

A list of the current icons in the subset can be seen in the icons object in src/icons/src/fabric-icons.ts.

SVG icons, including the icons for each PowerToy listed in the Settings, are contained in src/svg/. To add additional SVG icons add them to src/svg/ and register them in src/setup_icons.tsx.

Code Structure

The project structure is based on the UI Fabric scaffold obtained by initializing it with npm init uifabric.

The HTML entry-point of the project. Loads the ReactJS distribution script. Defines JavaScript functions to receive and send messages to the PowerToys Settings window.

Main ReactJS entrypoint, initializing the ReactDOM.

Defines the setup_powertoys_icons function that registers the icons to be used in the components.

Contains the ReactJS components, including the Settings controls for each type of setting.

Defines the main App component, containing the UI layout, navigation menu, dialogs and main load/save logic.

Defines the PowerToys General Settings component, including logic to construct the object sent to PowerToys to change the General settings.

Defines the component that generates the settings screen for a PowerToy depending on its settings definition.

Defines the base class for a Settings control.

General layout styles.

Icons generated from the Office UI Fabric Icons subset generation tool.

SVG icon assets.

Creating a new settings control

The BaseSettingsControl class can be extended to create a new Settings control type.

export class BaseSettingsControl extends React.Component <any, any> {
  parent_on_change: Function;
  constructor(props:any) {
    super(props);
    this.parent_on_change=props.on_change;
  }
  public get_value():any {
    return null;
  }
}

A settings control overrides the get_value function to return the value to be used for the Setting the control is representing. It will use the parent_on_change property to signal that the user made some changes to the settings.

Here's the StringTextSettingsControl component to serve as an example:

export class StringTextSettingsControl extends BaseSettingsControl {
  textref:any = null; // Keeps a reference to the corresponding TextField in the DOM.

  constructor(props:any) {
    super(props);
    this.textref = null;
    this.state={
      property_values: props.setting
    }
  }

  componentWillReceiveProps(props: any) {
    // Fully controlled component.
    // Reacting to a property change so that the control is redrawn properly.
    this.setState({ property_values: props.setting })
  }

  public get_value() : any {
    // Returns the TextField value.
    return {value: this.textref.value};
  }

  public render(): JSX.Element {
    // Renders a UI Fabric TextField.
    return (
      <TextField
        onChange = {
          (_event,_new_value) => {
            // Updates the state with the new value introduced in the TextField.
            this.setState( (prev_state:any) => ({
                property_values: {
                  ...(prev_state.property_values),
                  value: _new_value
                }
              })
            );
            // Signal the parent that the user changed a value.
            this.parent_on_change();
          }
        }
        value={this.state.property_values.value}
        label={this.state.property_values.display_name}
        componentRef= {(input) => {this.textref=input;}}
      />
    );
  }
}

Each settings property has a editor_type field that's used to differentiate between the Settings control types:

'test string_text': {
  display_name: 'This is what a string_text looks like',
  editor_type: 'string_text',
  value: 'A sample string value'
}

A new Settings control component can be added to src/components/. To render the new Settings control, its editor_type and component instance need to be added to the CustomSettingsScreen component render():

import React from 'react';
import {StringTextSettingsControl} from './StringTextSettingsControl';

...

export class CustomSettingsScreen extends React.Component <any, any> {
  references: any;
  parent_on_change: Function;
...
  public render(): JSX.Element {
    let power_toys_properties = this.state.powertoy.properties;
    return (
      <Stack tokens={{childrenGap:20}}>
...
        {
          Object.keys(power_toys_properties).
          map( (key) => {
            switch(power_toys_properties[key].editor_type) {
...
              case 'string_text':
                return <StringTextSettingsControl
                  setting = {power_toys_properties[key]}
                  key={key}
                  on_change={this.parent_on_change}
                  ref={(input) => {this.references[key]=input;}}
                  />;
...