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.