diff --git a/bun.lockb b/bun.lockb index 68862f49..7b1c4bde 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 49575d92..1da4c614 100644 --- a/package.json +++ b/package.json @@ -43,5 +43,6 @@ "packages/app" ], "dependencies": { + "react-responsive": "^10.0.0" } } \ No newline at end of file diff --git a/packages/app/src/app/main.tsx b/packages/app/src/app/main.tsx index cfc06d03..ce580d5e 100644 --- a/packages/app/src/app/main.tsx +++ b/packages/app/src/app/main.tsx @@ -20,7 +20,7 @@ import { TopBar } from '@/components/toolbars/top-bar' import { Timeline } from '@/components/core/timeline' import { ChatView } from '@/components/assistant/ChatView' import { Editors } from '@/components/editors/Editors' -import { BottomToolbar } from '@/components/toolbars/bottom-bar' +import { BottomBar } from '@/components/toolbars/bottom-bar' import { FruityDesktop, FruityWindow } from '@/components/windows' import { ScriptEditor } from '@/components/editors/ScriptEditor' import { SegmentEditor } from '@/components/editors/SegmentEditor' @@ -35,6 +35,7 @@ import { useDynamicWorkflows } from '@/services/editors/workflow-editor/useDynam import { useQueryStringLoader } from '@/components/toolbars/top-menu/file/useQueryStringLoader' import { useSetupIframeOnce } from './embed/useSetupIframeOnce' import { TimelineZoom } from '@/components/core/timeline/TimelineZoom' +import { useBreakpoints } from '@/lib/hooks/useBreakpoints' export enum ClapperIntegrationMode { APP = 'APP', @@ -57,6 +58,8 @@ function MainContent({ mode }: { mode: ClapperIntegrationMode }) { const isIframe = mode === ClapperIntegrationMode.IFRAME + const { isMd } = useBreakpoints() + // this has to be done at the root of the app, that way it can // perform its routine even when the monitor component is hidden useRenderLoop() @@ -323,14 +326,14 @@ function MainContent({ mode }: { mode: ClapperIntegrationMode }) { `dark fixed flex h-screen w-screen select-none flex-col overflow-hidden font-light text-neutral-900/90 dark:text-neutral-100/90` )} > - +
- {!isIframe && welcomeScreen} + {!isIframe && isMd && welcomeScreen} - {!isIframe && windowLayout === UIWindowLayout.GRID && } + {!isIframe && windowLayout === UIWindowLayout.GRID && }
) } diff --git a/packages/app/src/components/toolbars/bottom-bar/BottomMenuItem.tsx b/packages/app/src/components/toolbars/bottom-bar/BottomMenuItem.tsx new file mode 100644 index 00000000..a8526347 --- /dev/null +++ b/packages/app/src/components/toolbars/bottom-bar/BottomMenuItem.tsx @@ -0,0 +1,83 @@ +import { ReactNode } from 'react' +import { EditorView } from '@aitube/clapper-services' + +import { cn } from '@/lib/utils' +import { useEditors } from '@/services/editors/useEditors' +import { useTheme } from '@/services/ui/useTheme' +import { useUI } from '@/services' + +export function BottomMenuItem({ + children, + view: expectedView, + label, + onClick, +}: { + children: ReactNode + + /** + * Name of the menu item + */ + view?: EditorView + + /** + * Label of the tooltip + */ + label?: string + + /** + * Custom handler + */ + onClick?: () => void +}) { + const theme = useTheme() + const view = useEditors((s) => s.view) + const setView = useEditors((s) => s.setView) + const setShowExplorer = useUI((s) => s.setShowExplorer) + const setShowVideoPlayer = useUI((s) => s.setShowVideoPlayer) + + const isActive = view === expectedView + + const tooltipLabel = label || expectedView + + const handleClick = () => { + onClick?.() + + // nothing to do if there is no name or if we are already selecterd + if (!expectedView || isActive) { + return + } + setView(expectedView) + setShowExplorer(true) + setShowVideoPlayer(false) + } + + return ( +
+
+ {children} +
+
+ ) +} diff --git a/packages/app/src/components/toolbars/bottom-bar/DesktopBottomBar.tsx b/packages/app/src/components/toolbars/bottom-bar/DesktopBottomBar.tsx new file mode 100644 index 00000000..d9c860e7 --- /dev/null +++ b/packages/app/src/components/toolbars/bottom-bar/DesktopBottomBar.tsx @@ -0,0 +1,64 @@ +import { cn } from '@/lib/utils' +import { useTheme } from '@/services/ui/useTheme' + +import { APP_REVISION } from '@/lib/core/constants' +import { Tasks } from './tasks' +import { useTimeline } from '@aitube/timeline' +import { TimelineZoom } from '@/components/core/timeline/TimelineZoom' + +export function DesktopBottomBar() { + const theme = useTheme() + const bpm = useTimeline((s) => s.bpm) + const frameRate = useTimeline((s) => s.frameRate) + + return ( +
+
+
+ app version: + {APP_REVISION} +
+ + {/* + Note sure that's really useful since there is a garbage collector, + I got a situation where I had 1.2 Gb when loaded empty, + and it turned into 800 Mb after loading a big project, + thanks to the GC kicking in. + + what would be more useful is to collect system metrics in the Desktop version. + + */} + +
+ BPM: + {Math.round(bpm * 1000) / 1000} +
+ +
+ FPS: + + {Math.round(frameRate * 1000) / 1000} + +
+
+
+ + +
+
+ ) +} diff --git a/packages/app/src/components/toolbars/bottom-bar/MobileBottomBar.tsx b/packages/app/src/components/toolbars/bottom-bar/MobileBottomBar.tsx new file mode 100644 index 00000000..1f7f4ca2 --- /dev/null +++ b/packages/app/src/components/toolbars/bottom-bar/MobileBottomBar.tsx @@ -0,0 +1,99 @@ +import { CiViewTimeline } from 'react-icons/ci' +import { MdMovieEdit } from 'react-icons/md' +import { GoVideo } from 'react-icons/go' +import { GrUserSettings } from 'react-icons/gr' +import { PiBookOpenTextLight, PiTreeStructureLight } from 'react-icons/pi' +import { EditorView, SettingsCategory } from '@aitube/clapper-services' + +import { cn } from '@/lib/utils' +import { useEditors, useUI } from '@/services' +import { useTheme } from '@/services/ui/useTheme' + +import { NatureIcon } from '../editors-menu/NatureIcon' +import { BottomMenuItem } from './BottomMenuItem' +import { useEffect } from 'react' +import { useBreakpoints } from '@/lib/hooks/useBreakpoints' + +export function MobileBottomBar() { + const theme = useTheme() + const setView = useEditors((s) => s.setView) + const setShowExplorer = useUI((s) => s.setShowExplorer) + const setShowVideoPlayer = useUI((s) => s.setShowVideoPlayer) + const setShowAssistant = useUI((s) => s.setShowAssistant) + const setShowSettings = useUI((s) => s.setShowSettings) + + const { isMd } = useBreakpoints() + + useEffect(() => { + if (isMd) { + setShowExplorer(true) + setShowVideoPlayer(true) + } else { + setShowAssistant(false) + setShowExplorer(false) + setShowVideoPlayer(true) + } + }, [isMd]) + + return ( +
+ { + setShowExplorer(false) + }} + > + + + + + + + + + + + + { + setShowExplorer(false) + }} + > + + + { + setShowExplorer(false) + setShowVideoPlayer(true) + }} + > + + + setShowSettings(SettingsCategory.PROVIDER)} + > + + +
+ ) +} diff --git a/packages/app/src/components/toolbars/bottom-bar/index.tsx b/packages/app/src/components/toolbars/bottom-bar/index.tsx index 97caeb90..3919d0dc 100644 --- a/packages/app/src/components/toolbars/bottom-bar/index.tsx +++ b/packages/app/src/components/toolbars/bottom-bar/index.tsx @@ -1,66 +1,19 @@ import { cn } from '@/lib/utils' -import { useTheme } from '@/services/ui/useTheme' -import { Metrics } from './metrics' -import { APP_REVISION } from '@/lib/core/constants' -import { Tasks } from './tasks' -import { useTimeline } from '@aitube/timeline' -import { TimelineZoom } from '@/components/core/timeline/TimelineZoom' - -export function BottomToolbar() { - const theme = useTheme() - const bpm = useTimeline((s) => s.bpm) - const frameRate = useTimeline((s) => s.frameRate) +import { DesktopBottomBar } from './DesktopBottomBar' +import { MobileBottomBar } from './MobileBottomBar' +export function BottomBar() { return (
-
-
- app version: - {APP_REVISION} -
- - {/* - Note sure that's really useful since there is a garbage collector, - I got a situation where I had 1.2 Gb when loaded empty, - and it turned into 800 Mb after loading a big project, - thanks to the GC kicking in. - - what would be more useful is to collect system metrics in the Desktop version. - - */} - -
- BPM: - {Math.round(bpm * 1000) / 1000} -
- -
- FPS: - - {Math.round(frameRate * 1000) / 1000} - -
-
-
- - -
+ +
) } diff --git a/packages/app/src/components/toolbars/editors-menu/EditorsSideMenu.tsx b/packages/app/src/components/toolbars/editors-menu/EditorsSideMenu.tsx index 64416dbb..2726e820 100644 --- a/packages/app/src/components/toolbars/editors-menu/EditorsSideMenu.tsx +++ b/packages/app/src/components/toolbars/editors-menu/EditorsSideMenu.tsx @@ -23,12 +23,18 @@ import { EditorView } from '@aitube/clapper-services' import { useTheme } from '@/services/ui/useTheme' import { EditorsSideMenuItem } from './EditorsSideMenuItem' import { NatureIcon } from './NatureIcon' +import { cn } from '@/lib/utils' export function EditorsSideMenu() { const theme = useTheme() return (
({ + isSm: false, + isMd: false, + isLg: false, + isXl: false, + is2xl: false, + }) + + useEffect(() => { + const updateBreakpoints = () => { + const width = window.innerWidth + setBreakpoints({ + isSm: width >= 640, + isMd: width >= 768, + isLg: width >= 1024, + isXl: width >= 1280, + is2xl: width >= 1536, + }) + } + + // Initial check + updateBreakpoints() + + // Add event listener + window.addEventListener('resize', updateBreakpoints) + + // Cleanup + return () => window.removeEventListener('resize', updateBreakpoints) + }, []) + + return breakpoints +}