Prefer arrow functions (#2344)

This commit is contained in:
Lipis 2020-11-06 22:06:39 +02:00 committed by GitHub
parent e05acd6fd9
commit a20f3240fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 304 additions and 336 deletions

View File

@ -29,7 +29,7 @@ config.entry = "./src/index-node";
// By default, webpack is going to replace the require of the canvas.node file // By default, webpack is going to replace the require of the canvas.node file
// to just a string with the path of the canvas.node file. We need to tell // to just a string with the path of the canvas.node file. We need to tell
// webpack to avoid rewriting that dependency. // webpack to avoid rewriting that dependency.
config.externals = function (context, request, callback) { config.externals = (context, request, callback) => {
if (/\.node$/.test(request)) { if (/\.node$/.test(request)) {
return callback( return callback(
null, null,

View File

@ -23,11 +23,11 @@ const enableActionGroup = (
appState: AppState, appState: AppState,
) => getSelectedElements(getNonDeletedElements(elements), appState).length > 1; ) => getSelectedElements(getNonDeletedElements(elements), appState).length > 1;
function alignSelectedElements( const alignSelectedElements = (
elements: readonly ExcalidrawElement[], elements: readonly ExcalidrawElement[],
appState: Readonly<AppState>, appState: Readonly<AppState>,
alignment: Alignment, alignment: Alignment,
) { ) => {
const selectedElements = getSelectedElements( const selectedElements = getSelectedElements(
getNonDeletedElements(elements), getNonDeletedElements(elements),
appState, appState,
@ -38,7 +38,7 @@ function alignSelectedElements(
const updatedElementsMap = getElementMap(updatedElements); const updatedElementsMap = getElementMap(updatedElements);
return elements.map((element) => updatedElementsMap[element.id] || element); return elements.map((element) => updatedElementsMap[element.id] || element);
} };
export const actionAlignTop = register({ export const actionAlignTop = register({
name: "alignTop", name: "alignTop",

View File

@ -31,10 +31,10 @@ const deleteSelectedElements = (
}; };
}; };
function handleGroupEditingState( const handleGroupEditingState = (
appState: AppState, appState: AppState,
elements: readonly ExcalidrawElement[], elements: readonly ExcalidrawElement[],
): AppState { ): AppState => {
if (appState.editingGroupId) { if (appState.editingGroupId) {
const siblingElements = getElementsInGroup( const siblingElements = getElementsInGroup(
getNonDeletedElements(elements), getNonDeletedElements(elements),
@ -48,7 +48,7 @@ function handleGroupEditingState(
} }
} }
return appState; return appState;
} };
export const actionDeleteSelected = register({ export const actionDeleteSelected = register({
name: "deleteSelectedElements", name: "deleteSelectedElements",

View File

@ -89,7 +89,7 @@ const calculateTranslation = (
}; };
}; };
function getCommonBoundingBox(elements: ExcalidrawElement[]): Box { const getCommonBoundingBox = (elements: ExcalidrawElement[]): Box => {
const [minX, minY, maxX, maxY] = getCommonBounds(elements); const [minX, minY, maxX, maxY] = getCommonBounds(elements);
return { minX, minY, maxX, maxY }; return { minX, minY, maxX, maxY };
} };

View File

@ -24,21 +24,21 @@ type ParseSpreadsheetResult =
error: string; error: string;
}; };
function tryParseNumber(s: string): number | null { const tryParseNumber = (s: string): number | null => {
const match = /^[$€£¥₩]?([0-9]+(\.[0-9]+)?)$/.exec(s); const match = /^[$€£¥₩]?([0-9]+(\.[0-9]+)?)$/.exec(s);
if (!match) { if (!match) {
return null; return null;
} }
return parseFloat(match[1]); return parseFloat(match[1]);
} };
function isNumericColumn(lines: string[][], columnIndex: number) { const isNumericColumn = (lines: string[][], columnIndex: number) => {
return lines return lines
.slice(1) .slice(1)
.every((line) => tryParseNumber(line[columnIndex]) !== null); .every((line) => tryParseNumber(line[columnIndex]) !== null);
} };
function tryParseCells(cells: string[][]): ParseSpreadsheetResult { const tryParseCells = (cells: string[][]): ParseSpreadsheetResult => {
const numCols = cells[0].length; const numCols = cells[0].length;
if (numCols > 2) { if (numCols > 2) {
@ -94,9 +94,9 @@ function tryParseCells(cells: string[][]): ParseSpreadsheetResult {
values: rows.map((row) => tryParseNumber(row[valueColumnIndex])!), values: rows.map((row) => tryParseNumber(row[valueColumnIndex])!),
}, },
}; };
} };
function transposeCells(cells: string[][]) { const transposeCells = (cells: string[][]) => {
const nextCells: string[][] = []; const nextCells: string[][] = [];
for (let col = 0; col < cells[0].length; col++) { for (let col = 0; col < cells[0].length; col++) {
const nextCellRow: string[] = []; const nextCellRow: string[] = [];
@ -107,9 +107,9 @@ function transposeCells(cells: string[][]) {
} }
return nextCells; return nextCells;
} };
export function tryParseSpreadsheet(text: string): ParseSpreadsheetResult { export const tryParseSpreadsheet = (text: string): ParseSpreadsheetResult => {
// copy/paste from excel, in-browser excel, and google sheets is tsv // copy/paste from excel, in-browser excel, and google sheets is tsv
// for now we only accept 2 columns with an optional header // for now we only accept 2 columns with an optional header
const lines = text const lines = text
@ -139,7 +139,7 @@ export function tryParseSpreadsheet(text: string): ParseSpreadsheetResult {
} }
return result; return result;
} };
const BAR_WIDTH = 32; const BAR_WIDTH = 32;
const BAR_SPACING = 12; const BAR_SPACING = 12;
@ -148,12 +148,12 @@ const LABEL_SPACING = 3 * BAR_SPACING;
const Y_AXIS_LABEL_SPACING = LABEL_SPACING; const Y_AXIS_LABEL_SPACING = LABEL_SPACING;
const ANGLE = 5.87; const ANGLE = 5.87;
export function renderSpreadsheet( export const renderSpreadsheet = (
appState: AppState, appState: AppState,
spreadsheet: Spreadsheet, spreadsheet: Spreadsheet,
x: number, x: number,
y: number, y: number,
): ExcalidrawElement[] { ): ExcalidrawElement[] => {
const max = Math.max(...spreadsheet.values); const max = Math.max(...spreadsheet.values);
const min = Math.min(0, ...spreadsheet.values); const min = Math.min(0, ...spreadsheet.values);
const range = max - min; const range = max - min;
@ -268,4 +268,4 @@ export function renderSpreadsheet(
return [...bars, yAxisLabel, minYLabel, maxYLabel, ...xLabels].filter( return [...bars, yAxisLabel, minYLabel, maxYLabel, ...xLabels].filter(
(element) => element !== null, (element) => element !== null,
) as ExcalidrawElement[]; ) as ExcalidrawElement[];
} };

View File

@ -62,10 +62,10 @@ interface LayerUIProps {
lng: string; lng: string;
} }
function useOnClickOutside( const useOnClickOutside = (
ref: RefObject<HTMLElement>, ref: RefObject<HTMLElement>,
cb: (event: MouseEvent) => void, cb: (event: MouseEvent) => void,
) { ) => {
useEffect(() => { useEffect(() => {
const listener = (event: MouseEvent) => { const listener = (event: MouseEvent) => {
if (!ref.current) { if (!ref.current) {
@ -88,7 +88,7 @@ function useOnClickOutside(
document.removeEventListener("pointerdown", listener); document.removeEventListener("pointerdown", listener);
}; };
}, [ref, cb]); }, [ref, cb]);
} };
const LibraryMenuItems = ({ const LibraryMenuItems = ({
library, library,

View File

@ -8,7 +8,7 @@ let firebasePromise: Promise<
typeof import("firebase/app").default typeof import("firebase/app").default
> | null = null; > | null = null;
async function loadFirebase() { const loadFirebase = async () => {
const firebase = ( const firebase = (
await import(/* webpackChunkName: "firebase" */ "firebase/app") await import(/* webpackChunkName: "firebase" */ "firebase/app")
).default; ).default;
@ -18,15 +18,17 @@ async function loadFirebase() {
firebase.initializeApp(firebaseConfig); firebase.initializeApp(firebaseConfig);
return firebase; return firebase;
} };
async function getFirebase(): Promise<typeof import("firebase/app").default> { const getFirebase = async (): Promise<
typeof import("firebase/app").default
> => {
if (!firebasePromise) { if (!firebasePromise) {
firebasePromise = loadFirebase(); firebasePromise = loadFirebase();
} }
const firebase = await firebasePromise!; const firebase = await firebasePromise!;
return firebase; return firebase;
} };
interface FirebaseStoredScene { interface FirebaseStoredScene {
sceneVersion: number; sceneVersion: number;
@ -34,10 +36,10 @@ interface FirebaseStoredScene {
ciphertext: firebase.default.firestore.Blob; ciphertext: firebase.default.firestore.Blob;
} }
async function encryptElements( const encryptElements = async (
key: string, key: string,
elements: readonly ExcalidrawElement[], elements: readonly ExcalidrawElement[],
): Promise<{ ciphertext: ArrayBuffer; iv: Uint8Array }> { ): Promise<{ ciphertext: ArrayBuffer; iv: Uint8Array }> => {
const importedKey = await getImportedKey(key, "encrypt"); const importedKey = await getImportedKey(key, "encrypt");
const iv = createIV(); const iv = createIV();
const json = JSON.stringify(elements); const json = JSON.stringify(elements);
@ -52,13 +54,13 @@ async function encryptElements(
); );
return { ciphertext, iv }; return { ciphertext, iv };
} };
async function decryptElements( const decryptElements = async (
key: string, key: string,
iv: Uint8Array, iv: Uint8Array,
ciphertext: ArrayBuffer, ciphertext: ArrayBuffer,
): Promise<readonly ExcalidrawElement[]> { ): Promise<readonly ExcalidrawElement[]> => {
const importedKey = await getImportedKey(key, "decrypt"); const importedKey = await getImportedKey(key, "decrypt");
const decrypted = await window.crypto.subtle.decrypt( const decrypted = await window.crypto.subtle.decrypt(
{ {
@ -73,7 +75,7 @@ async function decryptElements(
new Uint8Array(decrypted) as any, new Uint8Array(decrypted) as any,
); );
return JSON.parse(decodedData); return JSON.parse(decodedData);
} };
const firebaseSceneVersionCache = new WeakMap<SocketIOClient.Socket, number>(); const firebaseSceneVersionCache = new WeakMap<SocketIOClient.Socket, number>();
@ -90,10 +92,10 @@ export const isSavedToFirebase = (
return true; return true;
}; };
export async function saveToFirebase( export const saveToFirebase = async (
portal: Portal, portal: Portal,
elements: readonly ExcalidrawElement[], elements: readonly ExcalidrawElement[],
) { ) => {
const { roomID, roomKey, socket } = portal; const { roomID, roomKey, socket } = portal;
if ( if (
// if no room exists, consider the room saved because there's nothing we can // if no room exists, consider the room saved because there's nothing we can
@ -141,12 +143,12 @@ export async function saveToFirebase(
} }
return didUpdate; return didUpdate;
} };
export async function loadFromFirebase( export const loadFromFirebase = async (
roomID: string, roomID: string,
roomKey: string, roomKey: string,
): Promise<readonly ExcalidrawElement[] | null> { ): Promise<readonly ExcalidrawElement[] | null> => {
const firebase = await getFirebase(); const firebase = await getFirebase();
const db = firebase.firestore(); const db = firebase.firestore();
@ -159,4 +161,4 @@ export async function loadFromFirebase(
const ciphertext = storedScene.ciphertext.toUint8Array(); const ciphertext = storedScene.ciphertext.toUint8Array();
const iv = storedScene.iv.toUint8Array(); const iv = storedScene.iv.toUint8Array();
return restoreElements(await decryptElements(roomKey, iv, ciphertext)); return restoreElements(await decryptElements(roomKey, iv, ciphertext));
} };

View File

@ -82,7 +82,7 @@ export const newElement = (
_newElementBase<ExcalidrawGenericElement>(opts.type, opts); _newElementBase<ExcalidrawGenericElement>(opts.type, opts);
/** computes element x/y offset based on textAlign/verticalAlign */ /** computes element x/y offset based on textAlign/verticalAlign */
function getTextElementPositionOffsets( const getTextElementPositionOffsets = (
opts: { opts: {
textAlign: ExcalidrawTextElement["textAlign"]; textAlign: ExcalidrawTextElement["textAlign"];
verticalAlign: ExcalidrawTextElement["verticalAlign"]; verticalAlign: ExcalidrawTextElement["verticalAlign"];
@ -91,7 +91,7 @@ function getTextElementPositionOffsets(
width: number; width: number;
height: number; height: number;
}, },
) { ) => {
return { return {
x: x:
opts.textAlign === "center" opts.textAlign === "center"
@ -101,7 +101,7 @@ function getTextElementPositionOffsets(
: 0, : 0,
y: opts.verticalAlign === "middle" ? metrics.height / 2 : 0, y: opts.verticalAlign === "middle" ? metrics.height / 2 : 0,
}; };
} };
export const newTextElement = ( export const newTextElement = (
opts: { opts: {

View File

@ -46,7 +46,7 @@ export const textWysiwyg = ({
getViewportCoords: (x: number, y: number) => [number, number]; getViewportCoords: (x: number, y: number) => [number, number];
element: ExcalidrawElement; element: ExcalidrawElement;
}) => { }) => {
function updateWysiwygStyle() { const updateWysiwygStyle = () => {
const updatedElement = Scene.getScene(element)?.getElement(id); const updatedElement = Scene.getScene(element)?.getElement(id);
if (updatedElement && isTextElement(updatedElement)) { if (updatedElement && isTextElement(updatedElement)) {
const [viewportX, viewportY] = getViewportCoords( const [viewportX, viewportY] = getViewportCoords(
@ -80,7 +80,7 @@ export const textWysiwyg = ({
filter: "var(--appearance-filter)", filter: "var(--appearance-filter)",
}); });
} }
} };
const editable = document.createElement("textarea"); const editable = document.createElement("textarea");

277
src/ga.ts
View File

@ -22,22 +22,25 @@ export type Direction = NVector;
export type Line = NVector; export type Line = NVector;
export type Transform = NVector; export type Transform = NVector;
export function point(x: number, y: number): Point { export const point = (x: number, y: number): Point => [0, 0, 0, 0, y, x, 1, 0];
return [0, 0, 0, 0, y, x, 1, 0];
}
export function origin(): Point { export const origin = (): Point => [0, 0, 0, 0, 0, 0, 1, 0];
return [0, 0, 0, 0, 0, 0, 1, 0];
}
export function direction(x: number, y: number): Direction { export const direction = (x: number, y: number): Direction => {
const norm = Math.hypot(x, y); // same as `inorm(direction(x, y))` const norm = Math.hypot(x, y); // same as `inorm(direction(x, y))`
return [0, 0, 0, 0, y / norm, x / norm, 0, 0]; return [0, 0, 0, 0, y / norm, x / norm, 0, 0];
} };
export function offset(x: number, y: number): Direction { export const offset = (x: number, y: number): Direction => [
return [0, 0, 0, 0, y, x, 0, 0]; 0,
} 0,
0,
0,
y,
x,
0,
0,
];
/// This is the "implementation" part of the library /// This is the "implementation" part of the library
@ -56,7 +59,7 @@ type NVector = readonly [
const NVECTOR_BASE = ["1", "e0", "e1", "e2", "e01", "e20", "e12", "e012"]; const NVECTOR_BASE = ["1", "e0", "e1", "e2", "e01", "e20", "e12", "e012"];
// Used to represent points, lines and transformations // Used to represent points, lines and transformations
export function nvector(value: number = 0, index: number = 0): NVector { export const nvector = (value: number = 0, index: number = 0): NVector => {
const result = [0, 0, 0, 0, 0, 0, 0, 0]; const result = [0, 0, 0, 0, 0, 0, 0, 0];
if (index < 0 || index > 7) { if (index < 0 || index > 7) {
throw new Error(`Expected \`index\` betwen 0 and 7, got \`${index}\``); throw new Error(`Expected \`index\` betwen 0 and 7, got \`${index}\``);
@ -65,10 +68,10 @@ export function nvector(value: number = 0, index: number = 0): NVector {
result[index] = value; result[index] = value;
} }
return (result as unknown) as NVector; return (result as unknown) as NVector;
} };
const STRING_EPSILON = 0.000001; const STRING_EPSILON = 0.000001;
export function toString(nvector: NVector): string { export const toString = (nvector: NVector): string => {
const result = nvector const result = nvector
.map((value, index) => .map((value, index) =>
Math.abs(value) > STRING_EPSILON Math.abs(value) > STRING_EPSILON
@ -79,66 +82,58 @@ export function toString(nvector: NVector): string {
.filter((representation) => representation != null) .filter((representation) => representation != null)
.join(" + "); .join(" + ");
return result === "" ? "0" : result; return result === "" ? "0" : result;
} };
// Reverse the order of the basis blades. // Reverse the order of the basis blades.
export function reverse(nvector: NVector): NVector { export const reverse = (nvector: NVector): NVector => [
return [ nvector[0],
nvector[0], nvector[1],
nvector[1], nvector[2],
nvector[2], nvector[3],
nvector[3], -nvector[4],
-nvector[4], -nvector[5],
-nvector[5], -nvector[6],
-nvector[6], -nvector[7],
-nvector[7], ];
];
}
// Poincare duality operator. // Poincare duality operator.
export function dual(nvector: NVector): NVector { export const dual = (nvector: NVector): NVector => [
return [ nvector[7],
nvector[7], nvector[6],
nvector[6], nvector[5],
nvector[5], nvector[4],
nvector[4], nvector[3],
nvector[3], nvector[2],
nvector[2], nvector[1],
nvector[1], nvector[0],
nvector[0], ];
];
}
// Clifford Conjugation // Clifford Conjugation
export function conjugate(nvector: NVector): NVector { export const conjugate = (nvector: NVector): NVector => [
return [ nvector[0],
nvector[0], -nvector[1],
-nvector[1], -nvector[2],
-nvector[2], -nvector[3],
-nvector[3], -nvector[4],
-nvector[4], -nvector[5],
-nvector[5], -nvector[6],
-nvector[6], nvector[7],
nvector[7], ];
];
}
// Main involution // Main involution
export function involute(nvector: NVector): NVector { export const involute = (nvector: NVector): NVector => [
return [ nvector[0],
nvector[0], -nvector[1],
-nvector[1], -nvector[2],
-nvector[2], -nvector[3],
-nvector[3], nvector[4],
nvector[4], nvector[5],
nvector[5], nvector[6],
nvector[6], -nvector[7],
-nvector[7], ];
];
}
// Multivector addition // Multivector addition
export function add(a: NVector, b: NVector | number): NVector { export const add = (a: NVector, b: NVector | number): NVector => {
if (isNumber(b)) { if (isNumber(b)) {
return [a[0] + b, a[1], a[2], a[3], a[4], a[5], a[6], a[7]]; return [a[0] + b, a[1], a[2], a[3], a[4], a[5], a[6], a[7]];
} }
@ -152,10 +147,10 @@ export function add(a: NVector, b: NVector | number): NVector {
a[6] + b[6], a[6] + b[6],
a[7] + b[7], a[7] + b[7],
]; ];
} };
// Multivector subtraction // Multivector subtraction
export function sub(a: NVector, b: NVector | number): NVector { export const sub = (a: NVector, b: NVector | number): NVector => {
if (isNumber(b)) { if (isNumber(b)) {
return [a[0] - b, a[1], a[2], a[3], a[4], a[5], a[6], a[7]]; return [a[0] - b, a[1], a[2], a[3], a[4], a[5], a[6], a[7]];
} }
@ -169,10 +164,10 @@ export function sub(a: NVector, b: NVector | number): NVector {
a[6] - b[6], a[6] - b[6],
a[7] - b[7], a[7] - b[7],
]; ];
} };
// The geometric product. // The geometric product.
export function mul(a: NVector, b: NVector | number): NVector { export const mul = (a: NVector, b: NVector | number): NVector => {
if (isNumber(b)) { if (isNumber(b)) {
return [ return [
a[0] * b, a[0] * b,
@ -223,112 +218,94 @@ export function mul(a: NVector, b: NVector | number): NVector {
b[1] * a[6] + b[1] * a[6] +
b[0] * a[7], b[0] * a[7],
]; ];
} };
export function mulScalar(a: NVector, b: NVector): number { export const mulScalar = (a: NVector, b: NVector): number =>
return b[0] * a[0] + b[2] * a[2] + b[3] * a[3] - b[6] * a[6]; b[0] * a[0] + b[2] * a[2] + b[3] * a[3] - b[6] * a[6];
}
// The outer/exterior/wedge product. // The outer/exterior/wedge product.
export function meet(a: NVector, b: NVector): NVector { export const meet = (a: NVector, b: NVector): NVector => [
return [ b[0] * a[0],
b[0] * a[0], b[1] * a[0] + b[0] * a[1],
b[1] * a[0] + b[0] * a[1], b[2] * a[0] + b[0] * a[2],
b[2] * a[0] + b[0] * a[2], b[3] * a[0] + b[0] * a[3],
b[3] * a[0] + b[0] * a[3], b[4] * a[0] + b[2] * a[1] - b[1] * a[2] + b[0] * a[4],
b[4] * a[0] + b[2] * a[1] - b[1] * a[2] + b[0] * a[4], b[5] * a[0] - b[3] * a[1] + b[1] * a[3] + b[0] * a[5],
b[5] * a[0] - b[3] * a[1] + b[1] * a[3] + b[0] * a[5], b[6] * a[0] + b[3] * a[2] - b[2] * a[3] + b[0] * a[6],
b[6] * a[0] + b[3] * a[2] - b[2] * a[3] + b[0] * a[6], b[7] * a[0] +
b[7] * a[0] + b[6] * a[1] +
b[6] * a[1] + b[5] * a[2] +
b[5] * a[2] + b[4] * a[3] +
b[4] * a[3] + b[3] * a[4] +
b[3] * a[4] + b[2] * a[5] +
b[2] * a[5] + b[1] * a[6],
b[1] * a[6], ];
];
}
// The regressive product. // The regressive product.
export function join(a: NVector, b: NVector): NVector { export const join = (a: NVector, b: NVector): NVector => [
return [ joinScalar(a, b),
joinScalar(a, b), a[1] * b[7] + a[4] * b[5] - a[5] * b[4] + a[7] * b[1],
a[1] * b[7] + a[4] * b[5] - a[5] * b[4] + a[7] * b[1], a[2] * b[7] - a[4] * b[6] + a[6] * b[4] + a[7] * b[2],
a[2] * b[7] - a[4] * b[6] + a[6] * b[4] + a[7] * b[2], a[3] * b[7] + a[5] * b[6] - a[6] * b[5] + a[7] * b[3],
a[3] * b[7] + a[5] * b[6] - a[6] * b[5] + a[7] * b[3], a[4] * b[7] + a[7] * b[4],
a[4] * b[7] + a[7] * b[4], a[5] * b[7] + a[7] * b[5],
a[5] * b[7] + a[7] * b[5], a[6] * b[7] + a[7] * b[6],
a[6] * b[7] + a[7] * b[6], a[7] * b[7],
a[7] * b[7], ];
];
}
export function joinScalar(a: NVector, b: NVector): number { export const joinScalar = (a: NVector, b: NVector): number =>
return ( a[0] * b[7] +
a[0] * b[7] + a[1] * b[6] +
a[1] * b[6] + a[2] * b[5] +
a[2] * b[5] + a[3] * b[4] +
a[3] * b[4] + a[4] * b[3] +
a[4] * b[3] + a[5] * b[2] +
a[5] * b[2] + a[6] * b[1] +
a[6] * b[1] + a[7] * b[0];
a[7] * b[0]
);
}
// The inner product. // The inner product.
export function dot(a: NVector, b: NVector): NVector { export const dot = (a: NVector, b: NVector): NVector => [
return [ b[0] * a[0] + b[2] * a[2] + b[3] * a[3] - b[6] * a[6],
b[0] * a[0] + b[2] * a[2] + b[3] * a[3] - b[6] * a[6], b[1] * a[0] +
b[1] * a[0] + b[0] * a[1] -
b[0] * a[1] - b[4] * a[2] +
b[4] * a[2] + b[5] * a[3] +
b[5] * a[3] + b[2] * a[4] -
b[2] * a[4] - b[3] * a[5] -
b[3] * a[5] - b[7] * a[6] -
b[7] * a[6] - b[6] * a[7],
b[6] * a[7], b[2] * a[0] + b[0] * a[2] - b[6] * a[3] + b[3] * a[6],
b[2] * a[0] + b[0] * a[2] - b[6] * a[3] + b[3] * a[6], b[3] * a[0] + b[6] * a[2] + b[0] * a[3] - b[2] * a[6],
b[3] * a[0] + b[6] * a[2] + b[0] * a[3] - b[2] * a[6], b[4] * a[0] + b[7] * a[3] + b[0] * a[4] + b[3] * a[7],
b[4] * a[0] + b[7] * a[3] + b[0] * a[4] + b[3] * a[7], b[5] * a[0] + b[7] * a[2] + b[0] * a[5] + b[2] * a[7],
b[5] * a[0] + b[7] * a[2] + b[0] * a[5] + b[2] * a[7], b[6] * a[0] + b[0] * a[6],
b[6] * a[0] + b[0] * a[6], b[7] * a[0] + b[0] * a[7],
b[7] * a[0] + b[0] * a[7], ];
];
}
export function norm(a: NVector): number { export const norm = (a: NVector): number =>
return Math.sqrt( Math.sqrt(Math.abs(a[0] * a[0] - a[2] * a[2] - a[3] * a[3] + a[6] * a[6]));
Math.abs(a[0] * a[0] - a[2] * a[2] - a[3] * a[3] + a[6] * a[6]),
);
}
export function inorm(a: NVector): number { export const inorm = (a: NVector): number =>
return Math.sqrt( Math.sqrt(Math.abs(a[7] * a[7] - a[5] * a[5] - a[4] * a[4] + a[1] * a[1]));
Math.abs(a[7] * a[7] - a[5] * a[5] - a[4] * a[4] + a[1] * a[1]),
);
}
export function normalized(a: NVector): NVector { export const normalized = (a: NVector): NVector => {
const n = norm(a); const n = norm(a);
if (n === 0 || n === 1) { if (n === 0 || n === 1) {
return a; return a;
} }
const sign = a[6] < 0 ? -1 : 1; const sign = a[6] < 0 ? -1 : 1;
return mul(a, sign / n); return mul(a, sign / n);
} };
export function inormalized(a: NVector): NVector { export const inormalized = (a: NVector): NVector => {
const n = inorm(a); const n = inorm(a);
if (n === 0 || n === 1) { if (n === 0 || n === 1) {
return a; return a;
} }
return mul(a, 1 / n); return mul(a, 1 / n);
} };
function isNumber(a: any): a is number { const isNumber = (a: any): a is number => typeof a === "number";
return typeof a === "number";
}
export const E0: NVector = nvector(1, 1); export const E0: NVector = nvector(1, 1);
export const E1: NVector = nvector(1, 2); export const E1: NVector = nvector(1, 2);

View File

@ -6,18 +6,21 @@ import { Line, Direction, Point } from "./ga";
* vector `(x, y)`. * vector `(x, y)`.
*/ */
export function from(point: Point): Point { export const from = (point: Point): Point => [
return [0, 0, 0, 0, point[4], point[5], 0, 0]; 0,
} 0,
0,
0,
point[4],
point[5],
0,
0,
];
export function fromTo(from: Point, to: Point): Direction { export const fromTo = (from: Point, to: Point): Direction =>
return GA.inormalized([0, 0, 0, 0, to[4] - from[4], to[5] - from[5], 0, 0]); GA.inormalized([0, 0, 0, 0, to[4] - from[4], to[5] - from[5], 0, 0]);
}
export function orthogonal(direction: Direction): Direction { export const orthogonal = (direction: Direction): Direction =>
return GA.inormalized([0, 0, 0, 0, -direction[5], direction[4], 0, 0]); GA.inormalized([0, 0, 0, 0, -direction[5], direction[4], 0, 0]);
}
export function orthogonalToLine(line: Line): Direction { export const orthogonalToLine = (line: Line): Direction => GA.mul(line, GA.I);
return GA.mul(line, GA.I);
}

View File

@ -15,48 +15,38 @@ import { Line, Point } from "./ga";
*/ */
// Returns line with direction (x, y) through origin // Returns line with direction (x, y) through origin
export function vector(x: number, y: number): Line { export const vector = (x: number, y: number): Line =>
return GA.normalized([0, 0, -y, x, 0, 0, 0, 0]); GA.normalized([0, 0, -y, x, 0, 0, 0, 0]);
}
// For equation ax + by + c = 0. // For equation ax + by + c = 0.
export function equation(a: number, b: number, c: number): Line { export const equation = (a: number, b: number, c: number): Line =>
return GA.normalized([0, c, a, b, 0, 0, 0, 0]); GA.normalized([0, c, a, b, 0, 0, 0, 0]);
}
export function through(from: Point, to: Point): Line { export const through = (from: Point, to: Point): Line =>
return GA.normalized(GA.join(to, from)); GA.normalized(GA.join(to, from));
}
export function orthogonal(line: Line, point: Point): Line { export const orthogonal = (line: Line, point: Point): Line =>
return GA.dot(line, point); GA.dot(line, point);
}
// Returns a line perpendicular to the line through `against` and `intersection` // Returns a line perpendicular to the line through `against` and `intersection`
// going through `intersection`. // going through `intersection`.
export function orthogonalThrough(against: Point, intersection: Point): Line { export const orthogonalThrough = (against: Point, intersection: Point): Line =>
return orthogonal(through(against, intersection), intersection); orthogonal(through(against, intersection), intersection);
}
export function parallel(line: Line, distance: number): Line { export const parallel = (line: Line, distance: number): Line => {
const result = line.slice(); const result = line.slice();
result[1] -= distance; result[1] -= distance;
return (result as unknown) as Line; return (result as unknown) as Line;
} };
export function parallelThrough(line: Line, point: Point): Line { export const parallelThrough = (line: Line, point: Point): Line =>
return orthogonal(orthogonal(point, line), point); orthogonal(orthogonal(point, line), point);
}
export function distance(line1: Line, line2: Line): number { export const distance = (line1: Line, line2: Line): number =>
return GA.inorm(GA.meet(line1, line2)); GA.inorm(GA.meet(line1, line2));
}
export function angle(line1: Line, line2: Line): number { export const angle = (line1: Line, line2: Line): number =>
return Math.acos(GA.dot(line1, line2)[0]); Math.acos(GA.dot(line1, line2)[0]);
}
// The orientation of the line // The orientation of the line
export function sign(line: Line): number { export const sign = (line: Line): number => Math.sign(line[1]);
return Math.sign(line[1]);
}

View File

@ -2,36 +2,40 @@ import * as GA from "./ga";
import * as GALine from "./galines"; import * as GALine from "./galines";
import { Point, Line, join } from "./ga"; import { Point, Line, join } from "./ga";
/** export const from = ([x, y]: readonly [number, number]): Point => [
* TODO: docs 0,
*/ 0,
0,
0,
y,
x,
1,
0,
];
export function from([x, y]: readonly [number, number]): Point { export const toTuple = (point: Point): [number, number] => [point[5], point[4]];
return [0, 0, 0, 0, y, x, 1, 0];
}
export function toTuple(point: Point): [number, number] { export const abs = (point: Point): Point => [
return [point[5], point[4]]; 0,
} 0,
0,
0,
Math.abs(point[4]),
Math.abs(point[5]),
1,
0,
];
export function abs(point: Point): Point { export const intersect = (line1: Line, line2: Line): Point =>
return [0, 0, 0, 0, Math.abs(point[4]), Math.abs(point[5]), 1, 0]; GA.normalized(GA.meet(line1, line2));
}
export function intersect(line1: Line, line2: Line): Point {
return GA.normalized(GA.meet(line1, line2));
}
// Projects `point` onto the `line`. // Projects `point` onto the `line`.
// The returned point is the closest point on the `line` to the `point`. // The returned point is the closest point on the `line` to the `point`.
export function project(point: Point, line: Line): Point { export const project = (point: Point, line: Line): Point =>
return intersect(GALine.orthogonal(line, point), line); intersect(GALine.orthogonal(line, point), line);
}
export function distance(point1: Point, point2: Point): number { export const distance = (point1: Point, point2: Point): number =>
return GA.norm(join(point1, point2)); GA.norm(join(point1, point2));
}
export function distanceToLine(point: Point, line: Line): number { export const distanceToLine = (point: Point, line: Line): number =>
return GA.joinScalar(point, line); GA.joinScalar(point, line);
}

View File

@ -6,33 +6,36 @@ import * as GADirection from "./gadirections";
* TODO: docs * TODO: docs
*/ */
export function rotation(pivot: Point, angle: number): Transform { export const rotation = (pivot: Point, angle: number): Transform =>
return GA.add(GA.mul(pivot, Math.sin(angle / 2)), Math.cos(angle / 2)); GA.add(GA.mul(pivot, Math.sin(angle / 2)), Math.cos(angle / 2));
}
export function translation(direction: Direction): Transform { export const translation = (direction: Direction): Transform => [
return [1, 0, 0, 0, -(0.5 * direction[5]), 0.5 * direction[4], 0, 0]; 1,
} 0,
0,
0,
-(0.5 * direction[5]),
0.5 * direction[4],
0,
0,
];
export function translationOrthogonal( export const translationOrthogonal = (
direction: Direction, direction: Direction,
distance: number, distance: number,
): Transform { ): Transform => {
const scale = 0.5 * distance; const scale = 0.5 * distance;
return [1, 0, 0, 0, scale * direction[4], scale * direction[5], 0, 0]; return [1, 0, 0, 0, scale * direction[4], scale * direction[5], 0, 0];
} };
export function translationAlong(line: Line, distance: number): Transform { export const translationAlong = (line: Line, distance: number): Transform =>
return GA.add(GA.mul(GADirection.orthogonalToLine(line), 0.5 * distance), 1); GA.add(GA.mul(GADirection.orthogonalToLine(line), 0.5 * distance), 1);
}
export function compose(motor1: Transform, motor2: Transform): Transform { export const compose = (motor1: Transform, motor2: Transform): Transform =>
return GA.mul(motor2, motor1); GA.mul(motor2, motor1);
}
export function apply( export const apply = (
motor: Transform, motor: Transform,
nvector: Point | Direction | Line, nvector: Point | Direction | Line,
): Point | Direction | Line { ): Point | Direction | Line =>
return GA.normalized(GA.mul(GA.mul(motor, nvector), GA.reverse(motor))); GA.normalized(GA.mul(GA.mul(motor, nvector), GA.reverse(motor)));
}

View File

@ -2,11 +2,11 @@ import { GroupId, ExcalidrawElement, NonDeleted } from "./element/types";
import { AppState } from "./types"; import { AppState } from "./types";
import { getSelectedElements } from "./scene"; import { getSelectedElements } from "./scene";
export function selectGroup( export const selectGroup = (
groupId: GroupId, groupId: GroupId,
appState: AppState, appState: AppState,
elements: readonly NonDeleted<ExcalidrawElement>[], elements: readonly NonDeleted<ExcalidrawElement>[],
): AppState { ): AppState => {
const elementsInGroup = elements.filter((element) => const elementsInGroup = elements.filter((element) =>
element.groupIds.includes(groupId), element.groupIds.includes(groupId),
); );
@ -35,42 +35,38 @@ export function selectGroup(
), ),
}, },
}; };
} };
/** /**
* If the element's group is selected, don't render an individual * If the element's group is selected, don't render an individual
* selection border around it. * selection border around it.
*/ */
export function isSelectedViaGroup( export const isSelectedViaGroup = (
appState: AppState, appState: AppState,
element: ExcalidrawElement, element: ExcalidrawElement,
) { ) => getSelectedGroupForElement(appState, element) != null;
return getSelectedGroupForElement(appState, element) != null;
}
export const getSelectedGroupForElement = ( export const getSelectedGroupForElement = (
appState: AppState, appState: AppState,
element: ExcalidrawElement, element: ExcalidrawElement,
) => { ) =>
return element.groupIds element.groupIds
.filter((groupId) => groupId !== appState.editingGroupId) .filter((groupId) => groupId !== appState.editingGroupId)
.find((groupId) => appState.selectedGroupIds[groupId]); .find((groupId) => appState.selectedGroupIds[groupId]);
};
export function getSelectedGroupIds(appState: AppState): GroupId[] { export const getSelectedGroupIds = (appState: AppState): GroupId[] =>
return Object.entries(appState.selectedGroupIds) Object.entries(appState.selectedGroupIds)
.filter(([groupId, isSelected]) => isSelected) .filter(([groupId, isSelected]) => isSelected)
.map(([groupId, isSelected]) => groupId); .map(([groupId, isSelected]) => groupId);
}
/** /**
* When you select an element, you often want to actually select the whole group it's in, unless * When you select an element, you often want to actually select the whole group it's in, unless
* you're currently editing that group. * you're currently editing that group.
*/ */
export function selectGroupsForSelectedElements( export const selectGroupsForSelectedElements = (
appState: AppState, appState: AppState,
elements: readonly NonDeleted<ExcalidrawElement>[], elements: readonly NonDeleted<ExcalidrawElement>[],
): AppState { ): AppState => {
let nextAppState = { ...appState }; let nextAppState = { ...appState };
const selectedElements = getSelectedElements(elements, appState); const selectedElements = getSelectedElements(elements, appState);
@ -91,7 +87,7 @@ export function selectGroupsForSelectedElements(
} }
return nextAppState; return nextAppState;
} };
export const editGroupForSelectedElement = ( export const editGroupForSelectedElement = (
appState: AppState, appState: AppState,
@ -107,29 +103,24 @@ export const editGroupForSelectedElement = (
}; };
}; };
export function isElementInGroup(element: ExcalidrawElement, groupId: string) { export const isElementInGroup = (element: ExcalidrawElement, groupId: string) =>
return element.groupIds.includes(groupId); element.groupIds.includes(groupId);
}
export function getElementsInGroup( export const getElementsInGroup = (
elements: readonly ExcalidrawElement[], elements: readonly ExcalidrawElement[],
groupId: string, groupId: string,
) { ) => elements.filter((element) => isElementInGroup(element, groupId));
return elements.filter((element) => isElementInGroup(element, groupId));
}
export function getSelectedGroupIdForElement( export const getSelectedGroupIdForElement = (
element: ExcalidrawElement, element: ExcalidrawElement,
selectedGroupIds: { [groupId: string]: boolean }, selectedGroupIds: { [groupId: string]: boolean },
) { ) => element.groupIds.find((groupId) => selectedGroupIds[groupId]);
return element.groupIds.find((groupId) => selectedGroupIds[groupId]);
}
export function getNewGroupIdsForDuplication( export const getNewGroupIdsForDuplication = (
groupIds: ExcalidrawElement["groupIds"], groupIds: ExcalidrawElement["groupIds"],
editingGroupId: AppState["editingGroupId"], editingGroupId: AppState["editingGroupId"],
mapper: (groupId: GroupId) => GroupId, mapper: (groupId: GroupId) => GroupId,
) { ) => {
const copy = [...groupIds]; const copy = [...groupIds];
const positionOfEditingGroupId = editingGroupId const positionOfEditingGroupId = editingGroupId
? groupIds.indexOf(editingGroupId) ? groupIds.indexOf(editingGroupId)
@ -141,13 +132,13 @@ export function getNewGroupIdsForDuplication(
} }
return copy; return copy;
} };
export function addToGroup( export const addToGroup = (
prevGroupIds: ExcalidrawElement["groupIds"], prevGroupIds: ExcalidrawElement["groupIds"],
newGroupId: GroupId, newGroupId: GroupId,
editingGroupId: AppState["editingGroupId"], editingGroupId: AppState["editingGroupId"],
) { ) => {
// insert before the editingGroupId, or push to the end. // insert before the editingGroupId, or push to the end.
const groupIds = [...prevGroupIds]; const groupIds = [...prevGroupIds];
const positionOfEditingGroupId = editingGroupId const positionOfEditingGroupId = editingGroupId
@ -157,11 +148,9 @@ export function addToGroup(
positionOfEditingGroupId > -1 ? positionOfEditingGroupId : groupIds.length; positionOfEditingGroupId > -1 ? positionOfEditingGroupId : groupIds.length;
groupIds.splice(positionToInsert, 0, newGroupId); groupIds.splice(positionToInsert, 0, newGroupId);
return groupIds; return groupIds;
} };
export function removeFromSelectedGroups( export const removeFromSelectedGroups = (
groupIds: ExcalidrawElement["groupIds"], groupIds: ExcalidrawElement["groupIds"],
selectedGroupIds: { [groupId: string]: boolean }, selectedGroupIds: { [groupId: string]: boolean },
) { ) => groupIds.filter((groupId) => !selectedGroupIds[groupId]);
return groupIds.filter((groupId) => !selectedGroupIds[groupId]);
}

View File

@ -334,7 +334,7 @@ export const renderScene = (
return acc; return acc;
}, [] as { angle: number; elementX1: number; elementY1: number; elementX2: number; elementY2: number; selectionColors: string[] }[]); }, [] as { angle: number; elementX1: number; elementY1: number; elementX2: number; elementY2: number; selectionColors: string[] }[]);
function addSelectionForGroupId(groupId: GroupId) { const addSelectionForGroupId = (groupId: GroupId) => {
const groupElements = getElementsInGroup(elements, groupId); const groupElements = getElementsInGroup(elements, groupId);
const [elementX1, elementY1, elementX2, elementY2] = getCommonBounds( const [elementX1, elementY1, elementX2, elementY2] = getCommonBounds(
groupElements, groupElements,
@ -347,7 +347,7 @@ export const renderScene = (
elementY2, elementY2,
selectionColors: [oc.black], selectionColors: [oc.black],
}); });
} };
for (const groupId of getSelectedGroupIds(appState)) { for (const groupId of getSelectedGroupIds(appState)) {
// TODO: support multiplayer selected group IDs // TODO: support multiplayer selected group IDs

View File

@ -10,11 +10,11 @@ import {
export const normalizeScroll = (pos: number) => export const normalizeScroll = (pos: number) =>
Math.floor(pos) as FlooredNumber; Math.floor(pos) as FlooredNumber;
function isOutsideViewPort( const isOutsideViewPort = (
appState: AppState, appState: AppState,
canvas: HTMLCanvasElement | null, canvas: HTMLCanvasElement | null,
cords: Array<number>, cords: Array<number>,
) { ) => {
const [x1, y1, x2, y2] = cords; const [x1, y1, x2, y2] = cords;
const { x: viewportX1, y: viewportY1 } = sceneCoordsToViewportCoords( const { x: viewportX1, y: viewportY1 } = sceneCoordsToViewportCoords(
{ sceneX: x1, sceneY: y1 }, { sceneX: x1, sceneY: y1 },
@ -28,7 +28,7 @@ function isOutsideViewPort(
viewportX2 - viewportX1 > appState.width || viewportX2 - viewportX1 > appState.width ||
viewportY2 - viewportY1 > appState.height viewportY2 - viewportY1 > appState.height
); );
} };
export const centerScrollOn = ({ export const centerScrollOn = ({
scenePoint, scenePoint,

View File

@ -29,7 +29,7 @@ beforeEach(async () => {
render(<App />); render(<App />);
}); });
function createAndSelectTwoRectangles() { const createAndSelectTwoRectangles = () => {
UI.clickTool("rectangle"); UI.clickTool("rectangle");
mouse.down(); mouse.down();
mouse.up(100, 100); mouse.up(100, 100);
@ -44,9 +44,9 @@ function createAndSelectTwoRectangles() {
Keyboard.withModifierKeys({ shift: true }, () => { Keyboard.withModifierKeys({ shift: true }, () => {
mouse.click(); mouse.click();
}); });
} };
function createAndSelectTwoRectanglesWithDifferentSizes() { const createAndSelectTwoRectanglesWithDifferentSizes = () => {
UI.clickTool("rectangle"); UI.clickTool("rectangle");
mouse.down(); mouse.down();
mouse.up(100, 100); mouse.up(100, 100);
@ -61,7 +61,7 @@ function createAndSelectTwoRectanglesWithDifferentSizes() {
Keyboard.withModifierKeys({ shift: true }, () => { Keyboard.withModifierKeys({ shift: true }, () => {
mouse.click(); mouse.click();
}); });
} };
it("aligns two objects correctly to the top", () => { it("aligns two objects correctly to the top", () => {
createAndSelectTwoRectangles(); createAndSelectTwoRectangles();
@ -185,7 +185,7 @@ it("centers two objects with different sizes correctly horizontally", () => {
expect(API.getSelectedElements()[1].y).toEqual(110); expect(API.getSelectedElements()[1].y).toEqual(110);
}); });
function createAndSelectGroupAndRectangle() { const createAndSelectGroupAndRectangle = () => {
UI.clickTool("rectangle"); UI.clickTool("rectangle");
mouse.down(); mouse.down();
mouse.up(100, 100); mouse.up(100, 100);
@ -213,7 +213,7 @@ function createAndSelectGroupAndRectangle() {
Keyboard.withModifierKeys({ shift: true }, () => { Keyboard.withModifierKeys({ shift: true }, () => {
mouse.click(); mouse.click();
}); });
} };
it("aligns a group with another element correctly to the top", () => { it("aligns a group with another element correctly to the top", () => {
createAndSelectGroupAndRectangle(); createAndSelectGroupAndRectangle();
@ -299,7 +299,7 @@ it("centers a group with another element correctly horizontally", () => {
expect(API.getSelectedElements()[2].x).toEqual(100); expect(API.getSelectedElements()[2].x).toEqual(100);
}); });
function createAndSelectTwoGroups() { const createAndSelectTwoGroups = () => {
UI.clickTool("rectangle"); UI.clickTool("rectangle");
mouse.down(); mouse.down();
mouse.up(100, 100); mouse.up(100, 100);
@ -339,7 +339,7 @@ function createAndSelectTwoGroups() {
Keyboard.withModifierKeys({ shift: true }, () => { Keyboard.withModifierKeys({ shift: true }, () => {
mouse.click(); mouse.click();
}); });
} };
it("aligns two groups correctly to the top", () => { it("aligns two groups correctly to the top", () => {
createAndSelectTwoGroups(); createAndSelectTwoGroups();
@ -437,7 +437,7 @@ it("centers two groups correctly horizontally", () => {
expect(API.getSelectedElements()[3].x).toEqual(200); expect(API.getSelectedElements()[3].x).toEqual(200);
}); });
function createAndSelectNestedGroupAndRectangle() { const createAndSelectNestedGroupAndRectangle = () => {
UI.clickTool("rectangle"); UI.clickTool("rectangle");
mouse.down(); mouse.down();
mouse.up(100, 100); mouse.up(100, 100);
@ -480,7 +480,7 @@ function createAndSelectNestedGroupAndRectangle() {
Keyboard.withModifierKeys({ shift: true }, () => { Keyboard.withModifierKeys({ shift: true }, () => {
mouse.click(); mouse.click();
}); });
} };
it("aligns nested group and other element correctly to the top", () => { it("aligns nested group and other element correctly to the top", () => {
createAndSelectNestedGroupAndRectangle(); createAndSelectNestedGroupAndRectangle();

View File

@ -242,12 +242,12 @@ const RE_RTL_CHECK = new RegExp(`^[^${RS_LTR_CHARS}]*[${RS_RTL_CHARS}]`);
*/ */
export const isRTL = (text: string) => RE_RTL_CHECK.test(text); export const isRTL = (text: string) => RE_RTL_CHECK.test(text);
export function tupleToCoors( export const tupleToCoors = (
xyTuple: readonly [number, number], xyTuple: readonly [number, number],
): { x: number; y: number } { ): { x: number; y: number } => {
const [x, y] = xyTuple; const [x, y] = xyTuple;
return { x, y }; return { x, y };
} };
/** use as a rejectionHandler to mute filesystem Abort errors */ /** use as a rejectionHandler to mute filesystem Abort errors */
export const muteFSAbortError = (error?: Error) => { export const muteFSAbortError = (error?: Error) => {