feat: add maximize button to all tool shapes

Add useMaximize hook to all shapes using StandardizedToolWrapper:
- MapShapeUtil, MultmuxShapeUtil, MarkdownShapeUtil
- ObsNoteShapeUtil, ImageGenShapeUtil, VideoGenShapeUtil
- HolonShapeUtil, PromptShapeUtil, EmbedShapeUtil
- FathomMeetingsBrowserShapeUtil, FathomNoteShapeUtil
- HolonBrowserShapeUtil, ObsidianBrowserShapeUtil
- TranscriptionShapeUtil, VideoChatShapeUtil

All tools now have maximize/fullscreen functionality via the
standardized header bar.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2025-12-08 01:02:57 -08:00
parent aa6201e013
commit 5a7d739926
15 changed files with 191 additions and 1 deletions

View File

@ -2,6 +2,7 @@ import { BaseBoxShapeUtil, TLBaseShape, HTMLContainer } from "tldraw"
import { useCallback, useState } from "react"
import { StandardizedToolWrapper } from "../components/StandardizedToolWrapper"
import { usePinnedToView } from "../hooks/usePinnedToView"
import { useMaximize } from "../hooks/useMaximize"
export type IEmbedShape = TLBaseShape<
"Embed",
@ -173,6 +174,15 @@ export class EmbedShape extends BaseBoxShapeUtil<IEmbedShape> {
// Use the pinning hook
usePinnedToView(this.editor, shape.id, shape.props.pinnedToView)
// Use the maximize hook for fullscreen functionality
const { isMaximized, toggleMaximize } = useMaximize({
editor: this.editor,
shapeId: shape.id,
currentW: shape.props.w,
currentH: shape.props.h,
shapeType: 'Embed',
})
const handleClose = () => {
this.editor.deleteShape(shape.id)
}
@ -271,6 +281,8 @@ export class EmbedShape extends BaseBoxShapeUtil<IEmbedShape> {
onClose={handleClose}
onMinimize={handleMinimize}
isMinimized={isMinimized}
onMaximize={toggleMaximize}
isMaximized={isMaximized}
editor={this.editor}
shapeId={shape.id}
isPinnedToView={shape.props.pinnedToView}
@ -361,6 +373,8 @@ export class EmbedShape extends BaseBoxShapeUtil<IEmbedShape> {
onClose={handleClose}
onMinimize={handleMinimize}
isMinimized={isMinimized}
onMaximize={toggleMaximize}
isMaximized={isMaximized}
editor={this.editor}
shapeId={shape.id}
isPinnedToView={shape.props.pinnedToView}
@ -429,6 +443,8 @@ export class EmbedShape extends BaseBoxShapeUtil<IEmbedShape> {
onClose={handleClose}
onMinimize={handleMinimize}
isMinimized={isMinimized}
onMaximize={toggleMaximize}
isMaximized={isMaximized}
editor={this.editor}
shapeId={shape.id}
isPinnedToView={shape.props.pinnedToView}

View File

@ -9,6 +9,7 @@ import React, { useState, useContext } from "react"
import { FathomMeetingsPanel } from "../components/FathomMeetingsPanel"
import { StandardizedToolWrapper } from "../components/StandardizedToolWrapper"
import { usePinnedToView } from "../hooks/usePinnedToView"
import { useMaximize } from "../hooks/useMaximize"
import { FathomNoteShape } from "./FathomNoteShapeUtil"
import { WORKER_URL, LOCAL_WORKER_URL } from "../constants/workerUrl"
import { getFathomApiKey } from "../lib/fathomApiKey"
@ -48,6 +49,15 @@ export class FathomMeetingsBrowserShape extends BaseBoxShapeUtil<IFathomMeetings
// Use the pinning hook to keep the shape fixed to viewport when pinned
usePinnedToView(this.editor, shape.id, shape.props.pinnedToView)
// Use the maximize hook for fullscreen functionality
const { isMaximized, toggleMaximize } = useMaximize({
editor: this.editor,
shapeId: shape.id,
currentW: w,
currentH: h,
shapeType: 'FathomMeetingsBrowser',
})
const handleClose = () => {
setIsOpen(false)
// Delete the browser shape immediately so it's tracked in undo/redo history
@ -518,6 +528,8 @@ export class FathomMeetingsBrowserShape extends BaseBoxShapeUtil<IFathomMeetings
onClose={handleClose}
onMinimize={handleMinimize}
isMinimized={isMinimized}
onMaximize={toggleMaximize}
isMaximized={isMaximized}
editor={this.editor}
shapeId={shape.id}
isPinnedToView={shape.props.pinnedToView}

View File

@ -3,6 +3,7 @@ import { BaseBoxShapeUtil, TLBaseShape, createShapeId, IndexKey, TLParentId, HTM
import type { JSX } from 'react'
import { StandardizedToolWrapper } from '../components/StandardizedToolWrapper'
import { usePinnedToView } from '../hooks/usePinnedToView'
import { useMaximize } from '../hooks/useMaximize'
export type IFathomNoteShape = TLBaseShape<
'FathomNote',
@ -45,6 +46,15 @@ export class FathomNoteShape extends BaseBoxShapeUtil<IFathomNoteShape> {
// Use the pinning hook
usePinnedToView(this.editor, shape.id, shape.props.pinnedToView)
// Use the maximize hook for fullscreen functionality
const { isMaximized, toggleMaximize } = useMaximize({
editor: this.editor,
shapeId: shape.id,
currentW: shape.props.w,
currentH: shape.props.h,
shapeType: 'FathomNote',
})
const handleClose = () => {
this.editor.deleteShape(shape.id)
}
@ -513,6 +523,8 @@ export class FathomNoteShape extends BaseBoxShapeUtil<IFathomNoteShape> {
onClose={handleClose}
onMinimize={handleMinimize}
isMinimized={isMinimized}
onMaximize={toggleMaximize}
isMaximized={isMaximized}
editor={this.editor}
shapeId={shape.id}
isPinnedToView={shape.props.pinnedToView}

View File

@ -8,6 +8,7 @@ import { HolonBrowser } from "../components/HolonBrowser"
import { HolonData } from "../lib/HoloSphereService"
import { StandardizedToolWrapper } from "../components/StandardizedToolWrapper"
import { usePinnedToView } from "../hooks/usePinnedToView"
import { useMaximize } from "../hooks/useMaximize"
type IHolonBrowser = TLBaseShape<
"HolonBrowser",
@ -43,6 +44,15 @@ export class HolonBrowserShape extends BaseBoxShapeUtil<IHolonBrowser> {
// Use the pinning hook to keep the shape fixed to viewport when pinned
usePinnedToView(this.editor, shape.id, shape.props.pinnedToView)
// Use the maximize hook for fullscreen functionality
const { isMaximized, toggleMaximize } = useMaximize({
editor: this.editor,
shapeId: shape.id,
currentW: w,
currentH: h,
shapeType: 'HolonBrowser',
})
const handleSelectHolon = (holonData: HolonData) => {
// Store current camera position to prevent it from changing
const currentCamera = this.editor.getCamera()
@ -146,6 +156,8 @@ export class HolonBrowserShape extends BaseBoxShapeUtil<IHolonBrowser> {
onClose={handleClose}
onMinimize={handleMinimize}
isMinimized={isMinimized}
onMaximize={toggleMaximize}
isMaximized={isMaximized}
editor={this.editor}
shapeId={shape.id}
isPinnedToView={shape.props.pinnedToView}

View File

@ -8,6 +8,7 @@ import { holosphereService, HoloSphereService, HolonConnection } from "@/lib/Hol
import * as h3 from 'h3-js'
import { StandardizedToolWrapper } from "../components/StandardizedToolWrapper"
import { usePinnedToView } from "../hooks/usePinnedToView"
import { useMaximize } from "../hooks/useMaximize"
type IHolon = TLBaseShape<
"Holon",
@ -112,6 +113,15 @@ export class HolonShape extends BaseBoxShapeUtil<IHolon> {
// Use the pinning hook to keep the shape fixed to viewport when pinned
usePinnedToView(this.editor, shape.id, shape.props.pinnedToView)
// Use the maximize hook for fullscreen functionality
const { isMaximized, toggleMaximize } = useMaximize({
editor: this.editor,
shapeId: shape.id,
currentW: shape.props.w,
currentH: shape.props.h,
shapeType: 'Holon',
})
// Note: Auto-initialization is disabled. Users must manually enter Holon IDs.
// This prevents the shape from auto-generating IDs based on coordinates.
@ -763,6 +773,8 @@ export class HolonShape extends BaseBoxShapeUtil<IHolon> {
onClose={handleClose}
onMinimize={handleMinimize}
isMinimized={isMinimized}
onMaximize={toggleMaximize}
isMaximized={isMaximized}
headerContent={headerContent}
editor={this.editor}
shapeId={shape.id}

View File

@ -10,6 +10,7 @@ import { getRunPodConfig } from "@/lib/clientConfig"
import { aiOrchestrator, isAIOrchestratorAvailable } from "@/lib/aiOrchestrator"
import { StandardizedToolWrapper } from "@/components/StandardizedToolWrapper"
import { usePinnedToView } from "@/hooks/usePinnedToView"
import { useMaximize } from "@/hooks/useMaximize"
// Feature flag: Set to false when AI Orchestrator or RunPod API is ready for production
const USE_MOCK_API = false
@ -326,6 +327,15 @@ export class ImageGenShape extends BaseBoxShapeUtil<IImageGen> {
// Pin to view functionality
usePinnedToView(editor, shape.id, shape.props.pinnedToView)
// Use the maximize hook for fullscreen functionality
const { isMaximized, toggleMaximize } = useMaximize({
editor: editor,
shapeId: shape.id,
currentW: shape.props.w,
currentH: shape.props.h,
shapeType: 'ImageGen',
})
const handlePinToggle = () => {
editor.updateShape<IImageGen>({
id: shape.id,
@ -589,6 +599,8 @@ export class ImageGenShape extends BaseBoxShapeUtil<IImageGen> {
onClose={handleClose}
onMinimize={handleMinimize}
isMinimized={isMinimized}
onMaximize={toggleMaximize}
isMaximized={isMaximized}
editor={editor}
shapeId={shape.id}
tags={shape.props.tags || []}

View File

@ -19,6 +19,8 @@ import { useRef, useEffect, useState, useCallback } from 'react';
import maplibregl from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';
import { StandardizedToolWrapper } from '../components/StandardizedToolWrapper';
import { usePinnedToView } from '../hooks/usePinnedToView';
import { useMaximize } from '../hooks/useMaximize';
// =============================================================================
// Types
@ -344,6 +346,18 @@ function MapComponent({ shape, editor, isSelected }: { shape: IMapShape; editor:
const styleKey = (shape.props.styleKey || 'voyager') as StyleKey;
const currentStyle = MAP_STYLES[styleKey] || MAP_STYLES.voyager;
// Use the pinning hook to keep the shape fixed to viewport when pinned
usePinnedToView(editor, shape.id, shape.props.pinnedToView);
// Use the maximize hook for fullscreen functionality
const { isMaximized, toggleMaximize } = useMaximize({
editor: editor,
shapeId: shape.id,
currentW: shape.props.w,
currentH: shape.props.h,
shapeType: 'Map',
});
// Track mounted state for cleanup
useEffect(() => {
isMountedRef.current = true;
@ -908,6 +922,8 @@ function MapComponent({ shape, editor, isSelected }: { shape: IMapShape; editor:
onClose={handleClose}
onMinimize={handleMinimize}
isMinimized={shape.props.isMinimized}
onMaximize={toggleMaximize}
isMaximized={isMaximized}
editor={editor}
shapeId={shape.id}
isPinnedToView={shape.props.pinnedToView}

View File

@ -28,6 +28,7 @@ import '@mdxeditor/editor/style.css'
import { BaseBoxShapeUtil, TLBaseShape, HTMLContainer } from '@tldraw/tldraw'
import { StandardizedToolWrapper } from '../components/StandardizedToolWrapper'
import { usePinnedToView } from '../hooks/usePinnedToView'
import { useMaximize } from '../hooks/useMaximize'
export type IMarkdownShape = TLBaseShape<
'Markdown',
@ -89,6 +90,15 @@ export class MarkdownShape extends BaseBoxShapeUtil<IMarkdownShape> {
// Use the pinning hook
usePinnedToView(this.editor, shape.id, shape.props.pinnedToView)
// Use the maximize hook for fullscreen functionality
const { isMaximized, toggleMaximize } = useMaximize({
editor: this.editor,
shapeId: shape.id,
currentW: shape.props.w,
currentH: shape.props.h,
shapeType: 'Markdown',
})
const handleClose = () => {
this.editor.deleteShape(shape.id)
}
@ -140,6 +150,8 @@ export class MarkdownShape extends BaseBoxShapeUtil<IMarkdownShape> {
onClose={handleClose}
onMinimize={handleMinimize}
isMinimized={isMinimized}
onMaximize={toggleMaximize}
isMaximized={isMaximized}
editor={this.editor}
shapeId={shape.id}
isPinnedToView={shape.props.pinnedToView}

View File

@ -2,6 +2,7 @@ import React, { useState, useEffect, useRef } from 'react'
import { BaseBoxShapeUtil, TLBaseShape, HTMLContainer, Geometry2d, Rectangle2d, T, createShapePropsMigrationIds, createShapePropsMigrationSequence } from 'tldraw'
import { StandardizedToolWrapper } from '../components/StandardizedToolWrapper'
import { usePinnedToView } from '../hooks/usePinnedToView'
import { useMaximize } from '../hooks/useMaximize'
import { Terminal } from '@xterm/xterm'
import { FitAddon } from '@xterm/addon-fit'
import '@xterm/xterm/css/xterm.css'
@ -158,6 +159,15 @@ export class MultmuxShape extends BaseBoxShapeUtil<IMultmuxShape> {
// Use the pinning hook
usePinnedToView(this.editor, shape.id, shape.props.pinnedToView)
// Use the maximize hook for fullscreen functionality
const { isMaximized, toggleMaximize } = useMaximize({
editor: this.editor,
shapeId: shape.id,
currentW: shape.props.w,
currentH: shape.props.h,
shapeType: 'Multmux',
})
// Runtime fix: correct old serverUrl port (3000 -> 3002)
// This handles shapes that may not have been migrated yet
useEffect(() => {
@ -511,6 +521,8 @@ export class MultmuxShape extends BaseBoxShapeUtil<IMultmuxShape> {
onClose={handleClose}
onMinimize={handleMinimize}
isMinimized={isMinimized}
onMaximize={toggleMaximize}
isMaximized={isMaximized}
editor={this.editor}
shapeId={shape.id}
isPinnedToView={shape.props.pinnedToView}
@ -718,6 +730,8 @@ export class MultmuxShape extends BaseBoxShapeUtil<IMultmuxShape> {
onClose={handleClose}
onMinimize={handleMinimize}
isMinimized={isMinimized}
onMaximize={toggleMaximize}
isMaximized={isMaximized}
editor={this.editor}
shapeId={shape.id}
isPinnedToView={shape.props.pinnedToView}

View File

@ -32,6 +32,7 @@ import { logGitHubSetupStatus } from '@/lib/githubSetupValidator'
import { getClientConfig } from '@/lib/clientConfig'
import { StandardizedToolWrapper } from '../components/StandardizedToolWrapper'
import { usePinnedToView } from '../hooks/usePinnedToView'
import { useMaximize } from '../hooks/useMaximize'
// Main ObsNote component with full markdown editing
const ObsNoteComponent: React.FC<{
@ -50,6 +51,15 @@ const ObsNoteComponent: React.FC<{
// Use the pinning hook to keep the shape fixed to viewport when pinned
usePinnedToView(shapeUtil.editor, shape.id, shape.props.pinnedToView)
// Use the maximize hook for fullscreen functionality
const { isMaximized, toggleMaximize } = useMaximize({
editor: shapeUtil.editor,
shapeId: shape.id,
currentW: shape.props.w,
currentH: shape.props.h,
shapeType: 'ObsNote',
})
// Track content changes for sync button visibility
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(shape.props.isModified)
@ -486,6 +496,8 @@ ${contentToSync}`
onClose={handleClose}
onMinimize={handleMinimize}
isMinimized={isMinimized}
onMaximize={toggleMaximize}
isMaximized={isMaximized}
headerContent={headerContent}
editor={shapeUtil.editor}
shapeId={shape.id}

View File

@ -12,6 +12,7 @@ import { findNonOverlappingPosition } from "@/utils/shapeCollisionUtils"
import { StandardizedToolWrapper } from "../components/StandardizedToolWrapper"
import { AuthContext } from "../context/AuthContext"
import { usePinnedToView } from "../hooks/usePinnedToView"
import { useMaximize } from "../hooks/useMaximize"
type IObsidianBrowser = TLBaseShape<
"ObsidianBrowser",
@ -46,7 +47,16 @@ export class ObsidianBrowserShape extends BaseBoxShapeUtil<IObsidianBrowser> {
// Use the pinning hook to keep the shape fixed to viewport when pinned
usePinnedToView(this.editor, shape.id, shape.props.pinnedToView)
// Use the maximize hook for fullscreen functionality
const { isMaximized, toggleMaximize } = useMaximize({
editor: this.editor,
shapeId: shape.id,
currentW: w,
currentH: h,
shapeType: 'ObsidianBrowser',
})
// Wrapper component to access auth context
const ObsidianBrowserContent: React.FC<{ vaultName?: string }> = ({ vaultName }) => {
@ -342,6 +352,8 @@ export class ObsidianBrowserShape extends BaseBoxShapeUtil<IObsidianBrowser> {
onClose={handleClose}
onMinimize={handleMinimize}
isMinimized={isMinimized}
onMaximize={toggleMaximize}
isMaximized={isMaximized}
editor={this.editor}
shapeId={shape.id}
headerContent={headerContent}

View File

@ -16,6 +16,7 @@ import { findNonOverlappingPosition } from "@/utils/shapeCollisionUtils"
import React, { useState } from "react"
import { StandardizedToolWrapper } from "../components/StandardizedToolWrapper"
import { usePinnedToView } from "../hooks/usePinnedToView"
import { useMaximize } from "../hooks/useMaximize"
type IPrompt = TLBaseShape<
"Prompt",
@ -373,6 +374,15 @@ export class PromptShape extends BaseBoxShapeUtil<IPrompt> {
// Use the pinning hook
usePinnedToView(this.editor, shape.id, shape.props.pinnedToView)
// Use the maximize hook for fullscreen functionality
const { isMaximized, toggleMaximize } = useMaximize({
editor: this.editor,
shapeId: shape.id,
currentW: shape.props.w,
currentH: shape.props.h,
shapeType: 'Prompt',
})
const handleClose = () => {
this.editor.deleteShape(shape.id)
}
@ -403,6 +413,8 @@ export class PromptShape extends BaseBoxShapeUtil<IPrompt> {
onClose={handleClose}
onMinimize={handleMinimize}
isMinimized={isMinimized}
onMaximize={toggleMaximize}
isMaximized={isMaximized}
editor={this.editor}
shapeId={shape.id}
isPinnedToView={shape.props.pinnedToView}

View File

@ -8,6 +8,7 @@ import { useWhisperTranscription } from "../hooks/useWhisperTranscriptionSimple"
import { useWebSpeechTranscription } from "../hooks/useWebSpeechTranscription"
import { StandardizedToolWrapper } from "../components/StandardizedToolWrapper"
import { usePinnedToView } from "../hooks/usePinnedToView"
import { useMaximize } from "../hooks/useMaximize"
type ITranscription = TLBaseShape<
"Transcription",
@ -102,6 +103,15 @@ export class TranscriptionShape extends BaseBoxShapeUtil<ITranscription> {
// Use the pinning hook to keep the shape fixed to viewport when pinned
usePinnedToView(this.editor, shape.id, shape.props.pinnedToView)
// Use the maximize hook for fullscreen functionality
const { isMaximized, toggleMaximize } = useMaximize({
editor: this.editor,
shapeId: shape.id,
currentW: w,
currentH: h,
shapeType: 'Transcription',
})
// Local Whisper model is always available (no API key needed)
const isLocalWhisperAvailable = true
@ -682,6 +692,8 @@ export class TranscriptionShape extends BaseBoxShapeUtil<ITranscription> {
onClose={handleClose}
onMinimize={handleMinimize}
isMinimized={isMinimized}
onMaximize={toggleMaximize}
isMaximized={isMaximized}
headerContent={headerContent}
editor={this.editor}
shapeId={shape.id}

View File

@ -3,6 +3,7 @@ import { useEffect, useState } from "react"
import { WORKER_URL } from "../constants/workerUrl"
import { StandardizedToolWrapper } from "../components/StandardizedToolWrapper"
import { usePinnedToView } from "../hooks/usePinnedToView"
import { useMaximize } from "../hooks/useMaximize"
interface DailyApiResponse {
url: string;
@ -464,6 +465,15 @@ export class VideoChatShape extends BaseBoxShapeUtil<IVideoChatShape> {
// Use the pinning hook to keep the shape fixed to viewport when pinned
usePinnedToView(this.editor, shape.id, shape.props.pinnedToView)
// Use the maximize hook for fullscreen functionality
const { isMaximized, toggleMaximize } = useMaximize({
editor: this.editor,
shapeId: shape.id,
currentW: shape.props.w,
currentH: shape.props.h,
shapeType: 'VideoChat',
})
if (error) {
return <div>Error creating room: {error.message}</div>
}
@ -562,6 +572,8 @@ export class VideoChatShape extends BaseBoxShapeUtil<IVideoChatShape> {
onClose={handleClose}
onMinimize={handleMinimize}
isMinimized={isMinimized}
onMaximize={toggleMaximize}
isMaximized={isMaximized}
editor={this.editor}
shapeId={shape.id}
isPinnedToView={shape.props.pinnedToView}

View File

@ -9,6 +9,7 @@ import React, { useState, useRef, useEffect } from "react"
import { getRunPodVideoConfig } from "@/lib/clientConfig"
import { StandardizedToolWrapper } from "@/components/StandardizedToolWrapper"
import { usePinnedToView } from "@/hooks/usePinnedToView"
import { useMaximize } from "@/hooks/useMaximize"
// Type for RunPod job response
interface RunPodJobResponse {
@ -105,6 +106,15 @@ export class VideoGenShape extends BaseBoxShapeUtil<IVideoGen> {
// Pin to view functionality
usePinnedToView(editor, shape.id, shape.props.pinnedToView)
// Use the maximize hook for fullscreen functionality
const { isMaximized, toggleMaximize } = useMaximize({
editor: editor,
shapeId: shape.id,
currentW: shape.props.w,
currentH: shape.props.h,
shapeType: 'VideoGen',
})
const handlePinToggle = () => {
editor.updateShape<IVideoGen>({
id: shape.id,
@ -387,6 +397,8 @@ export class VideoGenShape extends BaseBoxShapeUtil<IVideoGen> {
onClose={handleClose}
onMinimize={handleMinimize}
isMinimized={isMinimized}
onMaximize={toggleMaximize}
isMaximized={isMaximized}
editor={editor}
shapeId={shape.id}
tags={shape.props.tags}