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
38 changes: 22 additions & 16 deletions components/board/BoardCanvas.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -285,55 +285,61 @@
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3);
}

/* Zoom controls - matching navbar button style */
/* Zoom controls - styled like the panel switcher buttons (secondary pill, no
border, secondary-hover on hover) and sitting in the same top toolbar row,
just to their right. The anchor is at top:8/left:8; the primary side has two
20px buttons + 4px gap ending ~52px, so left:56px keeps a matching 4px gap.
z-index sits above the board's top shadow gradient (z-index:10). */
.zoom_controls {
position: absolute;
bottom: 24px;
left: 24px;
top: 8px;
left: 56px;
z-index: 11;
display: flex;
align-items: center;
gap: 4px;
padding: 6px 12px;
border-radius: 64px;
border: 2px solid var(--tertiary);
gap: 2px;
height: 36px;
padding: 0 4px;
border-radius: 16px;
background-color: var(--secondary);
color: var(--secondary-text);
user-select: none;
}

.zoom_btn {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
width: 26px;
height: 28px;
border: none;
border-radius: 50%;
border-radius: 14px;
background: transparent;
color: var(--primary-text);
color: var(--secondary-text);
cursor: pointer;
transition: background-color 0.15s ease;
}

.zoom_btn:hover {
background-color: var(--tertiary);
background-color: var(--secondary-hover);
}

.zoom_level {
min-width: 50px;
min-width: 38px;
text-align: center;
font-size: 13px;
color: var(--primary-text);
font-size: 12px;
font-weight: 500;
color: var(--secondary-text);
}

/* Hints in corner with low opacity */
.hints {
position: absolute;
bottom: 24px;
right: 24px;
left: 24px;
display: flex;
flex-direction: column;
align-items: flex-end;
align-items: flex-start;
gap: 4px;
font-size: 12px;
color: var(--secondary-text);
Expand Down
4 changes: 2 additions & 2 deletions components/board/BoardCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -989,11 +989,11 @@ const BoardCanvas = ({ isVisible, docId }: { isVisible: boolean; docId: string }

<div className={styles.zoom_controls}>
<button className={styles.zoom_btn} onClick={() => zoomFromCenter(false)}>
<Minus />
<Minus size={14} />
</button>
<span className={styles.zoom_level}>{Math.round(scale * 100)}%</span>
<button className={styles.zoom_btn} onClick={() => zoomFromCenter(true)}>
<Plus />
<Plus size={14} />
</button>
</div>

Expand Down
10 changes: 10 additions & 0 deletions components/editor/DocumentEditorPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,16 @@ const DocumentEditorPanel = ({
editor.setEditable(!isReadOnly);
}, [editor, isReadOnly]);

// Marker class on the editor DOM so global CSS (scriptio.css) can drop the
// first-of-page top-margin reset in endless-scroll mode. There the page-break
// widgets are hidden, so the reset would otherwise make each page's first
// node stick to the previous page's content.
useEffect(() => {
const el = editor?.view?.dom;
if (!el) return;
el.classList.toggle("endless-scroll", isEndlessScroll);
}, [editor, isEndlessScroll]);

// Ready state
useEffect(() => {
if (editor && isYjsReady) {
Expand Down
6 changes: 5 additions & 1 deletion components/editor/sidebar/DocumentTreeItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,11 @@ const DocumentTreeItem = ({
) : (
<span className={styles.chevron_placeholder} />
)}
<Icon size={14} className={styles.type_icon} />
<Icon
size={14}
className={styles.type_icon}
style={node.color ? { color: node.color } : undefined}
/>

{isRenaming ? (
<input
Expand Down
9 changes: 6 additions & 3 deletions components/editor/sidebar/DocumentTreeSidebarView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,12 @@ const DocumentTreeSidebarView = () => {
(parentId: string | null, type: "folder" | "editor" | "board") => {
if (!repository) return;
if (parentId) setExpanded((prev) => new Set(prev).add(parentId));
if (type === "folder") repository.createFolder(t("untitledFolder"), parentId);
else if (type === "board") repository.createBoardDocument(t("boardTitle"), parentId);
else repository.createEditorDocument(t("untitledDocument"), parentId);
let id: string;
if (type === "folder") id = repository.createFolder(t("untitledFolder"), parentId);
else if (type === "board") id = repository.createBoardDocument(t("boardTitle"), parentId);
else id = repository.createEditorDocument(t("untitledDocument"), parentId);
// Drop the new node straight into inline rename so the writer can name it instantly.
if (id) setRenamingId(id);
},
[repository, t],
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
display: flex;
flex-direction: column;
gap: 20px;
padding: 0 15px 30px 30px;
padding: 0 15px 20px 30px;
width: var(--navigation-sidebar-width);
min-height: 0;
overflow: hidden;
Expand Down
32 changes: 21 additions & 11 deletions components/navbar/ProductionPanel.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
display: flex;
flex-direction: column;
border-radius: 16px;
overflow: hidden;
/* Not `hidden`: the revision dropdown menu in the last section needs to
extend past the panel's bottom edge without being clipped. */
overflow: visible;
}

.header {
Expand Down Expand Up @@ -162,19 +164,27 @@
cursor: not-allowed;
}

/* Revision swatches */
.swatches {
display: flex;
flex-wrap: wrap;
gap: 6px;
/* Revision color dropdown */
.revision_select {
margin-top: 10px;
padding: 8px 12px;
font-size: 0.85rem;
background: var(--secondary);
border: 1px solid var(--separator);
border-radius: 8px;
color: var(--primary-text);
}

.swatch {
width: 18px;
height: 18px;
.revision_option {
display: flex;
align-items: center;
gap: 10px;
}

.revision_dot {
width: 14px;
height: 14px;
border-radius: 50%;
border: 1px solid var(--separator);
opacity: 0.5;
cursor: not-allowed;
flex-shrink: 0;
}
55 changes: 34 additions & 21 deletions components/navbar/ProductionPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { useCallback, useContext, useEffect, useMemo, useRef } from "react";
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
import { useTranslations } from "next-intl";
import { X, BookOpen, Clapperboard, PencilLine, Settings } from "lucide-react";

Expand All @@ -12,6 +12,7 @@ import { computeSceneItems } from "@src/lib/screenplay/scenes";
import { unlockDraftPopup, unlockPagesPopup, unlockScenesPopup } from "@src/lib/screenplay/popup";
import { getPageAnchors, getPageAnchorInfo } from "@src/lib/screenplay/extensions/pagination-extension";
import Switch from "@components/utils/Switch";
import Dropdown, { DropdownOption } from "@components/utils/Dropdown";

import styles from "./ProductionPanel.module.css";

Expand All @@ -20,16 +21,18 @@ interface ProductionPanelProps {
onClose: () => void;
}

const REVISION_COLORS = [
"#ffffff", // white
"#bbdfff", // blue
"#ffb6c1", // pink
"#ffea7a", // yellow
"#a5d6a7", // green
"#d4a017", // goldenrod
"#e0c58b", // buff
"#fa8072", // salmon
"#9b1c2a", // cherry
// Standard production revision color order. Names stay in English on purpose —
// they're surfaced verbatim in the printed page headers.
const REVISION_COLORS: { name: string; value: string }[] = [
{ name: "White", value: "#ffffff" },
{ name: "Blue", value: "#bbdfff" },
{ name: "Pink", value: "#ffb6c1" },
{ name: "Yellow", value: "#ffea7a" },
{ name: "Green", value: "#a5d6a7" },
{ name: "Goldenrod", value: "#d4a017" },
{ name: "Buff", value: "#e0c58b" },
{ name: "Salmon", value: "#fa8072" },
{ name: "Cherry", value: "#9b1c2a" },
];

const ProductionPanel = ({ isOpen, onClose }: ProductionPanelProps) => {
Expand All @@ -52,6 +55,20 @@ const ProductionPanel = ({ isOpen, onClose }: ProductionPanelProps) => {

const panelRef = useRef<HTMLDivElement>(null);

// Revisions are inert in v1: this only tracks the previewed color locally
// until revision tracking is wired to the repository.
const [revisionColor, setRevisionColor] = useState(REVISION_COLORS[0].name);

const revisionOptions: DropdownOption[] = REVISION_COLORS.map((c) => ({
value: c.name,
label: (
<span className={styles.revision_option}>
<span className={styles.revision_dot} style={{ backgroundColor: c.value }} />
{c.name}
</span>
),
}));

const handleOpenSettings = () => {
onClose();
openDashboard("Production");
Expand Down Expand Up @@ -424,16 +441,12 @@ const ProductionPanel = ({ isOpen, onClose }: ProductionPanelProps) => {
</div>
<Switch checked={false} onChange={() => {}} ariaLabel={t("revisions")} />
</div>
<div className={styles.swatches}>
{REVISION_COLORS.map((color, idx) => (
<span
key={idx}
className={styles.swatch}
style={{ backgroundColor: color }}
aria-disabled
/>
))}
</div>
<Dropdown
value={revisionColor}
onChange={setRevisionColor}
options={revisionOptions}
className={styles.revision_select}
/>
</div>
</div>
);
Expand Down
76 changes: 1 addition & 75 deletions components/navbar/ViewOptionsDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
"use client";

import { useContext, useState, useRef, useEffect, useCallback } from "react";
import { useState, useRef, useEffect, useCallback } from "react";
import { useTranslations } from "next-intl";
import { UserContext } from "@src/context/UserContext";
import { PanelType, useViewContext } from "@src/context/ViewContext";
import {
ChevronDown,
Scroll,
MessageSquare,
MessageSquareOff,
Maximize,
Minimize,
PanelRight,
PanelRightClose,
ArrowLeftRight,
Expand All @@ -22,14 +16,7 @@ import styles from "./ViewOptionsDropdown.module.css";

const ViewOptionsDropdown = () => {
const t = useTranslations("navbar");
const { isZenMode, updateIsZenMode } = useContext(UserContext);
const {
isEndlessScroll,
setIsEndlessScroll,
showComments,
setShowComments,
setLeftSidebarOpen,
setRightSidebarOpen,
isSplit,
primaryPanel,
setSecondaryPanel,
Expand All @@ -50,7 +37,6 @@ const ViewOptionsDropdown = () => {
}, [isSplit, primaryPanel, setSecondaryPanel]);

const [isOpen, setIsOpen] = useState(false);
const sidebarsBeforeFocus = useRef<{ left: boolean; right: boolean } | null>(null);
const dropdownRef = useRef<HTMLDivElement>(null);

// Close dropdown when clicking outside
Expand All @@ -67,41 +53,6 @@ const ViewOptionsDropdown = () => {
return () => window.removeEventListener("mousedown", handleClickOutside);
}, [isOpen]);

const enterFocusMode = useCallback(() => {
setLeftSidebarOpen((prev) => {
setRightSidebarOpen((prevRight) => {
sidebarsBeforeFocus.current = { left: prev, right: prevRight };
return false;
});
return false;
});
updateIsZenMode(true);
document.documentElement.requestFullscreen?.();
}, [updateIsZenMode, setLeftSidebarOpen, setRightSidebarOpen]);

const exitFocusMode = useCallback(() => {
updateIsZenMode(false);
if (document.fullscreenElement) {
document.exitFullscreen();
}
if (sidebarsBeforeFocus.current) {
setLeftSidebarOpen(sidebarsBeforeFocus.current.left);
setRightSidebarOpen(sidebarsBeforeFocus.current.right);
sidebarsBeforeFocus.current = null;
}
}, [updateIsZenMode, setLeftSidebarOpen, setRightSidebarOpen]);

// Sync zen mode state when user exits fullscreen via Escape
useEffect(() => {
const onFullscreenChange = () => {
if (!document.fullscreenElement && isZenMode) {
exitFocusMode();
}
};
document.addEventListener("fullscreenchange", onFullscreenChange);
return () => document.removeEventListener("fullscreenchange", onFullscreenChange);
}, [isZenMode, exitFocusMode]);

return (
<div className={styles.container} ref={dropdownRef}>
<button className={styles.trigger} onClick={() => setIsOpen(!isOpen)}>
Expand All @@ -124,31 +75,6 @@ const ViewOptionsDropdown = () => {
<ListTree size={16} />
<span className={styles.item_label}>{t("outline")}</span>
</button>
<button
className={`${styles.dropdown_item} ${isEndlessScroll ? styles.dropdown_item_active : ""}`}
onClick={() => setIsEndlessScroll(!isEndlessScroll)}
>
<Scroll size={16} />
<span className={styles.item_label}>{t("endlessScroll")}</span>
</button>
<button
className={`${styles.dropdown_item} ${!showComments ? styles.dropdown_item_active : ""}`}
onClick={() => setShowComments(!showComments)}
>
{showComments ? (
<MessageSquare size={16} />
) : (
<MessageSquareOff size={16} />
)}
<span className={styles.item_label}>{t("toggleComments")}</span>
</button>
<button
className={`${styles.dropdown_item} ${isZenMode ? styles.dropdown_item_active : ""}`}
onClick={isZenMode ? exitFocusMode : enterFocusMode}
>
{isZenMode ? <Minimize size={16} /> : <Maximize size={16} />}
<span className={styles.item_label}>{t("focusMode")}</span>
</button>
<button
className={`${styles.dropdown_item} ${isSplit ? styles.dropdown_item_active : ""}`}
onClick={handleSplitToggle}
Expand Down
Loading
Loading