mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
Merge ef9f15dfce
into 77aca48c84
This commit is contained in:
commit
c0ba640fe7
15 changed files with 123 additions and 1 deletions
32
packages/excalidraw/actions/actionToggleGridNotebookMode.tsx
Normal file
32
packages/excalidraw/actions/actionToggleGridNotebookMode.tsx
Normal 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,
|
||||
});
|
|
@ -19,6 +19,7 @@ export const actionToggleObjectsSnapMode = register({
|
|||
...appState,
|
||||
objectsSnapModeEnabled: !this.checked!(appState),
|
||||
gridModeEnabled: false,
|
||||
gridModeNotebookEnabled: false,
|
||||
},
|
||||
captureUpdate: CaptureUpdateAction.EVENTUALLY,
|
||||
};
|
||||
|
|
|
@ -78,6 +78,7 @@ export {
|
|||
} from "./actionClipboard";
|
||||
|
||||
export { actionToggleGridMode } from "./actionToggleGridMode";
|
||||
export { actionToggleGridNotebookMode } from "./actionToggleGridNotebookMode";
|
||||
export { actionToggleZenMode } from "./actionToggleZenMode";
|
||||
export { actionToggleObjectsSnapMode } from "./actionToggleObjectsSnapMode";
|
||||
|
||||
|
|
|
@ -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+/")],
|
||||
|
|
|
@ -55,6 +55,7 @@ export type ActionName =
|
|||
| "selectAll"
|
||||
| "pasteStyles"
|
||||
| "gridMode"
|
||||
| "gridModeNotebook"
|
||||
| "zenMode"
|
||||
| "objectsSnapMode"
|
||||
| "stats"
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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")]}
|
||||
|
|
|
@ -15,6 +15,7 @@ export const CODES = {
|
|||
THREE: "Digit3",
|
||||
NINE: "Digit9",
|
||||
QUOTE: "Quote",
|
||||
SEMICOLON: "Semicolon",
|
||||
ZERO: "Digit0",
|
||||
SLASH: "Slash",
|
||||
C: "KeyC",
|
||||
|
|
|
@ -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…",
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -246,6 +246,7 @@ export const exportToCanvas = async (
|
|||
canvasBackgroundColor: viewBackgroundColor,
|
||||
imageCache,
|
||||
renderGrid: false,
|
||||
renderGridNotebook: false,
|
||||
isExporting: true,
|
||||
// empty disables embeddable rendering
|
||||
embedsValidationStatus: new Map(),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue