chromium/chrome/browser/resources/bookmarks/actions.ts

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import {assert} from 'chrome://resources/js/assert.js';
import type {Action} from 'chrome://resources/js/store.js';

import {IncognitoAvailability, ROOT_NODE_ID} from './constants.js';
import type {BookmarkNode, BookmarksPageState, NodeMap} from './types.js';
import {getDescendants, getDisplayedList, normalizeNode} from './util.js';

/**
 * @fileoverview Module for functions which produce action objects. These are
 * listed in one place to document available actions and their parameters.
 */

export type CreateBookmarkAction = Action&{
  id: string,
  parentId: string,
  parentIndex: number,
  node: BookmarkNode,
};

export function createBookmark(
    id: string,
    treeNode: chrome.bookmarks.BookmarkTreeNode): CreateBookmarkAction {
  return {
    name: 'create-bookmark',
    id: id,
    parentId: treeNode.parentId!,
    parentIndex: treeNode.index!,
    node: normalizeNode(treeNode),
  };
}

export type EditBookmarkAction = Action&{
  id: string,
  changeInfo: {title: string, url?: string},
};

export function editBookmark(
    id: string, changeInfo: {title: string, url?: string}): EditBookmarkAction {
  return {
    name: 'edit-bookmark',
    id: id,
    changeInfo: changeInfo,
  };
}

export type MoveBookmarkAction = Action&{
  id: string,
  parentId: string,
  index: number,
  oldParentId: string,
  oldIndex: number,
};

export function moveBookmark(
    id: string, parentId: string, index: number, oldParentId: string,
    oldIndex: number): MoveBookmarkAction {
  return {
    name: 'move-bookmark',
    id: id,
    parentId: parentId,
    index: index,
    oldParentId: oldParentId,
    oldIndex: oldIndex,
  };
}

export type ReorderChildrenAction = Action&{
  id: string,
  children: string[],
};

export function reorderChildren(
    id: string, newChildIds: string[]): ReorderChildrenAction {
  return {
    name: 'reorder-children',
    id: id,
    children: newChildIds,
  };
}

export type RemoveBookmarkAction = Action&{
  id: string,
  parentId: string,
  index: number,
  descendants: Set<string>,
};

export function removeBookmark(
    id: string, parentId: string, index: number,
    nodes: NodeMap): RemoveBookmarkAction {
  const descendants = getDescendants(nodes, id);
  return {
    name: 'remove-bookmark',
    id: id,
    descendants: descendants,
    parentId: parentId,
    index: index,
  };
}

export type RefreshNodesAction = Action&{
  nodes: NodeMap,
};

export function refreshNodes(nodeMap: NodeMap): RefreshNodesAction {
  return {
    name: 'refresh-nodes',
    nodes: nodeMap,
  };
}

export type SelectFolderAction = Action&{
  id: string,
};

export function selectFolder(id: string, nodes?: NodeMap): SelectFolderAction|
    null {
  if (nodes && (id === ROOT_NODE_ID || !nodes[id] || nodes[id]!.url)) {
    console.warn('Tried to select invalid folder: ' + id);
    return null;
  }

  return {
    name: 'select-folder',
    id: id,
  };
}

export type ChangeFolderOpenAction = Action&{
  id: string,
  open: boolean,
};

export function changeFolderOpen(
    id: string, open: boolean): ChangeFolderOpenAction {
  return {
    name: 'change-folder-open',
    id: id,
    open: open,
  };
}

export function clearSearch(): Action {
  return {
    name: 'clear-search',
  };
}

export function deselectItems(): Action {
  return {
    name: 'deselect-items',
  };
}

export type SelectItemsAction = Action&{
  clear: boolean,
  toggle: boolean,
  anchor: string,
  items: string[],
};

export function selectItem(
    id: string, state: BookmarksPageState,
    config: {clear: boolean, range: boolean, toggle: boolean}):
    SelectItemsAction {
  assert(!config.toggle || !config.range);
  assert(!config.toggle || !config.clear);

  const anchor = state.selection.anchor;
  const toSelect: string[] = [];
  let newAnchor = id;

  if (config.range && anchor) {
    const displayedList = getDisplayedList(state);
    const selectedIndex = displayedList.indexOf(id);
    assert(selectedIndex !== -1);
    let anchorIndex = displayedList.indexOf(anchor);
    if (anchorIndex === -1) {
      anchorIndex = selectedIndex;
    }

    // When performing a range selection, don't change the anchor from what
    // was used in this selection.
    newAnchor = displayedList[anchorIndex]!;

    const startIndex = Math.min(anchorIndex, selectedIndex);
    const endIndex = Math.max(anchorIndex, selectedIndex);

    for (let i = startIndex; i <= endIndex; i++) {
      toSelect.push(displayedList[i]!);
    }
  } else {
    toSelect.push(id);
  }

  return {
    name: 'select-items',
    clear: config.clear,
    toggle: config.toggle,
    anchor: newAnchor,
    items: toSelect,
  };
}

export function selectAll(
    ids: string[], state: BookmarksPageState,
    anchor?: string): SelectItemsAction {
  const finalAnchor: string = anchor ? anchor! : state.selection!.anchor!;
  return {
    name: 'select-items',
    clear: true,
    toggle: false,
    anchor: finalAnchor,
    items: ids,
  };
}

export type UpdateAnchorAction = Action&{
  anchor: string,
};

export function updateAnchor(id: string): UpdateAnchorAction {
  return {
    name: 'update-anchor',
    anchor: id,
  };
}

export type StartSearchAction = Action&{
  term: string,
};

export function setSearchTerm(term: string): (Action|StartSearchAction) {
  if (!term) {
    return clearSearch();
  }

  return {
    name: 'start-search',
    term: term,
  };
}

export type FinishSearchAction = Action&{
  results: string[],
};

export function setSearchResults(ids: string[]): FinishSearchAction {
  return {
    name: 'finish-search',
    results: ids,
  };
}

export type SetPrefAction = Action&{
  value: IncognitoAvailability | boolean,
};

export function setIncognitoAvailability(availability: IncognitoAvailability):
    SetPrefAction {
  assert(availability !== IncognitoAvailability.FORCED);
  return {
    name: 'set-incognito-availability',
    value: availability,
  };
}

export function setCanEditBookmarks(canEdit: boolean): SetPrefAction {
  return {
    name: 'set-can-edit',
    value: canEdit,
  };
}