chromium/ui/file_manager/file_manager/state/ducks/search.ts

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

import {Slice} from '../../lib/base_store.js';
import {type SearchData, SearchLocation, type SearchOptions, SearchRecency, type State} from '../../state/state.js';

/**
 * @fileoverview Search slice of the store.
 */

const slice = new Slice<State, State['search']>('search');
export {slice as searchSlice};

/**
 * Returns if the given search data represents empty (cleared) search.
 */
export function isSearchEmpty(search: SearchData): boolean {
  return Object.values(search).every(f => f === undefined);
}

/**
 * Helper function that does a deep comparison between two SearchOptions.
 */
function optionsChanged(
    stored: SearchOptions|undefined, fresh: SearchOptions|undefined): boolean {
  if (fresh === undefined) {
    // If fresh options are undefined, that means keep the stored options. No
    // matter what the stored options are, we are saying they have not changed.
    return false;
  }
  if (stored === undefined) {
    return true;
  }
  return fresh.location !== stored.location ||
      fresh.recency !== stored.recency ||
      fresh.fileCategory !== stored.fileCategory;
}

const setSearchParameters = slice.addReducer('set', searchReducer);

function searchReducer(state: State, payload: SearchData): State {
  const blankSearch = {
    query: undefined,
    status: undefined,
    options: undefined,
  };
  // Special case: if none of the fields are set, the action clears the search
  // state in the store.
  if (isSearchEmpty(payload)) {
    // Only change the state if the stored value has some defined values.
    if (state.search && !isSearchEmpty(state.search)) {
      return {
        ...state,
        search: blankSearch,
      };
    }
    return state;
  }

  const currentSearch = state.search || blankSearch;
  // Create a clone of current search. We must not modify the original object,
  // as store customers are free to cache it and check for changes. If we modify
  // the original object the check for changes incorrectly return false.
  const search: SearchData = {...currentSearch};
  let changed = false;
  if (payload.query !== undefined && payload.query !== currentSearch.query) {
    search.query = payload.query;
    changed = true;
  }
  if (payload.status !== undefined && payload.status !== currentSearch.status) {
    search.status = payload.status;
    changed = true;
  }
  if (optionsChanged(currentSearch.options, payload.options)) {
    search.options = {...payload.options} as SearchOptions;
    changed = true;
  }
  return changed ? {...state, search} : state;
}

/**
 * Generates a search action based on the supplied data.
 * Query, status and options can be adjusted independently of each other.
 */
export const updateSearch = (data: SearchData) => setSearchParameters({
  query: data.query,
  status: data.status,
  options: data.options,
});

/**
 * Create action to clear all search settings.
 */
export const clearSearch = () => setSearchParameters({
  query: undefined,
  status: undefined,
  options: undefined,
});

/**
 * Search options to be used if the user did not specify their own.
 */
export function getDefaultSearchOptions(): SearchOptions {
  return {
    location: SearchLocation.THIS_FOLDER,
    recency: SearchRecency.ANYTIME,
    fileCategory: chrome.fileManagerPrivate.FileCategory.ALL,
  };
}