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
42 changes: 34 additions & 8 deletions components/dashboard/DashboardModal.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
display: none;
align-items: center;
justify-content: center;
flex-shrink: 0;
width: 36px;
height: 36px;
border: none;
Expand Down Expand Up @@ -146,6 +147,14 @@
min-width: 0;
}

/* Truncate rather than grow: a long tab title must not push the close button off
its gutter, so the button sits at the same spot on every tab. */
.headerLeft h3 {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

/* Back to the sections list; only rendered on phone (see DashboardModal). */
.back_btn {
display: flex;
Expand Down Expand Up @@ -210,6 +219,7 @@
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
width: 36px;
height: 36px;
border: none;
Expand Down Expand Up @@ -252,6 +262,11 @@
}

.modal {
/* Single horizontal gutter for both drawer screens (sections list and
* section content). Both use it for their outer edges so the close button
* and the panel content sit on the same lines whichever screen is up —
* switching screens must not shift the close button. */
--drawer-gutter: 16px;
width: var(--mobile-drawer-width);
max-width: var(--mobile-drawer-width);
height: 100%;
Expand All @@ -268,7 +283,8 @@
.sidebar {
display: none;
flex: 1;
padding: 8px calc(8px + var(--safe-right)) calc(16px + var(--safe-bottom)) 8px;
padding: 12px calc(var(--drawer-gutter) + var(--safe-right)) calc(16px + var(--safe-bottom))
var(--drawer-gutter);
border-right: none;
overflow-y: auto;
/* On mobile the whole modal is one drawer, so the sections screen wears the
Expand All @@ -277,18 +293,23 @@
background: var(--main-bg);
}

/* No horizontal padding of its own: the close button must land on the drawer
* gutter, exactly where the content screen's close button sits. */
.sidebarHeader {
padding: 4px 12px 8px;
padding: 4px 0 8px;
}

/* Align the title with the section labels + item icons below. Their left edge is
* navMenu(12) + own padding(12); the header only carries 12, so the title needs
* its own 12 to reach the same line. */
/* Align the title with the section labels + item icons below, whose left edge
* is the gutter + navItem's own padding(12). */
.sidebarTitle {
padding: 0 12px;
margin-bottom: 0;
}

.navMenu {
padding: 0;
}

.navMenuFooter {
margin-top: 24px;
padding-top: 12px;
Expand All @@ -309,10 +330,11 @@

.content {
flex: 1;
/* Symmetric horizontal padding + the right safe-area inset so the close
/* Symmetric horizontal gutter + the right safe-area inset so the close
* button and settings panels (Keybinds, Layout, …) never crowd the screen
* edge. */
padding: 12px calc(16px + var(--safe-right)) calc(16px + var(--safe-bottom)) 16px;
padding: 12px calc(var(--drawer-gutter) + var(--safe-right)) calc(16px + var(--safe-bottom))
var(--drawer-gutter);
/* Nudge inherited/em-based text down a touch for a denser, phone-friendly
* scale; width-driven layouts reflow into the narrow drawer on their own. */
font-size: 0.92rem;
Expand All @@ -326,7 +348,11 @@
font-size: 1.1rem;
}

/* The drawer gutter is the only horizontal padding on phone: no extra right
* padding and no reserved scrollbar gutter, so panel content lines up with the
* header on both edges instead of drifting left. */
.scrollArea {
padding-right: 6px;
padding-right: 0;
scrollbar-gutter: auto;
}
}
56 changes: 46 additions & 10 deletions components/editor/sidebar/SidebarCharacterItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import { pasteText } from "@src/lib/screenplay/editor";

import { ProjectContext } from "@src/context/ProjectContext";
import { join } from "@src/lib/utils/misc";
import { useTranslations } from "next-intl";

import { Highlighter, Link } from "lucide-react";
import { Highlighter, Link, MoreVertical } from "lucide-react";
import item from "./SidebarItem.module.css";

const DEFAULT_HIGHLIGHT_COLOR = "#6366f1"; // Indigo - matches extension default
Expand All @@ -18,21 +19,45 @@ type SidebarCharacterItemProps = CharacterContextProps & {
};

const SidebarCharacterItem = memo(({ character, isHighlighted }: SidebarCharacterItemProps) => {
const t = useTranslations("contextMenu");
const { updateContextMenu } = useContext(UserContext);
const { editor } = useContext(ProjectContext);
const { editor, isReadOnly } = useContext(ProjectContext);

const highlightColor = character.color || DEFAULT_HIGHLIGHT_COLOR;

// Clamp so the menu never opens off the right/bottom edge (matters on touch,
// where it's triggered from the ⋮ button near the panel edge). Read-only
// collapses the menu to the single Highlight item, so it needs far less room.
const openMenu = useCallback(
(x: number, y: number) => {
updateContextMenu({
type: ContextMenuType.CharacterItem,
position: {
x: Math.min(x, window.innerWidth - 230),
y: Math.min(y, window.innerHeight - (isReadOnly ? 60 : 180)),
},
typeSpecificProps: {
character,
},
});
},
[updateContextMenu, character, isReadOnly],
);

const handleDropdown = useCallback((e: React.MouseEvent) => {
e.preventDefault();
updateContextMenu({
type: ContextMenuType.CharacterItem,
position: { x: e.clientX, y: e.clientY },
typeSpecificProps: {
character,
},
});
}, [updateContextMenu, character]);
openMenu(e.clientX, e.clientY);
}, [openMenu]);

// Touch equivalent of right-click: the ⋮ button (shown only on coarse
// pointers). stopPropagation keeps the click from bubbling to the
// context-menu host's close-on-click handler.
const handleMenuButton = useCallback((e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
openMenu(rect.left, rect.bottom);
}, [openMenu]);

const handleDoubleClick = useCallback(() => {
// paste character name on double click
Expand All @@ -53,6 +78,17 @@ const SidebarCharacterItem = memo(({ character, isHighlighted }: SidebarCharacte
<Highlighter size={13} className={item.highlight_icon} style={{ color: highlightColor }} />
)}
{character.persistent && <Link size={13} className={item.icon} />}
{/* Always shown: the character menu keeps a working Highlight
* item in read-only, so gating this on write access would put
* highlighting out of reach on touch. */}
<button
className={item.menu_btn}
onPointerDown={(e) => e.stopPropagation()}
onClick={handleMenuButton}
aria-label={t("characterOptions")}
>
<MoreVertical size={16} />
</button>
</div>
</div>
</div>
Expand Down
62 changes: 50 additions & 12 deletions components/editor/sidebar/SidebarLocationItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,48 @@ import { UserContext } from "@src/context/UserContext";
import { pasteText } from "@src/lib/screenplay/editor";
import { ProjectContext } from "@src/context/ProjectContext";
import { join } from "@src/lib/utils/misc";
import { useTranslations } from "next-intl";

import { Link } from "lucide-react";
import { Link, MoreVertical } from "lucide-react";
import item from "./SidebarItem.module.css";

const SidebarLocationItem = memo(({ location }: LocationContextProps) => {
const t = useTranslations("contextMenu");
const { updateContextMenu } = useContext(UserContext);
const { editor } = useContext(ProjectContext);
const { editor, isReadOnly } = useContext(ProjectContext);

// Clamp so the menu never opens off the right/bottom edge (matters on touch,
// where it's triggered from the ⋮ button near the panel edge).
const openMenu = useCallback(
(x: number, y: number) => {
updateContextMenu({
type: ContextMenuType.LocationItem,
position: {
x: Math.min(x, window.innerWidth - 230),
y: Math.min(y, window.innerHeight - 110),
},
typeSpecificProps: {
location,
},
});
},
[updateContextMenu, location],
);

const handleDropdown = useCallback((e: React.MouseEvent) => {
e.preventDefault();
updateContextMenu({
type: ContextMenuType.LocationItem,
position: { x: e.clientX, y: e.clientY },
typeSpecificProps: {
location,
},
});
}, [updateContextMenu, location]);
openMenu(e.clientX, e.clientY);
}, [openMenu]);

// Touch equivalent of right-click: the ⋮ button (shown only on coarse
// pointers). stopPropagation keeps the click from bubbling to the
// context-menu host's close-on-click handler.
const handleMenuButton = useCallback((e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
openMenu(rect.left, rect.bottom);
}, [openMenu]);

const handleDoubleClick = useCallback(() => {
// paste location name on double click
Expand All @@ -33,8 +57,22 @@ const SidebarLocationItem = memo(({ location }: LocationContextProps) => {
return (
<div onContextMenu={handleDropdown} onDoubleClick={handleDoubleClick} className={item.container}>
<div className={item.data}>
<p className={join(item.title, "unselectable")}>{location.name}</p>
{location.persistent && <Link className={item.icon} size={14} />}
<div className={item.title_row}>
<p className={join(item.title, "unselectable")}>{location.name}</p>
</div>
<div className={item.icons_row}>
{location.persistent && <Link className={item.icon} size={14} />}
{!isReadOnly && (
<button
className={item.menu_btn}
onPointerDown={(e) => e.stopPropagation()}
onClick={handleMenuButton}
aria-label={t("locationOptions")}
>
<MoreVertical size={16} />
</button>
)}
</div>
</div>
</div>
);
Expand Down
4 changes: 3 additions & 1 deletion components/editor/sidebar/SidebarSceneItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { MoreVertical } from "lucide-react";
import { ContextMenuType, SceneContextProps } from "./ContextMenu";
import { UserContext } from "@src/context/UserContext";
import { join } from "@src/lib/utils/misc";
import { useTranslations } from "next-intl";
import { Scene } from "@src/lib/screenplay/scenes";
import SceneLengthItem from "../sidebar/SceneLengthItem";

Expand All @@ -25,6 +26,7 @@ type SidebarSceneItemProps = SceneContextProps & {
};

const SidebarSceneItem = memo(({ scene, index, showDropIndicator, isDragging, isCurrent, label, isOmitted, scrollRef, onPointerDown, onDoubleClick }: SidebarSceneItemProps) => {
const t = useTranslations("contextMenu");
const { updateContextMenu } = useContext(UserContext);

// Clamp so the menu never opens off the right/bottom edge (matters on touch,
Expand Down Expand Up @@ -97,7 +99,7 @@ const SidebarSceneItem = memo(({ scene, index, showDropIndicator, isDragging, is
className={nav_item.menu_btn}
onPointerDown={(e) => e.stopPropagation()}
onClick={handleMenuButton}
aria-label="Scene options"
aria-label={t("sceneOptions")}
>
<MoreVertical size={16} />
</button>
Expand Down
5 changes: 4 additions & 1 deletion messages/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,10 @@
"unomitScene": "Szene wiederherstellen",
"insertPageBreak": "Seitenumbruch einfügen",
"removePageBreak": "Seitenumbruch entfernen",
"pageBreakHint": "Manueller Seitenumbruch"
"pageBreakHint": "Manueller Seitenumbruch",
"sceneOptions": "Szenenoptionen",
"characterOptions": "Charakteroptionen",
"locationOptions": "Ortsoptionen"
},
"popup": {
"character": {
Expand Down
5 changes: 4 additions & 1 deletion messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,10 @@
"unomitScene": "Unomit scene",
"insertPageBreak": "Insert page break",
"removePageBreak": "Remove page break",
"pageBreakHint": "Manual page break"
"pageBreakHint": "Manual page break",
"sceneOptions": "Scene options",
"characterOptions": "Character options",
"locationOptions": "Location options"
},
"popup": {
"character": {
Expand Down
5 changes: 4 additions & 1 deletion messages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,10 @@
"unomitScene": "Restaurar escena",
"insertPageBreak": "Insertar salto de página",
"removePageBreak": "Quitar salto de página",
"pageBreakHint": "Salto de página manual"
"pageBreakHint": "Salto de página manual",
"sceneOptions": "Opciones de escena",
"characterOptions": "Opciones de personaje",
"locationOptions": "Opciones de localización"
},
"popup": {
"character": {
Expand Down
5 changes: 4 additions & 1 deletion messages/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,10 @@
"unomitScene": "Restaurer la scène",
"insertPageBreak": "Insérer un saut de page",
"removePageBreak": "Supprimer le saut de page",
"pageBreakHint": "Saut de page manuel"
"pageBreakHint": "Saut de page manuel",
"sceneOptions": "Options de la scène",
"characterOptions": "Options du personnage",
"locationOptions": "Options du lieu"
},
"popup": {
"character": {
Expand Down
5 changes: 4 additions & 1 deletion messages/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,10 @@
"unomitScene": "シーンを復元",
"insertPageBreak": "改ページを挿入",
"removePageBreak": "改ページを削除",
"pageBreakHint": "手動改ページ"
"pageBreakHint": "手動改ページ",
"sceneOptions": "シーンのオプション",
"characterOptions": "登場人物のオプション",
"locationOptions": "場所のオプション"
},
"popup": {
"character": {
Expand Down
5 changes: 4 additions & 1 deletion messages/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,10 @@
"unomitScene": "씬 복원",
"insertPageBreak": "페이지 나누기 삽입",
"removePageBreak": "페이지 나누기 제거",
"pageBreakHint": "수동 페이지 나누기"
"pageBreakHint": "수동 페이지 나누기",
"sceneOptions": "장면 옵션",
"characterOptions": "인물 옵션",
"locationOptions": "장소 옵션"
},
"popup": {
"character": {
Expand Down
5 changes: 4 additions & 1 deletion messages/pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,10 @@
"unomitScene": "Przywróć scenę",
"insertPageBreak": "Wstaw podział strony",
"removePageBreak": "Usuń podział strony",
"pageBreakHint": "Ręczny podział strony"
"pageBreakHint": "Ręczny podział strony",
"sceneOptions": "Opcje sceny",
"characterOptions": "Opcje postaci",
"locationOptions": "Opcje lokalizacji"
},
"popup": {
"character": {
Expand Down
5 changes: 4 additions & 1 deletion messages/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,10 @@
"unomitScene": "恢复场景",
"insertPageBreak": "插入分页符",
"removePageBreak": "移除分页符",
"pageBreakHint": "手动分页符"
"pageBreakHint": "手动分页符",
"sceneOptions": "场景选项",
"characterOptions": "角色选项",
"locationOptions": "位置选项"
},
"popup": {
"character": {
Expand Down
Loading
Loading