Skip to content

Commit 75f7509

Browse files
committed
feat: add quick actions management to MageForge
- Introduced `mageforge.quickActions` configuration to allow users to customize quick action buttons in the welcome view. - Implemented commands for adding, removing, and reordering quick actions. - Enhanced the welcome view to render configured quick actions and manage buttons based on the presence of a Magento root. - Added validation for quick actions to ensure they meet specified criteria. - Implemented drag-and-drop functionality for reordering quick actions in the UI. - Updated tests to cover new quick actions functionality and ensure proper behavior of commands.
1 parent fd23e53 commit 75f7509

6 files changed

Lines changed: 1077 additions & 16 deletions

File tree

package.json

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,69 @@
5959
"default": "php",
6060
"description": "PHP binary for local execution. Can be a full command like `docker-compose exec php` for custom setups.",
6161
"order": 4
62+
},
63+
"mageforge.quickActions": {
64+
"type": "array",
65+
"default": [
66+
{
67+
"label": "Build Theme",
68+
"command": "mageforge.theme.build",
69+
"icon": "hammer"
70+
},
71+
{
72+
"label": "Watch Theme",
73+
"command": "mageforge.theme.watch",
74+
"icon": "eye"
75+
},
76+
{
77+
"label": "Inspector",
78+
"command": "mageforge.theme.inspector",
79+
"icon": "search"
80+
},
81+
{
82+
"label": "Hyvä Check",
83+
"command": "mageforge.hyva.compatibilityCheck",
84+
"icon": "checklist"
85+
},
86+
{
87+
"label": "Feature Request",
88+
"url": "https://github.com/OpenForgeProject/mageforge-vscode/issues/new?template=feature_request.md",
89+
"icon": "bulb"
90+
},
91+
{
92+
"label": "Rate",
93+
"url": "https://marketplace.visualstudio.com/items?itemName=OpenForgeProject.mageforge&ssr=false#review-details",
94+
"icon": "star"
95+
}
96+
],
97+
"items": {
98+
"type": "object",
99+
"required": [
100+
"label",
101+
"icon"
102+
],
103+
"properties": {
104+
"label": {
105+
"type": "string",
106+
"description": "Display label of the quick action button."
107+
},
108+
"command": {
109+
"type": "string",
110+
"description": "VS Code command to execute when clicked (e.g. `mageforge.theme.build`)."
111+
},
112+
"url": {
113+
"type": "string",
114+
"description": "External URL to open when clicked. Only `https:` links are allowed."
115+
},
116+
"icon": {
117+
"type": "string",
118+
"description": "Tabler icon name without the `ti-` prefix (e.g. `hammer`)."
119+
}
120+
},
121+
"additionalProperties": false
122+
},
123+
"markdownDescription": "Buttons shown in the MageForge Welcome view under **Quick Actions**. Either `command` or `url` must be provided. Commands are limited to the `mageforge.` namespace.",
124+
"order": 5
62125
}
63126
}
64127
},
@@ -182,6 +245,28 @@
182245
"category": "MageForge",
183246
"icon": "$(refresh)"
184247
},
248+
{
249+
"command": "mageforge.addQuickAction",
250+
"title": "Add Quick Action",
251+
"category": "MageForge",
252+
"icon": "$(add)"
253+
},
254+
{
255+
"command": "mageforge.removeQuickAction",
256+
"title": "Remove Quick Action",
257+
"category": "MageForge"
258+
},
259+
{
260+
"command": "mageforge.reorderQuickAction",
261+
"title": "Reorder Quick Action",
262+
"category": "MageForge"
263+
},
264+
{
265+
"command": "mageforge.settingsQuickActions",
266+
"title": "Quick Actions Settings",
267+
"category": "MageForge",
268+
"icon": "$(settings)"
269+
},
185270
{
186271
"command": "mageforge.template.overrideFile",
187272
"title": "Override File…",
@@ -210,6 +295,22 @@
210295
{
211296
"command": "mageforge.refreshThemes",
212297
"when": "false"
298+
},
299+
{
300+
"command": "mageforge.addQuickAction",
301+
"when": "false"
302+
},
303+
{
304+
"command": "mageforge.removeQuickAction",
305+
"when": "false"
306+
},
307+
{
308+
"command": "mageforge.reorderQuickAction",
309+
"when": "false"
310+
},
311+
{
312+
"command": "mageforge.settingsQuickActions",
313+
"when": "false"
213314
}
214315
],
215316
"explorer/context": [

src/extension.ts

Lines changed: 189 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import * as path from 'node:path';
22
import * as vscode from 'vscode';
3-
import { CommandsProvider, MAGEFORGE_COMMANDS, MageforgeCommand } from './commandsProvider';
3+
import {
4+
CommandsProvider,
5+
getAvailableMageforgeCommands,
6+
MAGEFORGE_COMMANDS,
7+
MageforgeCommand,
8+
} from './commandsProvider';
49
import { ThemeTreeItem, ThemesProvider } from './themesProvider';
510
import { WelcomeViewProvider } from './welcomeProvider';
611
import { ChangelogViewProvider } from './changelogProvider';
@@ -67,6 +72,17 @@ export function activate(context: vscode.ExtensionContext) {
6772
commandsProvider.refresh(),
6873
),
6974
vscode.commands.registerCommand('mageforge.refreshThemes', () => themesProvider.refresh()),
75+
vscode.commands.registerCommand('mageforge.addQuickAction', () => addQuickAction()),
76+
vscode.commands.registerCommand('mageforge.removeQuickAction', (index: number) =>
77+
removeQuickAction(index),
78+
),
79+
vscode.commands.registerCommand('mageforge.settingsQuickActions', () =>
80+
settingsQuickActions(),
81+
),
82+
vscode.commands.registerCommand(
83+
'mageforge.reorderQuickAction',
84+
(fromIndex: number, toIndex: number) => reorderQuickAction(fromIndex, toIndex),
85+
),
7086
vscode.commands.registerCommand(
7187
'mageforge.template.overrideFile',
7288
async (uri?: vscode.Uri) => {
@@ -90,6 +106,178 @@ export function activate(context: vscode.ExtensionContext) {
90106
);
91107
}
92108

109+
/**
110+
* Let the user pick a MageForge command that is not already a quick action and
111+
* append it to the `mageforge.quickActions` setting.
112+
*/
113+
async function addQuickAction(): Promise<void> {
114+
const magentoRoot = getMagentoRoot();
115+
if (!magentoRoot) {
116+
void vscode.window.showErrorMessage('MageForge: No workspace folder open.');
117+
return;
118+
}
119+
120+
const config = vscode.workspace.getConfiguration('mageforge');
121+
const currentActions = config.get<
122+
{ label: string; command?: string; url?: string; icon: string }[]
123+
>('quickActions', []);
124+
125+
let availableCliCommands: string[];
126+
try {
127+
availableCliCommands = await getAvailableMageforgeCommands(magentoRoot);
128+
} catch {
129+
void vscode.window.showErrorMessage(
130+
'MageForge: Could not load available commands. Is the MageForge CLI installed?',
131+
);
132+
return;
133+
}
134+
135+
const commandByCli = new Map(MAGEFORGE_COMMANDS.map((cmd) => [cmd.cliCommand, cmd]));
136+
const existingCommands = new Set(
137+
currentActions.filter((a) => a.command).map((a) => a.command!),
138+
);
139+
140+
const choices = availableCliCommands
141+
.map((cliCommand) => commandByCli.get(cliCommand))
142+
.filter((cmd): cmd is MageforgeCommand => Boolean(cmd))
143+
.filter((cmd) => !existingCommands.has(cmd.id))
144+
.map((cmd) => ({
145+
label: cmd.label,
146+
description: cmd.description,
147+
mageforgeCommand: cmd,
148+
}));
149+
150+
if (choices.length === 0) {
151+
void vscode.window.showInformationMessage(
152+
'MageForge: All available commands are already quick actions.',
153+
);
154+
return;
155+
}
156+
157+
const picked = await vscode.window.showQuickPick(choices, {
158+
placeHolder: 'Select a command to add as quick action',
159+
});
160+
if (!picked) {
161+
return;
162+
}
163+
164+
const icon = await pickQuickActionIcon(picked.mageforgeCommand.label);
165+
if (!icon) {
166+
return;
167+
}
168+
169+
const newAction = {
170+
label: picked.mageforgeCommand.label,
171+
command: picked.mageforgeCommand.id,
172+
icon,
173+
};
174+
175+
await config.update(
176+
'quickActions',
177+
[...currentActions, newAction],
178+
vscode.ConfigurationTarget.Workspace,
179+
);
180+
void vscode.window.showInformationMessage(
181+
`MageForge: Added "${picked.mageforgeCommand.label}" to quick actions.`,
182+
);
183+
}
184+
185+
/**
186+
* Reorder quick actions by moving an item from one index to another.
187+
*/
188+
async function reorderQuickAction(fromIndex: number, toIndex: number): Promise<void> {
189+
const config = vscode.workspace.getConfiguration('mageforge');
190+
const currentActions = config.get<
191+
{ label: string; command?: string; url?: string; icon: string }[]
192+
>('quickActions', []);
193+
194+
if (
195+
fromIndex < 0 ||
196+
fromIndex >= currentActions.length ||
197+
toIndex < 0 ||
198+
toIndex >= currentActions.length ||
199+
fromIndex === toIndex
200+
) {
201+
return;
202+
}
203+
204+
const updated = [...currentActions];
205+
const [moved] = updated.splice(fromIndex, 1);
206+
updated.splice(toIndex, 0, moved);
207+
208+
await config.update('quickActions', updated, vscode.ConfigurationTarget.Workspace);
209+
}
210+
211+
/**
212+
* Remove a quick action by its index in the settings array.
213+
*/
214+
async function removeQuickAction(index: number): Promise<void> {
215+
const config = vscode.workspace.getConfiguration('mageforge');
216+
const currentActions = config.get<
217+
{ label: string; command?: string; url?: string; icon: string }[]
218+
>('quickActions', []);
219+
220+
if (index < 0 || index >= currentActions.length) {
221+
return;
222+
}
223+
224+
const removed = currentActions[index];
225+
const updated = [...currentActions.slice(0, index), ...currentActions.slice(index + 1)];
226+
227+
await config.update('quickActions', updated, vscode.ConfigurationTarget.Workspace);
228+
void vscode.window.showInformationMessage(
229+
`MageForge: Removed "${removed.label}" from quick actions.`,
230+
);
231+
}
232+
233+
/**
234+
* Open settings.json with the mageforge.quickActions key focused so the user
235+
* can reorder, rename and change icons manually.
236+
*/
237+
async function settingsQuickActions(): Promise<void> {
238+
await vscode.commands.executeCommand('workbench.action.openSettings', 'mageforge');
239+
}
240+
241+
/**
242+
* Let the user pick a Tabler icon for the new quick action.
243+
* Defaults to the command's suggested icon.
244+
*/
245+
async function pickQuickActionIcon(commandLabel: string): Promise<string | undefined> {
246+
const icons = [
247+
{ label: 'Hammer', icon: 'hammer' },
248+
{ label: 'Eye', icon: 'eye' },
249+
{ label: 'Search', icon: 'search' },
250+
{ label: 'Checklist', icon: 'checklist' },
251+
{ label: 'Tools', icon: 'tools' },
252+
{ label: 'Trash', icon: 'trash' },
253+
{ label: 'List', icon: 'list-unordered' },
254+
{ label: 'Copy', icon: 'copy' },
255+
{ label: 'Cloud Download', icon: 'cloud-download' },
256+
{ label: 'Pulse', icon: 'pulse' },
257+
{ label: 'Info', icon: 'info' },
258+
{ label: 'Star', icon: 'star' },
259+
{ label: 'Bulb', icon: 'bulb' },
260+
{ label: 'Color', icon: 'symbol-color' },
261+
{ label: 'Refresh', icon: 'refresh' },
262+
{ label: 'Shield', icon: 'shield' },
263+
{ label: 'Package', icon: 'package' },
264+
{ label: 'Rocket', icon: 'rocket' },
265+
{ label: 'Settings', icon: 'settings' },
266+
{ label: 'Terminal', icon: 'terminal' },
267+
];
268+
269+
const choices = icons.map((item) => ({
270+
label: `$(${item.icon}) ${item.label}`,
271+
icon: item.icon,
272+
}));
273+
274+
const picked = await vscode.window.showQuickPick(choices, {
275+
placeHolder: `Select an icon for "${commandLabel}"`,
276+
});
277+
278+
return picked?.icon;
279+
}
280+
93281
/**
94282
* Shows an update notification and opens the changelog tab when the extension
95283
* version has changed since the last activation.

0 commit comments

Comments
 (0)