// Copyright 2024, Command Line
// SPDX-License-Identifier: Apache-2.0
import { Button } from "@/element/button";
import {
ExpandableMenu,
ExpandableMenuItem,
ExpandableMenuItemGroup,
ExpandableMenuItemGroupTitle,
ExpandableMenuItemLeftElement,
ExpandableMenuItemRightElement,
} from "@/element/expandablemenu";
import { Input } from "@/element/input";
import { Popover, PopoverButton, PopoverContent } from "@/element/popover";
import { fireAndForget, makeIconClass, useAtomValueSafe } from "@/util/util";
import clsx from "clsx";
import { atom, PrimitiveAtom, useAtom, useAtomValue, useSetAtom } from "jotai";
import { splitAtom } from "jotai/utils";
import { OverlayScrollbarsComponent } from "overlayscrollbars-react";
import { CSSProperties, memo, useCallback, useEffect, useRef } from "react";
import WorkspaceSVG from "../asset/workspace.svg";
import { IconButton } from "../element/iconbutton";
import { atoms, getApi } from "../store/global";
import { WorkspaceService } from "../store/services";
import { getObjectValue, makeORef, setObjectValue } from "../store/wos";
import "./workspaceswitcher.scss";
interface ColorSelectorProps {
colors: string[];
selectedColor?: string;
onSelect: (color: string) => void;
className?: string;
}
const ColorSelector = memo(({ colors, selectedColor, onSelect, className }: ColorSelectorProps) => {
const handleColorClick = (color: string) => {
onSelect(color);
};
return (
{colors.map((color) => (
handleColorClick(color)}
/>
))}
);
});
interface IconSelectorProps {
icons: string[];
selectedIcon?: string;
onSelect: (icon: string) => void;
className?: string;
}
const IconSelector = memo(({ icons, selectedIcon, onSelect, className }: IconSelectorProps) => {
const handleIconClick = (icon: string) => {
onSelect(icon);
};
return (
{icons.map((icon) => {
const iconClass = makeIconClass(icon, false);
return (
handleIconClick(icon)}
/>
);
})}
);
});
interface ColorAndIconSelectorProps {
title: string;
icon: string;
color: string;
focusInput: boolean;
onTitleChange: (newTitle: string) => void;
onColorChange: (newColor: string) => void;
onIconChange: (newIcon: string) => void;
onDeleteWorkspace: () => void;
}
const ColorAndIconSelector = memo(
({
title,
icon,
color,
focusInput,
onTitleChange,
onColorChange,
onIconChange,
onDeleteWorkspace,
}: ColorAndIconSelectorProps) => {
const inputRef = useRef
(null);
useEffect(() => {
if (focusInput && inputRef.current) {
inputRef.current.focus();
}
}, [focusInput]);
return (
);
}
);
type WorkspaceListEntry = {
windowId: string;
workspace: Workspace;
};
type WorkspaceList = WorkspaceListEntry[];
const workspaceMapAtom = atom([]);
const workspaceSplitAtom = splitAtom(workspaceMapAtom);
const editingWorkspaceAtom = atom();
const WorkspaceSwitcher = () => {
const setWorkspaceList = useSetAtom(workspaceMapAtom);
const activeWorkspace = useAtomValueSafe(atoms.workspace);
const workspaceList = useAtomValue(workspaceSplitAtom);
const setEditingWorkspace = useSetAtom(editingWorkspaceAtom);
const updateWorkspaceList = useCallback(async () => {
const workspaceList = await WorkspaceService.ListWorkspaces();
if (!workspaceList) {
return;
}
const newList: WorkspaceList = [];
for (const entry of workspaceList) {
// This just ensures that the atom exists for easier setting of the object
getObjectValue(makeORef("workspace", entry.workspaceid));
newList.push({
windowId: entry.windowid,
workspace: await WorkspaceService.GetWorkspace(entry.workspaceid),
});
}
setWorkspaceList(newList);
}, []);
useEffect(() => {
fireAndForget(updateWorkspaceList);
}, []);
const onDeleteWorkspace = useCallback((workspaceId: string) => {
getApi().deleteWorkspace(workspaceId);
setTimeout(() => {
fireAndForget(updateWorkspaceList);
}, 10);
}, []);
const isActiveWorkspaceSaved = !!(activeWorkspace.name && activeWorkspace.icon);
const workspaceIcon = isActiveWorkspaceSaved ? (
) : (
);
const saveWorkspace = () => {
setObjectValue({ ...activeWorkspace, name: "New Workspace", icon: "circle", color: "green" }, undefined, true);
setTimeout(() => {
fireAndForget(updateWorkspaceList);
}, 10);
};
return (
setEditingWorkspace(null)}
>
{
fireAndForget(updateWorkspaceList);
}}
>
{workspaceIcon}
{isActiveWorkspaceSaved ? "Switch workspace" : "Open workspace"}
{workspaceList.map((entry, i) => (
))}
{isActiveWorkspaceSaved ? (
getApi().createWorkspace()}>
Create new workspace
) : (
saveWorkspace()}>
Save workspace
)}
);
};
const WorkspaceSwitcherItem = ({
entryAtom,
onDeleteWorkspace,
}: {
entryAtom: PrimitiveAtom;
onDeleteWorkspace: (workspaceId: string) => void;
}) => {
const activeWorkspace = useAtomValueSafe(atoms.workspace);
const [workspaceEntry, setWorkspaceEntry] = useAtom(entryAtom);
const [editingWorkspace, setEditingWorkspace] = useAtom(editingWorkspaceAtom);
const workspace = workspaceEntry.workspace;
const isCurrentWorkspace = activeWorkspace.oid === workspace.oid;
const setWorkspace = useCallback((newWorkspace: Workspace) => {
if (newWorkspace.name != "") {
setObjectValue({ ...newWorkspace, otype: "workspace" }, undefined, true);
}
setWorkspaceEntry({ ...workspaceEntry, workspace: newWorkspace });
}, []);
const isActive = !!workspaceEntry.windowId;
const editIconDecl: IconButtonDecl = {
elemtype: "iconbutton",
className: "edit",
icon: "pencil",
title: "Edit workspace",
click: (e) => {
e.stopPropagation();
if (editingWorkspace === workspace.oid) {
setEditingWorkspace(null);
} else {
setEditingWorkspace(workspace.oid);
}
},
};
const windowIconDecl: IconButtonDecl = {
elemtype: "iconbutton",
className: "window",
noAction: true,
icon: isCurrentWorkspace ? "check" : "window",
title: isCurrentWorkspace ? "This is your current workspace" : "This workspace is open",
};
const isEditing = editingWorkspace === workspace.oid;
return (
{
getApi().switchWorkspace(workspace.oid);
// Create a fake escape key event to close the popover
document.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape" }));
}}
>
{workspace.name}
{isActive && }
setWorkspace({ ...workspace, name: title })}
onColorChange={(color) => setWorkspace({ ...workspace, color })}
onIconChange={(icon) => setWorkspace({ ...workspace, icon })}
onDeleteWorkspace={() => onDeleteWorkspace(workspace.oid)}
/>
);
};
export { WorkspaceSwitcher };