This commit is contained in:
Nitish Patel 2025-03-24 13:07:45 +01:00 committed by GitHub
commit c0ba640fe7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 123 additions and 1 deletions

View file

@ -0,0 +1,32 @@
import { CODES, KEYS } from "../keys";
import { register } from "./register";
import type { AppState } from "../types";
import { gridIcon } from "../components/icons";
import { CaptureUpdateAction } from "../store";
export const actionToggleGridNotebookMode = register({
name: "gridModeNotebook",
icon: gridIcon,
keywords: ["snap"],
label: "labels.toggleGridNotebook",
viewMode: true,
trackEvent: {
category: "canvas",
predicate: (appState) => appState.gridModeNotebookEnabled,
},
perform(elements, appState) {
return {
appState: {
...appState,
gridModeNotebookEnabled: !this.checked!(appState),
objectsSnapModeEnabled: false,
},
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
},
checked: (appState: AppState) => appState.gridModeNotebookEnabled,
predicate: (element, appState, props) => {
return props.gridModeNotebookEnabled === undefined;
},
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.code === CODES.SEMICOLON,
});

View file

@ -19,6 +19,7 @@ export const actionToggleObjectsSnapMode = register({
...appState,
objectsSnapModeEnabled: !this.checked!(appState),
gridModeEnabled: false,
gridModeNotebookEnabled: false,
},
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};

View file

@ -78,6 +78,7 @@ export {
} from "./actionClipboard";
export { actionToggleGridMode } from "./actionToggleGridMode";
export { actionToggleGridNotebookMode } from "./actionToggleGridNotebookMode";
export { actionToggleZenMode } from "./actionToggleZenMode";
export { actionToggleObjectsSnapMode } from "./actionToggleObjectsSnapMode";

View file

@ -27,6 +27,7 @@ export type ShortcutName =
| "group"
| "ungroup"
| "gridMode"
| "gridModeNotebook"
| "zenMode"
| "objectsSnapMode"
| "stats"
@ -92,6 +93,7 @@ const shortcutMap: Record<ShortcutName, string[]> = {
group: [getShortcutKey("CtrlOrCmd+G")],
ungroup: [getShortcutKey("CtrlOrCmd+Shift+G")],
gridMode: [getShortcutKey("CtrlOrCmd+'")],
gridModeNotebook: [getShortcutKey("CtrlOrCmd+;")],
zenMode: [getShortcutKey("Alt+Z")],
objectsSnapMode: [getShortcutKey("Alt+S")],
stats: [getShortcutKey("Alt+/")],

View file

@ -55,6 +55,7 @@ export type ActionName =
| "selectAll"
| "pasteStyles"
| "gridMode"
| "gridModeNotebook"
| "zenMode"
| "objectsSnapMode"
| "stats"

View file

@ -65,6 +65,7 @@ export const getDefaultAppState = (): Omit<
gridSize: DEFAULT_GRID_SIZE,
gridStep: DEFAULT_GRID_STEP,
gridModeEnabled: false,
gridModeNotebookEnabled: false,
isBindingEnabled: true,
defaultSidebarDockedPreference: false,
isLoading: false,
@ -185,6 +186,7 @@ const APP_STATE_STORAGE_CONF = (<
gridSize: { browser: true, export: true, server: true },
gridStep: { browser: true, export: true, server: true },
gridModeEnabled: { browser: true, export: true, server: true },
gridModeNotebookEnabled: { browser: true, export: true, server: true },
height: { browser: false, export: false, server: false },
isBindingEnabled: { browser: false, export: false, server: false },
defaultSidebarDockedPreference: {

View file

@ -42,6 +42,7 @@ import {
actionSendBackward,
actionSendToBack,
actionToggleGridMode,
actionToggleGridNotebookMode,
actionToggleStats,
actionToggleZenMode,
actionUnbindText,
@ -336,6 +337,7 @@ import {
SnapCache,
isGridModeEnabled,
getGridPoint,
isGridModeNotebookEnabled,
} from "../snapping";
import { convertToExcalidrawElements } from "../data/transform";
import { Renderer } from "../scene/Renderer";
@ -1739,6 +1741,7 @@ class App extends React.Component<AppProps, AppState> {
imageCache: this.imageCache,
isExporting: false,
renderGrid: isGridModeEnabled(this),
renderGridNotebook: isGridModeNotebookEnabled(this),
canvasBackgroundColor:
this.state.viewBackgroundColor,
embedsValidationStatus: this.embedsValidationStatus,
@ -1758,6 +1761,7 @@ class App extends React.Component<AppProps, AppState> {
imageCache: this.imageCache,
isExporting: false,
renderGrid: false,
renderGridNotebook: false,
canvasBackgroundColor:
this.state.viewBackgroundColor,
embedsValidationStatus:
@ -10750,6 +10754,7 @@ class App extends React.Component<AppProps, AppState> {
return [
...options,
actionToggleGridMode,
actionToggleGridNotebookMode,
actionToggleZenMode,
actionToggleViewMode,
actionToggleStats,
@ -10767,6 +10772,7 @@ class App extends React.Component<AppProps, AppState> {
actionUnlockAllElements,
CONTEXT_MENU_SEPARATOR,
actionToggleGridMode,
actionToggleGridNotebookMode,
actionToggleObjectsSnapMode,
actionToggleZenMode,
actionToggleViewMode,

View file

@ -291,6 +291,10 @@ export const HelpDialog = ({ onClose }: { onClose?: () => void }) => {
label={t("labels.toggleGrid")}
shortcuts={[getShortcutKey("CtrlOrCmd+'")]}
/>
<Shortcut
label={t("labels.toggleGridNotebook")}
shortcuts={[getShortcutKey("CtrlOrCmd+;")]}
/>
<Shortcut
label={t("labels.viewMode")}
shortcuts={[getShortcutKey("Alt+R")]}

View file

@ -15,6 +15,7 @@ export const CODES = {
THREE: "Digit3",
NINE: "Digit9",
QUOTE: "Quote",
SEMICOLON: "Semicolon",
ZERO: "Digit0",
SLASH: "Slash",
C: "KeyC",

View file

@ -96,6 +96,7 @@
"ungroup": "Ungroup selection",
"collaborators": "Collaborators",
"toggleGrid": "Toggle grid",
"toggleGridNotebook": "Toggle grid notebook",
"addToLibrary": "Add to library",
"removeFromLibrary": "Remove from library",
"libraryLoadingMessage": "Loading library…",

View file

@ -109,6 +109,61 @@ const strokeGrid = (
}
context.restore();
};
const strokeNotebookLines = (
context: CanvasRenderingContext2D,
/** Line spacing in pixels */
lineSpacing: number,
/** Determines when to draw a bold line (e.g., every 5th line) */
boldStep: number,
scrollY: number,
zoom: Zoom,
width: number,
height: number
) => {
const offsetY = (scrollY % lineSpacing) - lineSpacing;
const actualLineSpacing = lineSpacing * zoom.value;
context.save();
if (zoom.value === 1) {
context.translate(0, offsetY % 1 ? 0 : 0.5);
}
for (
let y = offsetY;
y < offsetY + height + lineSpacing * 2;
y += lineSpacing
) {
const isBold = boldStep > 1 && Math.round(y - scrollY) % (boldStep * lineSpacing) === 0;
if (!isBold && actualLineSpacing < 10) {
continue;
}
const lineWidth = Math.min(1 / zoom.value, isBold ? 2 : 1);
context.lineWidth = lineWidth;
context.setLineDash([]); // Notebook lines are solid
context.beginPath();
context.strokeStyle = isBold
? "rgba(0, 0, 150, 0.2)"
: "rgba(0, 0, 255, 0.1)"; // Slightly darker bold lines
context.moveTo(0, y);
context.lineTo(width, y);
context.stroke();
}
// Optional: Add a red left margin line
context.strokeStyle = "rgba(255, 0, 0, 0.3)"; // Light red margin line
context.lineWidth = 1;
context.beginPath();
context.moveTo(40, 0); // Adjust margin position
context.lineTo(40, height);
context.stroke();
context.restore();
};
const frameClip = (
frame: ExcalidrawFrameLikeElement,
@ -220,7 +275,7 @@ const _renderStaticScene = ({
return;
}
const { renderGrid = true, isExporting } = renderConfig;
const { renderGrid = true, renderGridNotebook=true, isExporting } = renderConfig;
const [normalizedWidth, normalizedHeight] = getNormalizedCanvasDimensions(
canvas,
@ -252,6 +307,16 @@ const _renderStaticScene = ({
normalizedWidth / appState.zoom.value,
normalizedHeight / appState.zoom.value,
);
} else if (renderGridNotebook) {
strokeNotebookLines(
context,
appState.gridSize,
appState.gridStep,
appState.scrollY,
appState.zoom,
normalizedWidth / appState.zoom.value,
normalizedHeight / appState.zoom.value,
);
}
const groupsToBeAddedToFrame = new Set<string>();

View file

@ -246,6 +246,7 @@ export const exportToCanvas = async (
canvasBackgroundColor: viewBackgroundColor,
imageCache,
renderGrid: false,
renderGridNotebook: false,
isExporting: true,
// empty disables embeddable rendering
embedsValidationStatus: new Map(),

View file

@ -29,6 +29,7 @@ export type StaticCanvasRenderConfig = {
// ---------------------------------------------------------------------------
imageCache: AppClassProperties["imageCache"];
renderGrid: boolean;
renderGridNotebook: boolean;
/** when exporting the behavior is slightly different (e.g. we can't use
CSS filters), and we disable render optimizations for best output */
isExporting: boolean;

View file

@ -157,6 +157,8 @@ export class SnapCache {
export const isGridModeEnabled = (app: AppClassProperties): boolean =>
app.props.gridModeEnabled ?? app.state.gridModeEnabled;
export const isGridModeNotebookEnabled = (app: AppClassProperties): boolean =>
app.props.gridModeNotebookEnabled ?? app.state.gridModeNotebookEnabled;
export const isSnappingEnabled = ({
event,

View file

@ -361,6 +361,7 @@ export interface AppState {
gridSize: number;
gridStep: number;
gridModeEnabled: boolean;
gridModeNotebookEnabled: boolean;
viewModeEnabled: boolean;
/** top-most selected groups (i.e. does not include nested groups) */
@ -538,6 +539,7 @@ export interface ExcalidrawProps {
viewModeEnabled?: boolean;
zenModeEnabled?: boolean;
gridModeEnabled?: boolean;
gridModeNotebookEnabled?: boolean;
objectsSnapModeEnabled?: boolean;
libraryReturnUrl?: string;
theme?: Theme;