mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
added notebook grid mode
This commit is contained in:
parent
68578556ff
commit
ef9f15dfce
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,
|
||||||
|
});
|
|
@ -18,6 +18,7 @@ export const actionToggleObjectsSnapMode = register({
|
||||||
...appState,
|
...appState,
|
||||||
objectsSnapModeEnabled: !this.checked!(appState),
|
objectsSnapModeEnabled: !this.checked!(appState),
|
||||||
gridModeEnabled: false,
|
gridModeEnabled: false,
|
||||||
|
gridModeNotebookEnabled: false,
|
||||||
},
|
},
|
||||||
captureUpdate: CaptureUpdateAction.EVENTUALLY,
|
captureUpdate: CaptureUpdateAction.EVENTUALLY,
|
||||||
};
|
};
|
||||||
|
|
|
@ -78,6 +78,7 @@ export {
|
||||||
} from "./actionClipboard";
|
} from "./actionClipboard";
|
||||||
|
|
||||||
export { actionToggleGridMode } from "./actionToggleGridMode";
|
export { actionToggleGridMode } from "./actionToggleGridMode";
|
||||||
|
export { actionToggleGridNotebookMode } from "./actionToggleGridNotebookMode";
|
||||||
export { actionToggleZenMode } from "./actionToggleZenMode";
|
export { actionToggleZenMode } from "./actionToggleZenMode";
|
||||||
export { actionToggleObjectsSnapMode } from "./actionToggleObjectsSnapMode";
|
export { actionToggleObjectsSnapMode } from "./actionToggleObjectsSnapMode";
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ export type ShortcutName =
|
||||||
| "group"
|
| "group"
|
||||||
| "ungroup"
|
| "ungroup"
|
||||||
| "gridMode"
|
| "gridMode"
|
||||||
|
| "gridModeNotebook"
|
||||||
| "zenMode"
|
| "zenMode"
|
||||||
| "objectsSnapMode"
|
| "objectsSnapMode"
|
||||||
| "stats"
|
| "stats"
|
||||||
|
@ -91,6 +92,7 @@ const shortcutMap: Record<ShortcutName, string[]> = {
|
||||||
group: [getShortcutKey("CtrlOrCmd+G")],
|
group: [getShortcutKey("CtrlOrCmd+G")],
|
||||||
ungroup: [getShortcutKey("CtrlOrCmd+Shift+G")],
|
ungroup: [getShortcutKey("CtrlOrCmd+Shift+G")],
|
||||||
gridMode: [getShortcutKey("CtrlOrCmd+'")],
|
gridMode: [getShortcutKey("CtrlOrCmd+'")],
|
||||||
|
gridModeNotebook: [getShortcutKey("CtrlOrCmd+;")],
|
||||||
zenMode: [getShortcutKey("Alt+Z")],
|
zenMode: [getShortcutKey("Alt+Z")],
|
||||||
objectsSnapMode: [getShortcutKey("Alt+S")],
|
objectsSnapMode: [getShortcutKey("Alt+S")],
|
||||||
stats: [getShortcutKey("Alt+/")],
|
stats: [getShortcutKey("Alt+/")],
|
||||||
|
|
|
@ -55,6 +55,7 @@ export type ActionName =
|
||||||
| "selectAll"
|
| "selectAll"
|
||||||
| "pasteStyles"
|
| "pasteStyles"
|
||||||
| "gridMode"
|
| "gridMode"
|
||||||
|
| "gridModeNotebook"
|
||||||
| "zenMode"
|
| "zenMode"
|
||||||
| "objectsSnapMode"
|
| "objectsSnapMode"
|
||||||
| "stats"
|
| "stats"
|
||||||
|
|
|
@ -64,6 +64,7 @@ export const getDefaultAppState = (): Omit<
|
||||||
gridSize: DEFAULT_GRID_SIZE,
|
gridSize: DEFAULT_GRID_SIZE,
|
||||||
gridStep: DEFAULT_GRID_STEP,
|
gridStep: DEFAULT_GRID_STEP,
|
||||||
gridModeEnabled: false,
|
gridModeEnabled: false,
|
||||||
|
gridModeNotebookEnabled: false,
|
||||||
isBindingEnabled: true,
|
isBindingEnabled: true,
|
||||||
defaultSidebarDockedPreference: false,
|
defaultSidebarDockedPreference: false,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
|
@ -184,6 +185,7 @@ const APP_STATE_STORAGE_CONF = (<
|
||||||
gridSize: { browser: true, export: true, server: true },
|
gridSize: { browser: true, export: true, server: true },
|
||||||
gridStep: { browser: true, export: true, server: true },
|
gridStep: { browser: true, export: true, server: true },
|
||||||
gridModeEnabled: { 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 },
|
height: { browser: false, export: false, server: false },
|
||||||
isBindingEnabled: { browser: false, export: false, server: false },
|
isBindingEnabled: { browser: false, export: false, server: false },
|
||||||
defaultSidebarDockedPreference: {
|
defaultSidebarDockedPreference: {
|
||||||
|
|
|
@ -26,6 +26,7 @@ import {
|
||||||
actionSendBackward,
|
actionSendBackward,
|
||||||
actionSendToBack,
|
actionSendToBack,
|
||||||
actionToggleGridMode,
|
actionToggleGridMode,
|
||||||
|
actionToggleGridNotebookMode,
|
||||||
actionToggleStats,
|
actionToggleStats,
|
||||||
actionToggleZenMode,
|
actionToggleZenMode,
|
||||||
actionUnbindText,
|
actionUnbindText,
|
||||||
|
@ -388,6 +389,7 @@ import {
|
||||||
SnapCache,
|
SnapCache,
|
||||||
isGridModeEnabled,
|
isGridModeEnabled,
|
||||||
getGridPoint,
|
getGridPoint,
|
||||||
|
isGridModeNotebookEnabled,
|
||||||
} from "../snapping";
|
} from "../snapping";
|
||||||
import { actionWrapTextInContainer } from "../actions/actionBoundText";
|
import { actionWrapTextInContainer } from "../actions/actionBoundText";
|
||||||
import BraveMeasureTextError from "./BraveMeasureTextError";
|
import BraveMeasureTextError from "./BraveMeasureTextError";
|
||||||
|
@ -1740,6 +1742,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
imageCache: this.imageCache,
|
imageCache: this.imageCache,
|
||||||
isExporting: false,
|
isExporting: false,
|
||||||
renderGrid: isGridModeEnabled(this),
|
renderGrid: isGridModeEnabled(this),
|
||||||
|
renderGridNotebook: isGridModeNotebookEnabled(this),
|
||||||
canvasBackgroundColor:
|
canvasBackgroundColor:
|
||||||
this.state.viewBackgroundColor,
|
this.state.viewBackgroundColor,
|
||||||
embedsValidationStatus: this.embedsValidationStatus,
|
embedsValidationStatus: this.embedsValidationStatus,
|
||||||
|
@ -1759,6 +1762,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
imageCache: this.imageCache,
|
imageCache: this.imageCache,
|
||||||
isExporting: false,
|
isExporting: false,
|
||||||
renderGrid: false,
|
renderGrid: false,
|
||||||
|
renderGridNotebook: false,
|
||||||
canvasBackgroundColor:
|
canvasBackgroundColor:
|
||||||
this.state.viewBackgroundColor,
|
this.state.viewBackgroundColor,
|
||||||
embedsValidationStatus:
|
embedsValidationStatus:
|
||||||
|
@ -10783,6 +10787,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
return [
|
return [
|
||||||
...options,
|
...options,
|
||||||
actionToggleGridMode,
|
actionToggleGridMode,
|
||||||
|
actionToggleGridNotebookMode,
|
||||||
actionToggleZenMode,
|
actionToggleZenMode,
|
||||||
actionToggleViewMode,
|
actionToggleViewMode,
|
||||||
actionToggleStats,
|
actionToggleStats,
|
||||||
|
@ -10800,6 +10805,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
actionUnlockAllElements,
|
actionUnlockAllElements,
|
||||||
CONTEXT_MENU_SEPARATOR,
|
CONTEXT_MENU_SEPARATOR,
|
||||||
actionToggleGridMode,
|
actionToggleGridMode,
|
||||||
|
actionToggleGridNotebookMode,
|
||||||
actionToggleObjectsSnapMode,
|
actionToggleObjectsSnapMode,
|
||||||
actionToggleZenMode,
|
actionToggleZenMode,
|
||||||
actionToggleViewMode,
|
actionToggleViewMode,
|
||||||
|
|
|
@ -287,6 +287,10 @@ export const HelpDialog = ({ onClose }: { onClose?: () => void }) => {
|
||||||
label={t("labels.toggleGrid")}
|
label={t("labels.toggleGrid")}
|
||||||
shortcuts={[getShortcutKey("CtrlOrCmd+'")]}
|
shortcuts={[getShortcutKey("CtrlOrCmd+'")]}
|
||||||
/>
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("labels.toggleGridNotebook")}
|
||||||
|
shortcuts={[getShortcutKey("CtrlOrCmd+;")]}
|
||||||
|
/>
|
||||||
<Shortcut
|
<Shortcut
|
||||||
label={t("labels.viewMode")}
|
label={t("labels.viewMode")}
|
||||||
shortcuts={[getShortcutKey("Alt+R")]}
|
shortcuts={[getShortcutKey("Alt+R")]}
|
||||||
|
|
|
@ -14,6 +14,7 @@ export const CODES = {
|
||||||
THREE: "Digit3",
|
THREE: "Digit3",
|
||||||
NINE: "Digit9",
|
NINE: "Digit9",
|
||||||
QUOTE: "Quote",
|
QUOTE: "Quote",
|
||||||
|
SEMICOLON: "Semicolon",
|
||||||
ZERO: "Digit0",
|
ZERO: "Digit0",
|
||||||
SLASH: "Slash",
|
SLASH: "Slash",
|
||||||
C: "KeyC",
|
C: "KeyC",
|
||||||
|
|
|
@ -96,6 +96,7 @@
|
||||||
"ungroup": "Ungroup selection",
|
"ungroup": "Ungroup selection",
|
||||||
"collaborators": "Collaborators",
|
"collaborators": "Collaborators",
|
||||||
"toggleGrid": "Toggle grid",
|
"toggleGrid": "Toggle grid",
|
||||||
|
"toggleGridNotebook": "Toggle grid notebook",
|
||||||
"addToLibrary": "Add to library",
|
"addToLibrary": "Add to library",
|
||||||
"removeFromLibrary": "Remove from library",
|
"removeFromLibrary": "Remove from library",
|
||||||
"libraryLoadingMessage": "Loading library…",
|
"libraryLoadingMessage": "Loading library…",
|
||||||
|
|
|
@ -108,6 +108,61 @@ const strokeGrid = (
|
||||||
}
|
}
|
||||||
context.restore();
|
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 = (
|
const frameClip = (
|
||||||
frame: ExcalidrawFrameLikeElement,
|
frame: ExcalidrawFrameLikeElement,
|
||||||
|
@ -219,7 +274,7 @@ const _renderStaticScene = ({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { renderGrid = true, isExporting } = renderConfig;
|
const { renderGrid = true, renderGridNotebook=true, isExporting } = renderConfig;
|
||||||
|
|
||||||
const [normalizedWidth, normalizedHeight] = getNormalizedCanvasDimensions(
|
const [normalizedWidth, normalizedHeight] = getNormalizedCanvasDimensions(
|
||||||
canvas,
|
canvas,
|
||||||
|
@ -251,6 +306,16 @@ const _renderStaticScene = ({
|
||||||
normalizedWidth / appState.zoom.value,
|
normalizedWidth / appState.zoom.value,
|
||||||
normalizedHeight / 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>();
|
const groupsToBeAddedToFrame = new Set<string>();
|
||||||
|
|
|
@ -244,6 +244,7 @@ export const exportToCanvas = async (
|
||||||
canvasBackgroundColor: viewBackgroundColor,
|
canvasBackgroundColor: viewBackgroundColor,
|
||||||
imageCache,
|
imageCache,
|
||||||
renderGrid: false,
|
renderGrid: false,
|
||||||
|
renderGridNotebook: false,
|
||||||
isExporting: true,
|
isExporting: true,
|
||||||
// empty disables embeddable rendering
|
// empty disables embeddable rendering
|
||||||
embedsValidationStatus: new Map(),
|
embedsValidationStatus: new Map(),
|
||||||
|
|
|
@ -29,6 +29,7 @@ export type StaticCanvasRenderConfig = {
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
imageCache: AppClassProperties["imageCache"];
|
imageCache: AppClassProperties["imageCache"];
|
||||||
renderGrid: boolean;
|
renderGrid: boolean;
|
||||||
|
renderGridNotebook: boolean;
|
||||||
/** when exporting the behavior is slightly different (e.g. we can't use
|
/** when exporting the behavior is slightly different (e.g. we can't use
|
||||||
CSS filters), and we disable render optimizations for best output */
|
CSS filters), and we disable render optimizations for best output */
|
||||||
isExporting: boolean;
|
isExporting: boolean;
|
||||||
|
|
|
@ -154,6 +154,8 @@ export class SnapCache {
|
||||||
|
|
||||||
export const isGridModeEnabled = (app: AppClassProperties): boolean =>
|
export const isGridModeEnabled = (app: AppClassProperties): boolean =>
|
||||||
app.props.gridModeEnabled ?? app.state.gridModeEnabled;
|
app.props.gridModeEnabled ?? app.state.gridModeEnabled;
|
||||||
|
export const isGridModeNotebookEnabled = (app: AppClassProperties): boolean =>
|
||||||
|
app.props.gridModeNotebookEnabled ?? app.state.gridModeNotebookEnabled;
|
||||||
|
|
||||||
export const isSnappingEnabled = ({
|
export const isSnappingEnabled = ({
|
||||||
event,
|
event,
|
||||||
|
|
|
@ -361,6 +361,7 @@ export interface AppState {
|
||||||
gridSize: number;
|
gridSize: number;
|
||||||
gridStep: number;
|
gridStep: number;
|
||||||
gridModeEnabled: boolean;
|
gridModeEnabled: boolean;
|
||||||
|
gridModeNotebookEnabled: boolean;
|
||||||
viewModeEnabled: boolean;
|
viewModeEnabled: boolean;
|
||||||
|
|
||||||
/** top-most selected groups (i.e. does not include nested groups) */
|
/** top-most selected groups (i.e. does not include nested groups) */
|
||||||
|
@ -538,6 +539,7 @@ export interface ExcalidrawProps {
|
||||||
viewModeEnabled?: boolean;
|
viewModeEnabled?: boolean;
|
||||||
zenModeEnabled?: boolean;
|
zenModeEnabled?: boolean;
|
||||||
gridModeEnabled?: boolean;
|
gridModeEnabled?: boolean;
|
||||||
|
gridModeNotebookEnabled?: boolean;
|
||||||
objectsSnapModeEnabled?: boolean;
|
objectsSnapModeEnabled?: boolean;
|
||||||
libraryReturnUrl?: string;
|
libraryReturnUrl?: string;
|
||||||
theme?: Theme;
|
theme?: Theme;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue