This project generates the web UI shown in the PowerToys Settings.
It's a ReactJS
project created using UI Fabric.
Here are the commands to build and test this project:
npm install
npm run start
npm run build
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
:
- Copy the inline font data taken from
src/icons/css/fabric-icons-inline.css
and place it in thefontFace
src
value insrc/icons/src/fabric-icons.ts
.
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
.
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.
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;}}
/>;
...