// 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 colors = [ "#58C142", // Green (accent) "#00FFDB", // Teal "#429DFF", // Blue "#BF55EC", // Purple "#FF453A", // Red "#FF9500", // Orange "#FFE900", // Yellow ]; const icons = [ "custom@wave-logo-solid", "triangle", "star", "heart", "bolt", "solid@cloud", "moon", "layer-group", "rocket", "flask", "paperclip", "chart-line", "graduation-cap", "mug-hot", ]; 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, true); return ( handleIconClick(icon)} /> ); })}
); }); interface WorkspaceEditorProps { title: string; icon: string; color: string; focusInput: boolean; onTitleChange: (newTitle: string) => void; onColorChange: (newColor: string) => void; onIconChange: (newIcon: string) => void; onDeleteWorkspace: () => void; } const WorkspaceEditor = memo( ({ title, icon, color, focusInput, onTitleChange, onColorChange, onIconChange, onDeleteWorkspace, }: WorkspaceEditorProps) => { const inputRef = useRef(null); useEffect(() => { if (focusInput && inputRef.current) { inputRef.current.focus(); inputRef.current.select(); } }, [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 (${activeWorkspace.oid.slice(0, 5)})`, icon: icons[0], color: colors[0], }, undefined, true ); setTimeout(() => { fireAndForget(updateWorkspaceList); }, 10); setEditingWorkspace(activeWorkspace.oid); }; 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 };