Skip to content

Instantly share code, notes, and snippets.

@fredrikekelund
Created January 15, 2025 15:20
Redux store
import { configureStore, AnyAction } from '@reduxjs/toolkit';
import * as Sentry from '@sentry/electron/renderer';
import { ThunkAction, ThunkDispatch } from 'redux-thunk';
import { DEFAULT_PHP_VERSION } from '../../vendor/wp-now/src/constants';
import { getIpcApi } from '../lib/get-ipc-api';
// Types
type WPCliItem = { name: string };
export interface ChatState {
// Site details
currentURL: string;
pluginList: string[];
themeList: string[];
numberOfSites: number;
phpVersion: string;
siteName: string;
isSiteLoadedDict: Record< string, boolean >;
// Theme details
themeName: string;
isBlockTheme: boolean;
// System details
os: string;
availableEditors: string[];
wpVersion: string;
}
// Action Types
export const SET_SITE_INFO = 'chat/SET_SITE_INFO';
export const SET_LISTS = 'chat/SET_LISTS';
export const SET_SITE_LOADED_STATUS = 'chat/SET_SITE_LOADED_STATUS';
export const UPDATE_THEME = 'chat/UPDATE_THEME';
// Action Interfaces
interface SetSiteInfoAction {
type: typeof SET_SITE_INFO;
payload: {
currentURL: string;
phpVersion: string;
siteName: string;
isSiteLoadedDict: Record< string, boolean >;
};
}
interface SetListsAction {
type: typeof SET_LISTS;
payload: {
plugins: string[];
themes: string[];
};
}
interface SetSiteLoadedStatusAction {
type: typeof SET_SITE_LOADED_STATUS;
payload: {
siteId: string;
isLoaded: boolean;
};
}
interface UpdateThemeAction {
type: typeof UPDATE_THEME;
payload: {
name: string;
isBlockTheme: boolean;
};
}
type ChatActionTypes =
| SetSiteInfoAction
| SetListsAction
| SetSiteLoadedStatusAction
| UpdateThemeAction;
// Action Creators
export const setSiteInfo = (
currentURL: string,
phpVersion: string,
siteName: string,
isSiteLoadedDict: Record< string, boolean >
): SetSiteInfoAction => ( {
type: SET_SITE_INFO,
payload: { currentURL, phpVersion, siteName, isSiteLoadedDict },
} );
export const setLists = ( plugins: string[], themes: string[] ): SetListsAction => ( {
type: SET_LISTS,
payload: { plugins, themes },
} );
export const setSiteLoadedStatus = (
siteId: string,
isLoaded: boolean
): SetSiteLoadedStatusAction => ( {
type: SET_SITE_LOADED_STATUS,
payload: { siteId, isLoaded },
} );
export const updateTheme = ( name: string, isBlockTheme: boolean ): UpdateThemeAction => ( {
type: UPDATE_THEME,
payload: { name, isBlockTheme },
} );
// Async Action Creators
export const fetchPluginList = (
siteId: string
): ThunkAction< Promise< string[] >, ChatState, unknown, AnyAction > => {
return async () => {
const { stdout, stderr } = await getIpcApi().executeWPCLiInline( {
siteId,
args: 'plugin list --format=json --status=active',
skipPluginsAndThemes: true,
} );
if ( stderr ) {
return [];
}
try {
const data = JSON.parse( stdout );
return data?.map( ( item: WPCliItem ) => item.name ) || [];
} catch ( error ) {
Sentry.captureException( error, { extra: { stdout } } );
return [];
}
};
};
export const fetchThemeList = (
siteId: string
): ThunkAction< Promise< string[] >, ChatState, unknown, AnyAction > => {
return async () => {
const { stdout, stderr } = await getIpcApi().executeWPCLiInline( {
siteId,
args: 'theme list --format=json',
skipPluginsAndThemes: true,
} );
if ( stderr ) {
return [];
}
try {
const data = JSON.parse( stdout );
return data?.map( ( item: WPCliItem ) => item.name ) || [];
} catch ( error ) {
Sentry.captureException( error, { extra: { stdout } } );
return [];
}
};
};
export const updateFromSite = (
site: SiteDetails
): ThunkAction< Promise< void >, ChatState, unknown, AnyAction > => {
return async ( dispatch: ThunkDispatch< ChatState, unknown, AnyAction > ) => {
dispatch(
setSiteInfo(
`http://localhost:${ site.port }`,
site.phpVersion ?? DEFAULT_PHP_VERSION,
site.name,
{ [ site.id ]: true }
)
);
try {
const [ plugins, themes ] = await Promise.all( [
dispatch( fetchPluginList( site.id ) ),
dispatch( fetchThemeList( site.id ) ),
] );
dispatch( setLists( plugins, themes ) );
} catch ( error ) {
dispatch( setSiteLoadedStatus( site.id, false ) );
}
};
};
// Initial State
const initialState: ChatState = {
currentURL: '',
pluginList: [],
themeList: [],
numberOfSites: 0,
themeName: '',
wpVersion: '',
phpVersion: DEFAULT_PHP_VERSION,
isBlockTheme: false,
os: window.appGlobals?.platform || '',
availableEditors: [],
siteName: '',
isSiteLoadedDict: {},
};
// Reducer
function chatReducer( state = initialState, action: ChatActionTypes ): ChatState {
switch ( action.type ) {
case SET_SITE_INFO:
return {
...state,
currentURL: action.payload.currentURL,
phpVersion: action.payload.phpVersion,
siteName: action.payload.siteName,
isSiteLoadedDict: { ...state.isSiteLoadedDict, ...action.payload.isSiteLoadedDict },
};
case SET_LISTS:
return {
...state,
pluginList: action.payload.plugins,
themeList: action.payload.themes,
};
case SET_SITE_LOADED_STATUS:
return {
...state,
isSiteLoadedDict: {
...state.isSiteLoadedDict,
[ action.payload.siteId ]: action.payload.isLoaded,
},
};
case UPDATE_THEME:
return {
...state,
themeName: action.payload.name,
isBlockTheme: action.payload.isBlockTheme,
};
default:
return state;
}
}
// Store
export const chatStore = configureStore( {
reducer: chatReducer,
} );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment