mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-06-06 02:31:13 +02:00
feat: editable commands
This commit is contained in:
16
src/core/command-launcher-state.ts
Normal file
16
src/core/command-launcher-state.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { atom } from "jotai";
|
||||
|
||||
/**
|
||||
* Edit mode state for CommandLauncher.
|
||||
* When set, CommandLauncher opens in edit mode for the specified window.
|
||||
*/
|
||||
export interface EditModeState {
|
||||
windowId: string;
|
||||
initialCommand: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Atom to control edit mode in CommandLauncher.
|
||||
* Set this to trigger edit mode, null for normal create mode.
|
||||
*/
|
||||
export const commandLauncherEditModeAtom = atom<EditModeState | null>(null);
|
||||
@@ -30,7 +30,7 @@ export const createWorkspace = (
|
||||
*/
|
||||
export const addWindow = (
|
||||
state: GrimoireState,
|
||||
payload: { appId: string; title: string; props: any },
|
||||
payload: { appId: string; title: string; props: any; commandString?: string },
|
||||
): GrimoireState => {
|
||||
const activeId = state.activeWorkspaceId;
|
||||
const ws = state.workspaces[activeId];
|
||||
@@ -40,6 +40,7 @@ export const addWindow = (
|
||||
appId: payload.appId as any,
|
||||
title: payload.title,
|
||||
props: payload.props,
|
||||
commandString: payload.commandString,
|
||||
};
|
||||
|
||||
// Simple Binary Split Logic
|
||||
@@ -263,3 +264,56 @@ export const setActiveAccountRelays = (
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Deletes a workspace by ID.
|
||||
* Cannot delete the last remaining workspace.
|
||||
* Does NOT change activeWorkspaceId - caller is responsible for workspace navigation.
|
||||
*/
|
||||
export const deleteWorkspace = (
|
||||
state: GrimoireState,
|
||||
workspaceId: string,
|
||||
): GrimoireState => {
|
||||
const workspaceIds = Object.keys(state.workspaces);
|
||||
|
||||
// Don't delete if it's the only workspace
|
||||
if (workspaceIds.length <= 1) {
|
||||
return state;
|
||||
}
|
||||
|
||||
// Don't delete if workspace doesn't exist
|
||||
if (!state.workspaces[workspaceId]) {
|
||||
return state;
|
||||
}
|
||||
|
||||
// Remove the workspace (don't touch activeWorkspaceId - that's the caller's job)
|
||||
const { [workspaceId]: _removed, ...remainingWorkspaces } = state.workspaces;
|
||||
|
||||
return {
|
||||
...state,
|
||||
workspaces: remainingWorkspaces,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates an existing window with new properties.
|
||||
* Allows updating props, title, commandString, and even appId (which changes the viewer type).
|
||||
*/
|
||||
export const updateWindow = (
|
||||
state: GrimoireState,
|
||||
windowId: string,
|
||||
updates: Partial<Pick<WindowInstance, "props" | "title" | "commandString" | "appId">>,
|
||||
): GrimoireState => {
|
||||
const window = state.windows[windowId];
|
||||
if (!window) {
|
||||
return state; // Window doesn't exist, return unchanged
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
windows: {
|
||||
...state.windows,
|
||||
[windowId]: { ...window, ...updates },
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect } from "react";
|
||||
import { useAtom } from "jotai";
|
||||
import { atomWithStorage, createJSONStorage } from "jotai/utils";
|
||||
import { GrimoireState, AppId } from "@/types/app";
|
||||
import { GrimoireState, AppId, WindowInstance } from "@/types/app";
|
||||
import { useLocale } from "@/hooks/useLocale";
|
||||
import * as Logic from "./logic";
|
||||
|
||||
@@ -82,14 +82,17 @@ export const useGrimoire = () => {
|
||||
const count = Object.keys(state.workspaces).length + 1;
|
||||
setState((prev) => Logic.createWorkspace(prev, count.toString()));
|
||||
},
|
||||
addWindow: (appId: AppId, props: any, title?: string) =>
|
||||
addWindow: (appId: AppId, props: any, title?: string, commandString?: string) =>
|
||||
setState((prev) =>
|
||||
Logic.addWindow(prev, {
|
||||
appId,
|
||||
props,
|
||||
title: title || appId.toUpperCase(),
|
||||
commandString,
|
||||
}),
|
||||
),
|
||||
updateWindow: (windowId: string, updates: Partial<Pick<WindowInstance, "props" | "title" | "commandString" | "appId">>) =>
|
||||
setState((prev) => Logic.updateWindow(prev, windowId, updates)),
|
||||
removeWindow: (id: string) =>
|
||||
setState((prev) => Logic.removeWindow(prev, id)),
|
||||
moveWindowToWorkspace: (windowId: string, targetWorkspaceId: string) =>
|
||||
@@ -99,7 +102,37 @@ export const useGrimoire = () => {
|
||||
updateLayout: (layout: any) =>
|
||||
setState((prev) => Logic.updateLayout(prev, layout)),
|
||||
setActiveWorkspace: (id: string) =>
|
||||
setState((prev) => ({ ...prev, activeWorkspaceId: id })),
|
||||
setState((prev) => {
|
||||
// Validate target workspace exists
|
||||
if (!prev.workspaces[id]) {
|
||||
console.warn(`Cannot switch to non-existent workspace: ${id}`);
|
||||
return prev;
|
||||
}
|
||||
|
||||
// If not actually switching, return unchanged
|
||||
if (prev.activeWorkspaceId === id) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
// Check if we're leaving an empty workspace and should auto-remove it
|
||||
const currentWorkspace = prev.workspaces[prev.activeWorkspaceId];
|
||||
const shouldDeleteCurrent =
|
||||
currentWorkspace &&
|
||||
currentWorkspace.windowIds.length === 0 &&
|
||||
Object.keys(prev.workspaces).length > 1;
|
||||
|
||||
if (shouldDeleteCurrent) {
|
||||
// Delete the empty workspace, then switch to target
|
||||
const afterDelete = Logic.deleteWorkspace(
|
||||
prev,
|
||||
prev.activeWorkspaceId,
|
||||
);
|
||||
return { ...afterDelete, activeWorkspaceId: id };
|
||||
}
|
||||
|
||||
// Normal workspace switch
|
||||
return { ...prev, activeWorkspaceId: id };
|
||||
}),
|
||||
setActiveAccount: (pubkey: string | undefined) =>
|
||||
setState((prev) => Logic.setActiveAccount(prev, pubkey)),
|
||||
setActiveAccountRelays: (relays: any) =>
|
||||
|
||||
Reference in New Issue
Block a user