function addGUIControls(genWorkspace, defaultOptions, config = {}) {
  // Initialize state.
  const guiState = loadGUIState();

  // Initialize toolboxes.
  const toolboxes =
    /** @type {Object<string,Blockly.utils.toolbox.ToolboxDefinition>} */ (
      config.toolboxes || {
        categories: toolboxCategories,
        simple: toolboxSimple,
      }
    );
  const defaultToolboxName = initDefaultToolbox(defaultOptions, toolboxes);
  guiState.toolboxName = guiState.toolboxName || defaultToolboxName;
  guiState.options.toolbox = toolboxes[guiState.toolboxName];

  // Initialize themes.
  const themes = getThemes(defaultOptions);
  const defaultThemeName = defaultOptions.theme
    ? /** @type {!Blockly.Theme} */ (defaultOptions.theme).name
    : 'classic';
  guiState.themeName = guiState.themeName || defaultThemeName;
  guiState.options.theme = themes[guiState.themeName];

  const defaultRendererName = defaultOptions.renderer
    ? defaultOptions.renderer
    : 'geras';
  guiState.renderer = guiState.renderer || defaultRendererName;
  guiState.debugEnabled = guiState.debugEnabled || false;

  // Merge default and saved state.
  const saveOptions = /** @type {!Blockly.BlocklyOptions} */ ({
    ...defaultOptions,
    ...guiState.options,
  });
  initDebugRenderer(guiState);

  let workspace = genWorkspace(saveOptions);
  const resizeEnabled = !config.disableResize;

  const gui = new dat.GUI({
    autoPlace: false,
    closeOnTop: true,
    width: 250,
    load: guiState.guiObject || {},
  });

  const guiElement = gui.domElement;
  guiElement.style.position = 'absolute';
  guiElement.style.zIndex = '1000';
  guiElement.onclick = () => {
    // Save the GUI state locally.
    guiState.guiObject = gui.getSaveObject();
    saveGUIState(guiState, defaultToolboxName, defaultThemeName);
  };

  const onResize = () => {
    const metrics = workspace.getMetrics();
    if (workspace.RTL) {
      guiElement.style.left = metrics.absoluteLeft + 'px';
      guiElement.style.right = 'auto';
    } else {
      guiElement.style.left = 'auto';
      if (metrics.toolboxPosition === Blockly.TOOLBOX_AT_RIGHT) {
        guiElement.style.right = metrics.toolboxWidth + 'px';
      } else {
        guiElement.style.right = '0';
      }
    }
    guiElement.style.top = metrics.absoluteTop + 'px';
  };
  if (resizeEnabled) {
    onResize();
  }

  const container = /** @type {!HTMLElement} */ (
    workspace.getInjectionDiv().parentNode
  );
  container.style.position = 'relative';
  container.appendChild(guiElement);

  const options = Object.assign({}, workspace.options);

  const onChangeInternal = () => {
    // Serialize current workspace state.
    const state = Blockly.serialization.workspaces.save(workspace);
    // Dispose of the current workspace
    workspace.dispose();
    // Create a new workspace with options.
    workspace = genWorkspace(saveOptions);
    // Deserialize state into workspace.
    Blockly.serialization.workspaces.load(state, workspace);
    // Resize the gui.
    if (resizeEnabled) {
      onResize();
    }
    // Save the GUI state locally.
    guiState.guiObject = gui.getSaveObject();
    saveGUIState(guiState, defaultToolboxName, defaultThemeName);
    // Update options.
    merge(saveOptions, workspace.options);
    gui.updateDisplay();
  };

  const onChange = (key, value) => {
    saveOptions[key] = value;
    guiState.options[key] = value;
    onChangeInternal();
  };

  const reset = () => {
    // Reset saved options.
    Object.keys(saveOptions).forEach((k) => delete saveOptions[k]);
    assign(saveOptions, defaultOptions);
    Object.keys(guiState.options).forEach((k) => delete guiState.options[k]);
    // Reset debug options.
    guiState.renderer = defaultRendererName;
    initDebugRenderer(guiState, true);
    updateDebugFolder(debugOptionsFolder, false);

    // Reset toolbox selection.
    guiState.toolboxName = defaultToolboxName;
    guiState.themeName = defaultThemeName;

    // Close all folders.
    Object.values(gui.__folders).forEach((f) => f.close());

    onChangeInternal();
  };

  gui.add(
    {
      Reset: reset,
    },
    'Reset',
  );

  // Options folder.
  const optionsFolder = gui.addFolder('Options');
  setTooltip(
    optionsFolder,
    'Options that affect the appearance of the workspace.',
  );
  openFolderIfOptionSelected(optionsFolder, guiState, guiState.options, [
    'rtl',
    'renderer',
    'toolboxPosition',
    'horizontalLayout',
  ]);

  setTooltip(
    optionsFolder
      .add(options, 'RTL')
      .name('rtl')
      .onChange((value) => onChange('rtl', value)),
    'If true, mirror the editor (for Arabic or Hebrew locales).',
  );

  // Renderer.
  populateRendererOption(optionsFolder, guiState, onChange);

  // Theme.
  populateThemeOption(
    optionsFolder,
    guiState,
    themes,
    defaultThemeName,
    onChange,
  );

  // Toolbox.
  populateToolboxOption(
    optionsFolder,
    guiState,
    toolboxes,
    defaultToolboxName,
    onChange,
  );
  populateToolboxSidesOption(
    optionsFolder,
    options,
    saveOptions,
    guiState,
    onChangeInternal,
  );

  // Basic options.
  const basicFolder = optionsFolder.addFolder('Basic');
  setTooltip(basicFolder, 'Basic options like sound, comment etc...');
  populateBasicOptions(basicFolder, options, guiState, onChange);

  // Move options.
  const moveFolder = optionsFolder.addFolder('Move');
  setTooltip(moveFolder, 'Move options like scrollbars, drag etc...');
  populateMoveOptions(moveFolder, options, saveOptions, onChange);
  openFolderIfOptionSelected(moveFolder, guiState, guiState.options, ['move']);

  // Zoom options.
  const zoomFolder = optionsFolder.addFolder('Zoom');
  setTooltip(zoomFolder, 'Zoom options like controls, startScale etc...');
  populateZoomOptions(zoomFolder, options, saveOptions, onChange);
  openFolderIfOptionSelected(moveFolder, guiState, guiState.options, ['zoom']);

  // Grid options.
  const gridFolder = optionsFolder.addFolder('Grid');
  setTooltip(gridFolder, 'Grid options like spacing, length etc...');
  populateGridOptions(gridFolder, options, saveOptions, onChange);
  openFolderIfOptionSelected(moveFolder, guiState, guiState.options, ['grid']);

  // Debug renderer.
  const debugFolder = gui.addFolder('Debug');
  // Adds the checkbox to toggle using the debug renderer.
  const debugController = debugFolder.add(guiState, 'debugEnabled');
  // Folder with all the debug options. Hidden if the debugger is not enabled.
  const debugOptionsFolder = debugFolder.addFolder('Debug Options');

  setTooltip(debugFolder, 'Rendering debug configuration.');
  populateDebugFolder(debugController, debugOptionsFolder, guiState, onChange);
  populateDebugOptionsFolder(debugOptionsFolder, guiState, onChangeInternal);

  // GUI actions.
  const actionsFolder = gui.addFolder('Actions');
  const actionSubFolders = {};
  const actions = {};

  /**
   * Get the current workspace.
   * @returns {!Blockly.WorkspaceSvg} The Blockly workspace.
   */
  const getWorkspace = () => {
    return workspace;
  };

  /**
   * Add a custom action to the list of playground actions.
   * @param {string} name The action label.
   * @param {function(!Blockly.Workspace):void} callback The callback to call
   *     when the action is clicked.
   * @param {string=} folderName Optional folder to place the action under.
   * @param {string=} tooltip Optional tooltip to set.
   * @returns {dat.GUIController} The GUI controller.
   */
  const addAction = (name, callback, folderName, tooltip) => {
    actions[name] = () => {
      callback(workspace);
    };
    let folder = actionsFolder;
    if (folderName) {
      if (actionSubFolders[folderName]) {
        folder = actionSubFolders[folderName];
      } else {
        folder = actionsFolder.addFolder(folderName);
        folder.open();
        actionSubFolders[folderName] = folder;
      }
    }
    const controller = folder.add(actions, name);
    tooltip && setTooltip(controller, tooltip);
    name && controller.name(name);
    return controller;
  };

  /**
   * Add a custom checkbox action to the list of playground actions.
   * @param {string} name The action label.
   * @param {function(!Blockly.Workspace,boolean):void} callback The callback to
   *     call when the action is clicked.
   * @param {string=} folderName Optional folder to place the action under.
   * @param {boolean=} defaultValue Default value.
   * @param {string=} tooltip Optional tooltip to set.
   * @returns {dat.GUIController} The GUI controller.
   */
  const addCheckboxAction = (
    name,
    callback,
    folderName,
    defaultValue,
    tooltip,
  ) => {
    actions[name] = !!defaultValue;
    let folder = actionsFolder;
    if (folderName) {
      if (actionSubFolders[folderName]) {
        folder = actionSubFolders[folderName];
      } else {
        folder = actionsFolder.addFolder(folderName);
        folder.open();
        actionSubFolders[folderName] = folder;
      }
    }
    const controller = folder.add(actions, name);
    tooltip && setTooltip(controller, tooltip);
    name && controller.name(name);
    controller.listen().onFinishChange((value) => {
      callback(workspace, value);
    });

    return controller;
  };

  const devGui = /** @type {?} */ (gui);
  devGui.addCheckboxAction = addCheckboxAction;
  devGui.addAction = addAction;
  devGui.getWorkspace = getWorkspace;

  addActions(devGui, workspace);

  return devGui;
}
Use dat.GUI to add controls to adjust configuration of a Blockly workspace.
@param {function(!Blockly.BlocklyOptions):Blockly.WorkspaceSvg} genWorkspace
    A workspace creation method called every time the toolbox is
    re-configured.
@param {Blockly.BlocklyOptions} defaultOptions The default workspace options
    to use.
@param {GUIConfig=} config Optional GUI config.
@returns {dat.GUI} The dat.GUI instance.