Full tab chat on mobile
This commit is contained in:
parent
ffaa137d08
commit
3d94a298b8
|
|
@ -0,0 +1,14 @@
|
|||
import { useEffect, useState } from "preact/hooks";
|
||||
|
||||
export function useMediaQuery(query: string): boolean {
|
||||
const [matches, setMatches] = useState(() => window.matchMedia(query).matches);
|
||||
|
||||
useEffect(() => {
|
||||
const mq = window.matchMedia(query);
|
||||
const handler = (e: MediaQueryListEvent) => setMatches(e.matches);
|
||||
mq.addEventListener('change', handler);
|
||||
return () => mq.removeEventListener('change', handler);
|
||||
}, [query]);
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
export const MOBILE_BREAKPOINT = 1000;
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
import { ContentEditable } from "@common/components/ContentEditable";
|
||||
import { highlight } from "@common/highlight";
|
||||
import { useInputState } from "@common/hooks/useInputState";
|
||||
import { useMediaQuery } from "@common/hooks/useMediaQuery";
|
||||
import clsx from "clsx";
|
||||
import { Check, ChevronsRight, Edit2, GitFork, RefreshCw, Sparkles, Trash2, X } from "lucide-preact";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
import { MOBILE_BREAKPOINT } from '../assets/breakpoints';
|
||||
import styles from '../assets/chat-sidebar.module.css';
|
||||
import sidebarStyles from '../assets/sidebar.module.css';
|
||||
import { useAppState, type ChatMessage } from "../contexts/state";
|
||||
|
|
@ -635,9 +637,10 @@ export const ChatPanel = ({ visible }: { visible: boolean }) => {
|
|||
|
||||
export const ChatSidebar = () => {
|
||||
const { currentWorld, chatOpen, dispatch } = useAppState();
|
||||
const isMobile = useMediaQuery(`(max-width: ${MOBILE_BREAKPOINT}px)`);
|
||||
|
||||
// In chat-only worlds, chat is a full editor tab — no sidebar needed
|
||||
if (currentWorld?.chatOnly) return null;
|
||||
// In chat-only worlds or on mobile, chat is a full editor tab — no sidebar needed
|
||||
if (currentWorld?.chatOnly || isMobile) return null;
|
||||
|
||||
return (
|
||||
<Sidebar
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { highlight } from "@common/highlight";
|
||||
import { useMediaQuery } from "@common/hooks/useMediaQuery";
|
||||
import clsx from "clsx";
|
||||
import { BookMarked, BookOpen, BrainCircuit, Code, FileText, Globe, Layers, List, MapPin, MessageSquare, MessagesSquare, Users, type LucideIcon } from "lucide-preact";
|
||||
import { useEffect, useMemo, useRef } from "preact/hooks";
|
||||
import { MOBILE_BREAKPOINT } from '../assets/breakpoints';
|
||||
import styles from '../assets/editor.module.css';
|
||||
import { useAppState, type Tab } from "../contexts/state";
|
||||
import Prompt from "../utils/prompt";
|
||||
|
|
@ -36,10 +38,11 @@ const WORLD_TABS: { id: Tab; label: string; icon: LucideIcon; right?: boolean }[
|
|||
{ id: "system", label: "System", icon: BrainCircuit },
|
||||
];
|
||||
|
||||
const CHAT_TAB = { id: "chat", label: "Chat", icon: MessageSquare } as const;
|
||||
// Tabs for a chat session within a chat-only world
|
||||
const CHAT_STORY_TABS: { id: Tab; label: string; icon: LucideIcon; right?: boolean }[] = [
|
||||
{ id: "menu", label: "Menu", icon: List },
|
||||
{ id: "chat", label: "Chat", icon: MessageSquare },
|
||||
CHAT_TAB,
|
||||
{ id: "scratchpad", label: "Scratchpad", icon: FileText, right: true },
|
||||
{ id: "prompt", label: "Prompt", icon: Code },
|
||||
];
|
||||
|
|
@ -88,14 +91,20 @@ export const Editor = () => {
|
|||
return () => cancelAnimationFrame(raf);
|
||||
}, [currentStory?.id, currentWorld?.id, currentTab]);
|
||||
|
||||
const isMobile = useMediaQuery(`(max-width: ${MOBILE_BREAKPOINT}px)`);
|
||||
|
||||
const hasSelection = currentWorld !== null;
|
||||
const isChatOnly = currentWorld?.chatOnly ?? false;
|
||||
|
||||
const tabs = currentStory
|
||||
? (isChatOnly ? CHAT_STORY_TABS : STORY_TABS)
|
||||
: currentWorld
|
||||
? (isChatOnly ? CHAT_WORLD_TABS : WORLD_TABS)
|
||||
: [{ id: "menu" as Tab, label: "Menu", icon: List }];
|
||||
const tabs = useMemo(() => {
|
||||
if (currentStory) {
|
||||
if (isChatOnly) return CHAT_STORY_TABS;
|
||||
if (isMobile) return [...STORY_TABS, { ...CHAT_TAB, right: true }];
|
||||
return STORY_TABS;
|
||||
}
|
||||
if (currentWorld) return isChatOnly ? CHAT_WORLD_TABS : WORLD_TABS;
|
||||
return [{ id: "menu" as Tab, label: "Menu", icon: List }];
|
||||
}, [currentStory, currentWorld, isChatOnly, isMobile]);
|
||||
|
||||
// Title bar: use MessagesSquare icon for chat-only worlds
|
||||
const WorldIcon = isChatOnly ? MessagesSquare : Globe;
|
||||
|
|
@ -124,7 +133,7 @@ export const Editor = () => {
|
|||
class={clsx(styles.promptPreview, currentTab !== "prompt" && styles.tabHidden)}
|
||||
dangerouslySetInnerHTML={{ __html: Prompt.substituteVars(appState, promptPreview) }}
|
||||
/>
|
||||
{isChatOnly && <ChatPanel visible={currentTab === "chat"} />}
|
||||
{(isChatOnly || isMobile) && <ChatPanel visible={currentTab === "chat"} />}
|
||||
</>)}
|
||||
{(currentStory || currentWorld) && (<>
|
||||
<LoreEditor visible={currentTab === "lore"} />
|
||||
|
|
@ -145,7 +154,7 @@ export const Editor = () => {
|
|||
<span class={styles.tabLabel}>{tab.label}</span>
|
||||
</button>
|
||||
))}
|
||||
{currentStory && !isChatOnly && (
|
||||
{currentStory && !isChatOnly && !isMobile && (
|
||||
<button
|
||||
class={clsx(styles.tab, styles.tabRight, chatOpen && styles.active)}
|
||||
onClick={() => dispatch({ type: 'SET_CHAT_OPEN', open: !chatOpen })}
|
||||
|
|
|
|||
Loading…
Reference in New Issue