import { isEqual } from "lodash"; import { Dispatch } from "redux"; import { push } from "connected-react-router"; import { SearchFilter } from "../searchFilters"; import { getFromApi } from "../util"; import { ActionType, AppState, Graph, InstanceDetails, InstanceSort, SearchResponse } from "./types"; // Instance details const requestInstanceDetails = (instanceName: string) => ({ payload: instanceName, type: ActionType.REQUEST_INSTANCE_DETAILS, }); const receiveInstanceDetails = (instanceDetails: InstanceDetails) => ({ payload: instanceDetails, type: ActionType.RECEIVE_INSTANCE_DETAILS, }); const instanceLoadFailed = () => ({ type: ActionType.INSTANCE_LOAD_ERROR, }); const deselectInstance = () => ({ type: ActionType.DESELECT_INSTANCE, }); // Graph const requestGraph = () => ({ type: ActionType.REQUEST_GRAPH, }); const receiveGraph = (graph: Graph) => ({ payload: graph, type: ActionType.RECEIVE_GRAPH, }); const graphLoadFailed = () => ({ type: ActionType.GRAPH_LOAD_ERROR, }); // Instance list const requestInstanceList = (sort?: InstanceSort) => ({ payload: sort, type: ActionType.REQUEST_INSTANCES, }); const receiveInstanceList = (instances: InstanceDetails[]) => ({ payload: instances, type: ActionType.RECEIVE_INSTANCES, }); const instanceListLoadFailed = () => ({ type: ActionType.INSTANCE_LIST_LOAD_ERROR, }); // Search const requestSearchResult = (query: string, filters: SearchFilter[]) => ({ payload: { query, filters }, type: ActionType.REQUEST_SEARCH_RESULTS, }); const receiveSearchResults = (result: SearchResponse) => ({ payload: result, type: ActionType.RECEIVE_SEARCH_RESULTS, }); const searchFailed = () => ({ type: ActionType.SEARCH_RESULTS_ERROR, }); const resetSearch = () => ({ type: ActionType.RESET_SEARCH, }); export const setResultHover = (domain?: string) => ({ payload: domain, type: ActionType.SET_SEARCH_RESULT_HOVER, }); /** Async actions: https://redux.js.org/advanced/asyncactions */ export const loadInstance = (instanceName: string | null) => (dispatch: Dispatch, getState: () => AppState) => { if (!instanceName) { dispatch(deselectInstance()); if (getState().router.location.pathname.startsWith("/instance/")) { dispatch(push("/")); } return; } dispatch(requestInstanceDetails(instanceName)); return getFromApi(`instances/${instanceName}`) .then((details) => dispatch(receiveInstanceDetails(details))) .catch(() => dispatch(instanceLoadFailed())); }; export const updateSearch = (query: string, filters: SearchFilter[]) => ( dispatch: Dispatch, getState: () => AppState ) => { query = query.trim(); if (!query) { dispatch(resetSearch()); return; } const prevQuery = getState().search.query; const prevFilters = getState().search.filters; const isNewQuery = prevQuery !== query || !isEqual(prevFilters, filters); const { next } = getState().search; let url = `search/?query=${query}`; if (!isNewQuery && next) { url += `&after=${next}`; } // Add filters // The format is e.g. type_eq=mastodon or user_count_gt=1000 filters.forEach((filter) => { url += `&${filter.field}_${filter.relation}=${filter.value}`; }); dispatch(requestSearchResult(query, filters)); return getFromApi(url) .then((result) => dispatch(receiveSearchResults(result))) .catch(() => dispatch(searchFailed())); }; export const fetchGraph = () => (dispatch: Dispatch) => { dispatch(requestGraph()); return getFromApi("graph") .then((graph) => dispatch(receiveGraph(graph))) .catch(() => dispatch(graphLoadFailed())); }; export const loadInstanceList = (page?: number, sort?: InstanceSort) => ( dispatch: Dispatch, getState: () => AppState ) => { sort = sort || getState().data.instanceListSort; dispatch(requestInstanceList(sort)); const params: string[] = []; if (page) { params.push(`page=${page}`); } if (sort) { params.push(`sortField=${sort.field}`); params.push(`sortDirection=${sort.direction}`); } const path = params ? `instances?${params.join("&")}` : "instances"; return getFromApi(path) .then((instancesListResponse) => dispatch(receiveInstanceList(instancesListResponse))) .catch(() => dispatch(instanceListLoadFailed())); };