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 (
+
+ )
+}
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
+}