Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 2 additions & 12 deletions components/dashboard/DashboardSidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
"use client";

import { ReactNode, useContext, useState } from "react";
import { mutate } from "swr";
import { DashboardContext } from "@src/context/DashboardContext";
import { Info, LogIn, LogOut } from "lucide-react";
import { useTranslations } from "next-intl";

import styles from "./DashboardModal.module.css";
import dangerStyles from "./project/DangerZone.module.css";
import modal from "../utils/ModalBtn.module.css";
import { signOut } from "next-auth/react";
import { isTauri } from "@tauri-apps/api/core";
import { signOutAccount } from "@src/lib/utils/auth-actions";
import { useCookieUser } from "@src/lib/utils/hooks";

export type Category =
Expand Down Expand Up @@ -54,15 +52,7 @@ const SidebarMenu = ({ structure, activeTab, onTabChange }: SidebarMenuProps) =>
const [showLogOutConfirm, setShowLogOutConfirm] = useState(false);

const onLogOut = async () => {
if (isTauri()) {
// Desktop holds the bearer token locally; the server has no cookie to clear.
const { clearDesktopToken } = await import("@src/lib/desktop-auth");
await clearDesktopToken();
} else {
await signOut({ redirect: false });
}

await mutate("/api/users/cookie", undefined);
await signOutAccount();
closeDashboard();
};

Expand Down
37 changes: 25 additions & 12 deletions components/editor/DocumentEditorPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,10 @@ export interface DocumentEditorPanelProps {
focusedTypeOverride?: "screenplay" | "title" | "draft";
}

// Scroll distance (px) that fully hides the mobile chrome. Roughly the navbar
// height so the bar slides away at content speed — a 1:1 feel with the scroll.
const CHROME_HIDE_RANGE = 64;
// Scroll distance (px) that fully hides the mobile chrome. Deliberately several
// times the navbar height so the bar eases away gradually over a longer swipe
// rather than snapping shut after a flick — matches the pace of a natural scroll.
const CHROME_HIDE_RANGE = 220;

const DocumentEditorPanel = ({
config,
Expand Down Expand Up @@ -829,10 +830,16 @@ const DocumentEditorPanel = ({
tapTimer.current = null;
if (isReadOnly) return;
setMobileEditMode(true);
// setEditable flips in an effect after this render, so defer the
// focus a tick until the editor is actually editable.
// Make the editor editable and focus it SYNCHRONOUSLY inside this tap
// gesture. iOS only raises the on-screen keyboard when focus() runs in
// the same user-gesture turn — deferring it (setTimeout) breaks that
// chain and the keyboard stays down. The mobileEditMode effect also
// flips setEditable(true), so this just gets there a tick earlier.
const ed = editor;
if (ed) setTimeout(() => ed.commands.focus(), 0);
if (ed) {
ed.setEditable(true);
ed.commands.focus();
}
return;
}

Expand All @@ -846,17 +853,23 @@ const DocumentEditorPanel = ({

const onScroll = (e: React.UIEvent<HTMLDivElement>) => {
if (suggestions.length > 0) updateSuggestions?.([]);
const scrollTop = e.currentTarget.scrollTop;
const el = e.currentTarget;
// Clamp to the real scroll range. iOS rubber-band overscroll reports a
// scrollTop below 0 (top) or beyond the maximum (bottom) and then springs
// back, which would otherwise feed spurious up/down deltas into the chrome
// hide and make the navbar flicker as the bounce settles. Clamping pins
// the delta to 0 while overscrolling, so the bounce leaves the bar alone.
const maxScroll = el.scrollHeight - el.clientHeight;
const scrollTop = Math.max(0, Math.min(el.scrollTop, maxScroll));
setIsScrolled(scrollTop > 0);
if (isPhone) {
updateThumb();
revealScrollThumb();

// Map scroll movement onto the hide progress 1:1 over CHROME_HIDE_RANGE
// px: scrolling down reveals more of the script by sliding the chrome
// away at content speed; scrolling up brings it straight back. Snap
// fully open at the very top. In edit mode the navbar carries the
// exit/undo/redo controls, so keep it pinned open there.
// Accumulate scroll movement into the hide progress over
// CHROME_HIDE_RANGE px: scrolling down slides the chrome away, scrolling
// up brings it back. Snap fully open at the very top. In edit mode the
// navbar carries the exit/undo/redo controls, so keep it pinned open.
const delta = scrollTop - lastScrollTop.current;
if (mobileEditMode || scrollTop <= 4) {
applyChromeHide(0);
Expand Down
18 changes: 13 additions & 5 deletions components/editor/MobileFormatToolbar.module.css
Original file line number Diff line number Diff line change
@@ -1,23 +1,31 @@
/* Phone-only formatting bar pinned just above the on-screen keyboard. `bottom`
/* Phone-only formatting bar that floats just above the on-screen keyboard. `bottom`
* is set inline from the tracked keyboard inset (see MobileFormatToolbar.tsx). */
.toolbar {
position: fixed;
left: 0;
right: 0;
z-index: 60;

/* Float as a rounded island rather than a full-width docked bar: inset from
* the screen edges and lifted off the keyboard's top edge (margins), with
* rounded corners of its own, so it echoes the iOS keyboard's rounded shape
* and reads as part of the same floating chrome instead of a hard-edged bar
* butted against it. `bottom: keyboardInset` (inline) pins it to the keyboard
* top; the bottom margin is the visible gap between the two. */
margin: 0 8px 8px;

display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
gap: 8px;

padding: 6px 12px;
padding-bottom: max(6px, env(safe-area-inset-bottom, 0px));
padding: 6px 8px;

background-color: var(--secondary);
border-top: 1px solid var(--separator);
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.12);
border: 1px solid var(--separator);
border-radius: 18px;
box-shadow: 0 4px 18px rgba(0, 0, 0, 0.18);

user-select: none;
-webkit-user-select: none;
Expand Down
24 changes: 18 additions & 6 deletions components/navbar/ProjectNavbar.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@
background-color: var(--secondary);
border-radius: 999px;
box-shadow: var(--panel-shadow);

/* Hide by sliding the pill straight up off the bar and fading in lockstep,
* both driven by --chrome-hide (0 shown → 1 hidden). The pill translates its
* FULL travel (a whole navbar height) so it keeps moving until it's gone,
* rather than collapsing in place and leaving a clipped sliver to fade out —
* translate and opacity now finish together. No transition: it tracks the
* scroll gesture directly (see applyChromeHide). On desktop these clusters
* don't render and --chrome-hide stays 0, so this is inert there. */
transform: translateY(calc(var(--chrome-hide, 0) * -1 * var(--navbar-height)));
opacity: calc(1 - var(--chrome-hide, 0));
}

/* Always hug the right edge — the only other cluster (edit-mode controls) is on
Expand Down Expand Up @@ -111,13 +121,15 @@
padding-block: 0;
padding-top: var(--safe-top);
overflow: hidden;
/* Collapse continuously as the reader scrolls down: --chrome-hide runs
* 0 (shown) → 1 (hidden), driven straight from scrollTop in
* DocumentEditorPanel. Height reclaims the space (the workspace is a flex
* column, so this reflows cleanly) and there is intentionally no
* transition — the bar tracks the gesture rather than easing after it. */
/* Collapse the bar's height continuously as the reader scrolls down:
* --chrome-hide runs 0 (shown) → 1 (hidden), driven straight from
* scrollTop in DocumentEditorPanel. Height reclaims the space (the
* workspace is a flex column, so this reflows cleanly) and there is
* intentionally no transition — it tracks the gesture, not an ease. The
* fade lives on the pill clusters themselves (.mobile_left/.mobile_right)
* so it stays in lockstep with their slide-up rather than fading the
* whole row on a separate curve. */
height: calc(var(--navbar-height) * (1 - var(--chrome-hide, 0)));
opacity: calc(1 - var(--chrome-hide, 0));
}

/* Fully hidden: drop hit-testing so the collapsed sliver can't catch taps. */
Expand Down
Loading
Loading