diff --git a/src/components/general/computer-open-button.tsx b/src/components/general/computer-open-button.tsx new file mode 100644 index 0000000..1a362b9 --- /dev/null +++ b/src/components/general/computer-open-button.tsx @@ -0,0 +1,76 @@ +import { useState } from "react"; +import Button from "@moduletrace/twui/layout/Button"; +import type { + TWUIButtonColorType, + TWUIButtonSizeType, + TWUIButtonVariantType, +} from "@moduletrace/twui/layout/Button"; +import { AppWindow } from "lucide-react"; +import { twMerge } from "tailwind-merge"; +import ComputerViewerModal from "./computer-viewer-modal"; + +type Props = { + computer_id: number; + /** + * The computer's name, for the modal and the button's title. + */ + name: string; + /** + * Words after the icon; without them it's an icon button. + */ + label?: string; + title?: string; + variant?: TWUIButtonVariantType; + color?: TWUIButtonColorType; + size?: TWUIButtonSizeType; + disabled?: boolean; + className?: string; +}; + +const IconButtonClasses = + "shrink-0 p-2! text-ink-secondary dark:text-ink-secondary hover:text-ink hover:dark:text-ink hover:bg-sunken hover:dark:bg-sunken rounded-default transition-colors duration-[var(--m-fast)] focus-visible:ring-2 focus-visible:ring-ink"; + +/** + * Opens a computer in the viewer over the page, in its browser, terminal + * or code editor. Lists use it as an icon button; the computer's own page + * adds a label. + */ +export default function ComputerOpenButton({ + computer_id, + name, + label, + title, + variant = "ghost", + color = "text", + size, + disabled, + className, +}: Props) { + const [open, setOpen] = useState(false); + const button_title = title || `Open ${name}`; + + return ( + <> + + setOpen(false)} + computer_id={computer_id} + name={name} + /> + + ); +} diff --git a/src/components/general/computer-viewer-modal.tsx b/src/components/general/computer-viewer-modal.tsx new file mode 100644 index 0000000..8d72068 --- /dev/null +++ b/src/components/general/computer-viewer-modal.tsx @@ -0,0 +1,82 @@ +import type { ReactNode } from "react"; +import Modal from "@moduletrace/twui/elements/Modal"; +import H2 from "@moduletrace/twui/layout/H2"; +import Row from "@moduletrace/twui/layout/Row"; +import Span from "@moduletrace/twui/layout/Span"; +import Stack from "@moduletrace/twui/layout/Stack"; +import type { ComputerViewMode } from "@/src/dict/computer-dict"; +import ComputerView from "./computer-view"; + +type Props = { + open: boolean; + onClose: () => void; + computer_id: number; + /** + * The computer's name, in the header and the frame titles. + */ + name: string; + /** + * The mode it opens in. + */ + default_mode?: ComputerViewMode; + /** + * A line under the name, e.g. a take-over note. + */ + note?: string; + /** + * Controls at the end of the header, e.g. handing it back. + */ + action?: ReactNode; +}; + +/** + * A computer, large, in its browser, terminal or code editor, over the + * page. `ComputerOpenButton` opens it from lists; chat's viewer adds a + * note and the hand-back action after a take-over. + */ +export default function ComputerViewerModal({ + open, + onClose, + computer_id, + name, + default_mode, + note, + action, +}: Props) { + if (!open) return null; + + return ( + { + if (!next_open) onClose(); + }} + className="max-w-[95vw]! h-[90vh] p-0 bg-paper flex flex-col overflow-hidden" + > + + +

+ {name} +

+ {note ? ( + + {note} + + ) : null} +
+ {action ? ( + + {action} + + ) : null} +
+ +
+ ); +} diff --git a/src/pages/admin/bots/[bot_id]/(partials)/computer-viewer-modal.tsx b/src/pages/admin/bots/[bot_id]/(partials)/computer-viewer-modal.tsx index fde78be..67f3a7a 100644 --- a/src/pages/admin/bots/[bot_id]/(partials)/computer-viewer-modal.tsx +++ b/src/pages/admin/bots/[bot_id]/(partials)/computer-viewer-modal.tsx @@ -1,12 +1,9 @@ import { useContext, useState } from "react"; import { AppContext } from "@/src/pages/__root"; -import Modal from "@moduletrace/twui/elements/Modal"; import Button from "@moduletrace/twui/layout/Button"; -import H2 from "@moduletrace/twui/layout/H2"; -import Row from "@moduletrace/twui/layout/Row"; import Span from "@moduletrace/twui/layout/Span"; import Stack from "@moduletrace/twui/layout/Stack"; -import ComputerView from "@/src/components/general/computer-view"; +import SharedComputerViewerModal from "@/src/components/general/computer-viewer-modal"; import type { BotWithLastMessage } from "@/src/types"; import { BotComputersContext } from "../(data)/bot-computers-context"; import sendBotMessage from "../(functions)/send-bot-message"; @@ -53,26 +50,20 @@ export default function ComputerViewerModal({ bot }: Props) { }; return ( - { - if (!open) closeComputer(); - }} - className="max-w-[95vw]! h-[90vh] p-0 bg-paper flex flex-col overflow-hidden" - > - - -

- {name} -

- {viewing.take_over ? ( - - {`${bot_name} asked you to take over. Hand it back when you're done.`} - - ) : null} -
- {viewing.take_over ? ( - + onClose={closeComputer} + computer_id={Number(computer.id)} + name={name} + default_mode={viewing.mode} + note={ + viewing.take_over + ? `${bot_name} asked you to take over. Hand it back when you're done.` + : undefined + } + action={ + viewing.take_over ? ( + <> + />
); diff --git a/src/pages/admin/settings/workspaces/[workspace_id]/(partials)/workspace-computer-row.tsx b/src/pages/admin/settings/workspaces/[workspace_id]/(partials)/workspace-computer-row.tsx index c40fb1d..45a5997 100644 --- a/src/pages/admin/settings/workspaces/[workspace_id]/(partials)/workspace-computer-row.tsx +++ b/src/pages/admin/settings/workspaces/[workspace_id]/(partials)/workspace-computer-row.tsx @@ -1,8 +1,6 @@ -import ComputerIconTile from "@/src/components/general/computer-icon-tile"; -import SettingsListRow from "@/src/pages/admin/settings/(partials)/settings-list-row"; -import SettingsRowAction from "@/src/pages/admin/settings/(partials)/settings-row-action"; import type { ComputerWithStatus } from "@/src/types"; -import computerStatusLabel from "@/src/utils/computer-status-label"; +import ComputerListRow from "@/src/pages/admin/settings/(partials)/computer-list-row"; +import SettingsRowAction from "@/src/pages/admin/settings/(partials)/settings-row-action"; type Props = { computer: ComputerWithStatus; @@ -32,11 +30,9 @@ export default function WorkspaceComputerRow({ const name = computer.name || `Computer ${computer.id}`; return ( - } - title={name} + {is_default ? null : ( @@ -48,6 +44,6 @@ export default function WorkspaceComputerRow({ {is_super_admin && computer.kind != "main" ? ( ) : null} - + ); } diff --git a/src/pages/admin/workspaces/[workspace_id]/(partials)/workspace-dashboard-computer-row.tsx b/src/pages/admin/workspaces/[workspace_id]/(partials)/workspace-dashboard-computer-row.tsx index 9ef7b68..02e757e 100644 --- a/src/pages/admin/workspaces/[workspace_id]/(partials)/workspace-dashboard-computer-row.tsx +++ b/src/pages/admin/workspaces/[workspace_id]/(partials)/workspace-dashboard-computer-row.tsx @@ -1,7 +1,5 @@ -import ComputerIconTile from "@/src/components/general/computer-icon-tile"; -import SettingsListRow from "@/src/pages/admin/settings/(partials)/settings-list-row"; +import ComputerListRow from "@/src/pages/admin/settings/(partials)/computer-list-row"; import type { ComputerWithStatus } from "@/src/types"; -import computerStatusLabel from "@/src/utils/computer-status-label"; type Props = { computer: ComputerWithStatus; @@ -9,17 +7,9 @@ type Props = { }; /** - * A computer in the workspace dashboard: its state and what it's for. + * A computer in the workspace dashboard: its state, resources and what + * it's for, opening its settings page. */ export default function WorkspaceDashboardComputerRow({ computer, is_default }: Props) { - const name = computer.name || `Computer ${computer.id}`; - - return ( - } - title={name} - badge={is_default ? "Default" : undefined} - subtitle={`${computerStatusLabel({ computer })}${computer.description ? ` · ${computer.description}` : ""}`} - /> - ); + return ; }