diff --git a/src/components/WindowToolbar.tsx b/src/components/WindowToolbar.tsx
index b2b279d..5f6928f 100644
--- a/src/components/WindowToolbar.tsx
+++ b/src/components/WindowToolbar.tsx
@@ -7,6 +7,7 @@ import {
CopyCheck,
ArrowRightFromLine,
ExternalLink,
+ Plus,
} from "lucide-react";
import { useSetAtom } from "jotai";
import { useState } from "react";
@@ -44,24 +45,29 @@ export function WindowToolbar({
}: WindowToolbarProps) {
const setEditMode = useSetAtom(commandLauncherEditModeAtom);
const [showSpellDialog, setShowSpellDialog] = useState(false);
- const { state, moveWindowToWorkspace, setActiveWorkspace } = useGrimoire();
+ const { state, moveWindowToWorkspace, moveWindowToNewWorkspace } =
+ useGrimoire();
// Get workspaces for move action
const otherWorkspaces = Object.values(state.workspaces)
.filter((ws) => ws.id !== state.activeWorkspaceId)
.sort((a, b) => a.number - b.number);
- const hasMultipleWorkspaces = Object.keys(state.workspaces).length > 1;
const handleMoveToWorkspace = (targetWorkspaceId: string) => {
if (!window) return;
const targetWorkspace = state.workspaces[targetWorkspaceId];
moveWindowToWorkspace(window.id, targetWorkspaceId);
- setActiveWorkspace(targetWorkspaceId);
toast.success(
`Moved to tab ${targetWorkspace.number}${targetWorkspace.label ? ` (${targetWorkspace.label})` : ""}`,
);
};
+ const handleMoveToNewTab = () => {
+ if (!window) return;
+ moveWindowToNewWorkspace(window.id);
+ toast.success("Moved to new tab");
+ };
+
const handleEdit = () => {
if (!window) return;
@@ -195,31 +201,30 @@ export function WindowToolbar({
Pop out window
- {/* Move to tab submenu - only show if multiple workspaces */}
- {hasMultipleWorkspaces && (
- <>
-
-
-
+
+
+
+ Move to tab
+
+
+
+
+ New
+
+ {otherWorkspaces.length > 0 && }
+ {otherWorkspaces.map((ws) => (
+ handleMoveToWorkspace(ws.id)}
>
-
- Move to tab
-
-
- {otherWorkspaces.map((ws) => (
- handleMoveToWorkspace(ws.id)}
- >
- {ws.number}
- {ws.label ? ` ${ws.label}` : ""}
-
- ))}
-
-
- >
- )}
+ {ws.number}
+ {ws.label ? ` ${ws.label}` : ""}
+
+ ))}
+
+
{/* REQ/COUNT-specific actions */}
{isSpellableWindow && (
diff --git a/src/core/logic.ts b/src/core/logic.ts
index 07527c1..83aff8f 100644
--- a/src/core/logic.ts
+++ b/src/core/logic.ts
@@ -227,6 +227,52 @@ export const moveWindowToWorkspace = (
};
};
+/**
+ * Creates a new workspace and moves a window to it.
+ * Returns an object with the new state and the new workspace ID.
+ */
+export const moveWindowToNewWorkspace = (
+ state: GrimoireState,
+ windowId: string,
+): { state: GrimoireState; newWorkspaceId: string } => {
+ const currentId = state.activeWorkspaceId;
+ const currentWs = state.workspaces[currentId];
+
+ // Find next available workspace number
+ const nextNumber = findLowestAvailableWorkspaceNumber(state.workspaces);
+
+ // Create new workspace ID
+ const newWorkspaceId = uuidv4();
+
+ // Remove window from current workspace
+ const newCurrentLayout = removeFromLayout(currentWs.layout, windowId);
+ const newCurrentWindowIds = currentWs.windowIds.filter(
+ (id) => id !== windowId,
+ );
+
+ // Create new state with new workspace and moved window
+ const newState: GrimoireState = {
+ ...state,
+ activeWorkspaceId: newWorkspaceId,
+ workspaces: {
+ ...state.workspaces,
+ [currentId]: {
+ ...currentWs,
+ layout: newCurrentLayout,
+ windowIds: newCurrentWindowIds,
+ },
+ [newWorkspaceId]: {
+ id: newWorkspaceId,
+ number: nextNumber,
+ layout: windowId,
+ windowIds: [windowId],
+ },
+ },
+ };
+
+ return { state: newState, newWorkspaceId };
+};
+
export const updateLayout = (
state: GrimoireState,
layout: MosaicNode | null,
diff --git a/src/core/state.ts b/src/core/state.ts
index 98f8618..6775b1f 100644
--- a/src/core/state.ts
+++ b/src/core/state.ts
@@ -236,6 +236,20 @@ export const useGrimoire = () => {
[setState],
);
+ const moveWindowToNewWorkspace = useCallback(
+ (windowId: string): number => {
+ let newWorkspaceNumber = 0;
+ setState((prev) => {
+ const result = Logic.moveWindowToNewWorkspace(prev, windowId);
+ newWorkspaceNumber =
+ result.state.workspaces[result.newWorkspaceId].number;
+ return result.state;
+ });
+ return newWorkspaceNumber;
+ },
+ [setState],
+ );
+
const updateLayout = useCallback(
(layout: any) => {
setState((prev) => Logic.updateLayout(prev, layout));
@@ -385,6 +399,7 @@ export const useGrimoire = () => {
updateWindow,
removeWindow,
moveWindowToWorkspace,
+ moveWindowToNewWorkspace,
updateLayout,
setActiveWorkspace,
setActiveAccount,