-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
SY-1221 Temporarily Focus a Layout (#834)
* [console] - experiments with focusing * [console] - ability to temp focus a layout * [client/py] - removed file * [console] - updated overview page * [console] - improved collapse and expand tooling for multiple windows * [console] - minor aesthetic adjustments * [console] - adjusted tab selector * [console] - adjustements to portal based rendering * style: Remove dead code + more descriptive variable names * [pluto] - added 'double' option to triggers to allow for detecting double press/click * [console] - consolidated Mosaic component * [console] - fixed Control + R to rename --------- Co-authored-by: pjdotson <patrick@synnaxlabs.com>
- Loading branch information
Showing
96 changed files
with
2,181 additions
and
634 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
// Copyright 2024 Synnax Labs, Inc. | ||
// | ||
// Use of this software is governed by the Business Source License included in the file | ||
// licenses/BSL.txt. | ||
// | ||
// As of the Change Date specified in that file, in accordance with the Business Source | ||
// License, use of this software will be governed by the Apache License, Version 2.0, | ||
// included in the file licenses/APL.txt. | ||
|
||
import { MAIN_WINDOW } from "@synnaxlabs/drift"; | ||
import { useSelectWindowKey } from "@synnaxlabs/drift/react"; | ||
import { Icon } from "@synnaxlabs/media"; | ||
import { Menu, Mosaic, Text } from "@synnaxlabs/pluto"; | ||
import { direction } from "@synnaxlabs/x"; | ||
import { type FC, type ReactElement } from "react"; | ||
import { useDispatch, useStore } from "react-redux"; | ||
|
||
import { usePlacer, useRemover } from "@/layout/hooks"; | ||
import { useSelectMosaic } from "@/layout/selectors"; | ||
import { | ||
createMosaicWindow, | ||
moveMosaicTab, | ||
setFocus, | ||
splitMosaicNode, | ||
} from "@/layout/slice"; | ||
|
||
export interface FocusMenuItemProps { | ||
layoutKey: string; | ||
} | ||
|
||
export const FocusMenuItem = ({ layoutKey }: FocusMenuItemProps): ReactElement => { | ||
const dispatch = useDispatch(); | ||
const windowKey = useSelectWindowKey() as string; | ||
return ( | ||
<Menu.Item | ||
itemKey="focus" | ||
startIcon={<Icon.Focus />} | ||
onClick={() => dispatch(setFocus({ windowKey: windowKey, key: layoutKey }))} | ||
trigger={["Control", "F"]} | ||
> | ||
Focus | ||
</Menu.Item> | ||
); | ||
}; | ||
|
||
export const useOpenInNewWindow = () => { | ||
const dispatch = useDispatch(); | ||
const placer = usePlacer(); | ||
return (layoutKey: string) => { | ||
const { key } = placer(createMosaicWindow({})); | ||
dispatch( | ||
moveMosaicTab({ | ||
windowKey: key, | ||
key: 1, | ||
tabKey: layoutKey, | ||
loc: "center", | ||
}), | ||
); | ||
}; | ||
}; | ||
|
||
export const useMoveIntoMainWindow = () => { | ||
const store = useStore(); | ||
return (layoutKey: string) => { | ||
store.dispatch( | ||
moveMosaicTab({ | ||
windowKey: MAIN_WINDOW, | ||
tabKey: layoutKey, | ||
loc: "center", | ||
}), | ||
); | ||
}; | ||
}; | ||
|
||
export const OpenInNewWindowMenuItem = ({ | ||
layoutKey, | ||
}: FocusMenuItemProps): ReactElement | null => { | ||
const openInNewWindow = useOpenInNewWindow(); | ||
const isMain = useSelectWindowKey() === MAIN_WINDOW; | ||
if (!isMain) return null; | ||
return ( | ||
<Menu.Item | ||
itemKey="openInNewWindow" | ||
startIcon={<Icon.OpenInNewWindow />} | ||
onClick={() => openInNewWindow(layoutKey)} | ||
trigger={["Control", "O"]} | ||
> | ||
Open in New Window | ||
</Menu.Item> | ||
); | ||
}; | ||
|
||
export const MoveToMainWindowMenuItem = ({ | ||
layoutKey, | ||
}: FocusMenuItemProps): ReactElement | null => { | ||
const moveIntoMainWindow = useMoveIntoMainWindow(); | ||
const windowKey = useSelectWindowKey(); | ||
if (windowKey === MAIN_WINDOW) return null; | ||
return ( | ||
<Menu.Item | ||
itemKey="moveIntoMainWindow" | ||
startIcon={<Icon.OpenInNewWindow />} | ||
onClick={() => moveIntoMainWindow(layoutKey)} | ||
> | ||
Move to Main Window | ||
</Menu.Item> | ||
); | ||
}; | ||
|
||
export const CloseMenuItem = ({ layoutKey }: FocusMenuItemProps): ReactElement => { | ||
const remove = useRemover(); | ||
return ( | ||
<Menu.Item | ||
itemKey="close" | ||
startIcon={<Icon.Close />} | ||
onClick={() => remove(layoutKey)} | ||
trigger={["Control", "W"]} | ||
> | ||
Close | ||
</Menu.Item> | ||
); | ||
}; | ||
|
||
export const RenameMenuItem = ({ layoutKey }: FocusMenuItemProps): ReactElement => ( | ||
<Menu.Item | ||
itemKey="rename" | ||
startIcon={<Icon.Rename />} | ||
onClick={() => Text.edit(`pluto-tab-${layoutKey}`)} | ||
trigger={["Control", "R"]} | ||
> | ||
Rename | ||
</Menu.Item> | ||
); | ||
|
||
const splitMenuItemFactory = ( | ||
direction: direction.Direction, | ||
): FC<FocusMenuItemProps & { children?: ReactElement }> => { | ||
const C = ({ | ||
layoutKey, | ||
children, | ||
}: FocusMenuItemProps & { children?: ReactElement }) => { | ||
const dispatch = useDispatch(); | ||
const [windowKey, mosaic] = useSelectMosaic(); | ||
const canSplit = Mosaic.canSplit(mosaic, layoutKey); | ||
if (!canSplit) return null; | ||
return ( | ||
<> | ||
{children} | ||
<Menu.Item | ||
itemKey={`split${direction}`} | ||
startIcon={direction === "x" ? <Icon.SplitX /> : <Icon.SplitY />} | ||
onClick={() => | ||
dispatch(splitMosaicNode({ windowKey, tabKey: layoutKey, direction })) | ||
} | ||
> | ||
Split {direction === "x" ? "Horizontally" : "Vertically"} | ||
</Menu.Item> | ||
</> | ||
); | ||
}; | ||
C.displayName = `Split${direction.toUpperCase()}MenuItem`; | ||
return C; | ||
}; | ||
export const SplitXMenuItem = splitMenuItemFactory("x"); | ||
export const SplitYMenuItem = splitMenuItemFactory("y"); | ||
|
||
export interface MenuItems { | ||
layoutKey: string; | ||
} | ||
|
||
export const MenuItems = ({ layoutKey }: MenuItems): ReactElement => ( | ||
<> | ||
<RenameMenuItem layoutKey={layoutKey} /> | ||
<CloseMenuItem layoutKey={layoutKey} /> | ||
<Menu.Divider /> | ||
<FocusMenuItem layoutKey={layoutKey} /> | ||
<OpenInNewWindowMenuItem layoutKey={layoutKey} /> | ||
<MoveToMainWindowMenuItem layoutKey={layoutKey} /> | ||
<SplitXMenuItem layoutKey={layoutKey}> | ||
<Menu.Divider /> | ||
</SplitXMenuItem> | ||
<SplitYMenuItem layoutKey={layoutKey} /> | ||
</> | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
// Copyright 2024 Synnax Labs, Inc. | ||
// | ||
// Use of this software is governed by the Business Source License included in the file | ||
// licenses/BSL.txt. | ||
// | ||
// As of the Change Date specified in that file, in accordance with the Business Source | ||
// License, use of this software will be governed by the Apache License, Version 2.0, | ||
// included in the file licenses/APL.txt. | ||
|
||
import "@/layout/Modals.css"; | ||
|
||
import { Icon } from "@synnaxlabs/media"; | ||
import { Breadcrumb, Button, Menu, Modal as Core, Nav } from "@synnaxlabs/pluto"; | ||
import { CSSProperties } from "react"; | ||
|
||
import { Content } from "@/layout/Content"; | ||
import { State, WindowProps } from "@/layout/slice"; | ||
import { DefaultContextMenu } from "@/layout/Window"; | ||
|
||
const layoutCSS = (window?: WindowProps): CSSProperties => ({ | ||
width: "100%", | ||
height: "100%", | ||
maxWidth: window?.size?.width, | ||
maxHeight: window?.size?.height, | ||
minWidth: window?.minSize?.width, | ||
minHeight: window?.minSize?.height, | ||
}); | ||
|
||
interface ModalProps { | ||
state: State; | ||
remove: (key: string) => void; | ||
centered?: boolean; | ||
root?: string; | ||
} | ||
|
||
export const Modal = ({ state, remove, centered, root }: ModalProps) => { | ||
const { key, name, window, icon } = state; | ||
const menuProps = Menu.useContextMenu(); | ||
return ( | ||
<Menu.ContextMenu menu={() => <DefaultContextMenu />} {...menuProps}> | ||
<Core.Modal | ||
key={key} | ||
centered={centered} | ||
visible | ||
close={() => remove(key)} | ||
style={layoutCSS(window)} | ||
root={root} | ||
> | ||
{window?.navTop && ( | ||
<Nav.Bar location="top" size="6rem"> | ||
{(window?.showTitle ?? true) && ( | ||
<Nav.Bar.Start style={{ paddingLeft: "2rem" }}> | ||
<Breadcrumb.Breadcrumb icon={icon}>{name}</Breadcrumb.Breadcrumb> | ||
</Nav.Bar.Start> | ||
)} | ||
<Nav.Bar.End style={{ paddingRight: "1rem" }}> | ||
<Button.Icon onClick={() => remove(key)} size="small"> | ||
<Icon.Close style={{ color: "var(--pluto-gray-l8)" }} /> | ||
</Button.Icon> | ||
</Nav.Bar.End> | ||
</Nav.Bar> | ||
)} | ||
<Content layoutKey={key} /> | ||
</Core.Modal> | ||
</Menu.ContextMenu> | ||
); | ||
}; |
Oops, something went wrong.