Defining custom actions
File action definitions
File actions are JS objects that follow the FileAction interface. You can see all
supported fields in the FileAction API reference. I suggest you
check out the API reference before reading this page to get more context. Note that id
is the only required file action field - all other fields are strictly optional.
In their essence, file action definitions are plain JS objects, but it is recommended to
define them using the defineFileAction helper method. It does some runtime checks and
Typescript magic to produce a type-safe file action. The simplified signature of this
method looks like this:
defineFileAction(action: FileAction, effect?: FileActionEffect) => FileAction;
You can ignore the effect parameter for now - it's an advanced feature, and
understanding it requires some more context about how Chonky works. The Understanding
effects page talks about it in detail.
Let's look at a real example. Below you can see the definition for the SortFilesBySize
file action. It's a real built-in file action that is enabled by default.
import { defineFileAction, FileData } from 'chonky';import { Nullable } from 'tsdef';const SortFilesBySize = defineFileAction({id: 'sort_files_by_size',sortKeySelector: (file: Nullable<FileData>) => (file ? file.size : undefined),button: {name: 'Sort by size',toolbar: true,group: 'Options',},} as const);
As you can see from the definition, it provides a button field with button.toolbar
set to true, meaning the button for this action will appear in the toolbar. It also
defines a sortKeySelector method, which will be used to sort the files when user
triggers the action by pressing its toolbar button.
After the action is triggered, Chonky starts processing it internally. Chonky executes the action effect along with other operations the action might define - sorting, changing file view, updating selection, etc. Finally, when all of this internal processing is finished, Chonky calls the user-defined file action handler.
What is as const?
If you're new to Typescript, the as const suffix after the object definition in the
example above might seem unusual to you:
{id: 'sort_files_by_size',// ...} as const
The as const suffix represents a const assertion. It tells
Typescript to bake-in the exact type of the object, which helps you write type-safe code
later down the line.
Defining your own action
Let's assume you want to define a new file action. You can start from this very simple (yet valid) definition:
import { defineFileAction } from 'chonky';const action = defineFileAction({id: 'my_custom_action',// other fields will go here});
For now, the only field that is defined on your action is action.id. The next few
sections on this page will walk you through all the different fields you can define on
your action. You can also read the FileAction API reference for a
short description of each field.
Specifying action triggers
You can explicitly specify three ways to trigger your action - via a keyboard shortcut, via a toolbar button, or via a context menu button. There are some other ways to trigger actions as covered on Triggering actions page, but you don't have control over them in your action definition.
action.button
Setting the button property in your action definition will add a button to
Chonky UI that can be used to trigger your action. The value for the button field
should be an object following the FileActionButton interface:
export interface FileActionButton {name: string; // Button nametoolbar?: boolean; // Whether to show the button in the toolbarcontextMenu?: boolean; // Whether to show the button in the context menugroup?: string; // Button group (dropdown in toolbar or section in context menu)tooltip?: string; // Help tooltip texticon?: ChonkyIconName | string | any; // Icon nameiconOnly?: boolean; // Whether to only display the icon}
Note that you should set either button.toolbar or button.contextMenu (or both) to
true for your button to actually appear in the UI. The icon field follows Chonky's
approach to icons, as laid out on Using icons page.
action.hotkeys
Keyboard shortcuts are defined using the hotkeys field. It should be an array of
strings, where each string represents a shortcut, e.g. ctrl+q or just space. See
hotkeys-js documentation for more information on how to define keys.
Please respect your users' needs when defining custom shortcuts. For example, don't
override the ctrl+r hotkey that already has a well-established meaning (reload page).
action.requiresSelection and action.fileFilter
There are actions that only make sense when user has selected some files - like
downloading or deleting files. For such actions, you can set requiresSelection field
to true - this will prevent the action from being triggered until user makes a
non-empty selection. Action buttons will also be rendered in disabled state.
If you want to narrow down the selection for a particular action, you can specify the
fileFilter field. It's a predicate function that will be used to filter selected
files. It should follow the FileFilter type:
export type FileFilter = (file: Nullable<FileData>) => boolean;
Action triggers example
We define two actions, both of which have toolbar and context menu buttons. You can
right click on a file entry to bring up the context menu. Note that Delete hidden
files is disabled until you select a hidden file (or right-click on it). You can also
try pressing Ctrl+O or Ctrl+U on your keyboard to activate the actions.
Changing internal Chonky state: Basics
"Internal Chonky state" includes a lot of things, some of which are pretty low-level and complicated. However, there are also simpler concepts like current sorting function, current file view (grid or list), boolean options, and current file selection. The file action framework exposes simple mechanisms for you to change these 4 parts of Chonky's state.
action.sortKeySelector
sortKeySelector should follow the FileSortKeySelector type shown below. It is a
function used to determine what key your files will be sorted on. If you specify a
sortKeySelector, when your action is activated for the first time, the files will be
sorted on your sort key in ascending order. On subsequent activations of your action,
sort order will be toggled between ascending and descending.
export type FileSortKeySelector = (file: Nullable<FileData>) => any;
action.fileViewConfig
fileViewConfig should follow the FileViewConfig type shown below. It is used to
switch between List and Grid views, as well as to set things like list row height or
grid thumbnail size. Note that FileViewMode.Compact is still experimental, and should
not be used in production.
export type FileViewConfig = FileViewConfigList | FileViewConfigGrid;export enum FileViewMode {List = 'list',Compact = 'compact',Grid = 'grid',}export type FileViewConfigList = {mode: FileViewMode.List;entryHeight: number;};export type FileViewConfigGrid = {mode: FileViewMode.Compact | FileViewMode.Grid;entryWidth: number;entryHeight: number;};
action.option
option should follow the FileActionOption type shown below. It represents some
boolean flag associated with your action. Internal Chonky components or effects of other
actions can use this boolean flag to change their behaviour. option.id will be used to
represent this flag in Chonky's internal state. The flag will be set to the
option.defaultValue you provided on initial component mount. On every activation of
your action, the flag will be toggled.
export interface FileActionOption {id: string; // Unique option IDdefaultValue: boolean; // Whether the option is enabled by default (required)}
action.selectionTransform
selectionTransform should follow the FileSelectionTransform type shown below. As the
name implies, it can be used to transform the selection in some way. The obvious use
cases are to select all files or clear the selection (and there are default built-in
actions that do exactly that). However, you can also get much more creative, like the
select the most recently edited files.
Note that the file selection in this function is represented as an ES6 set.
If an ID of a particular file is in the set, it means this file is selected.
prevSelection represents the file selection before the transform was called, and your
selectionTransform function should either return the new selection or null if you
want to leave the selection unchanged.
There are 3 other parametrs provided for your convenience. fileIds represents the
IDs of all files supplied to Chonky. fileMap represents a mapping from file ID to
file object - this is useful if you want to check some property of the file, e.g.
fileMap[fileId].name === 'MyFile.txt'. hiddenFileIds represents all files that
currently not visible to the user, e.g. because Show hidden files option is false or
because user is currently using file search. For best user experience, you should avoid
selecting files in hiddenFileIds.
export type FileSelectionTransform = (data: {prevSelection: Set<string>;fileIds: ReadonlyArray<string>;fileMap: Readonly<FileMap>;hiddenFileIds: Set<string>;}) => Nullable<Set<string>>;
Example of changing Chonky's state
Changing internal Chonky state: Advanced topics
As stated before, the "advanced" way to change Chonky state is to use file action effects. See Understanding effects page for more information.
Action payload type and extra state type
You might have noticed the action.__payloadType and action.__extraStateType fields.
Extra state is still an experimental feature, so you can ignore it for now. On the other
hand, the payload type is already used by some built-in actions. It is used in part
for ensuring type-safe definitions and in part for runtime payload validation.
Note that actions that define a payload type cannot have action.button or
action.hotkeys specified, because buttons and hotkeys can only dispatch actions
with a non-null payload.
The way to define action.__payloadType is slightly unusual. You must ensure that
Typescript infers the correct type for that field. Consider the definition for the
OpenFiles action:
import { defineFileAction } from 'chonky';import { OpenFilesPayload } from 'chonky/lib/types/action-payloads.types';const OpenFiles = defineFileAction({id: 'open_files',__payloadType: {} as OpenFilesPayload,} as const);
If we wouldn't provide as OpenFilesPayload suffix, Typescript would infer the type of
__payloadType as an empty object. As an aside: in theory, the payload could be of any
type, but it's best stick to plain JS objects.