Add more ESLint rules and change the formatting scripts (#626)

* Add curly rule in ESLint for consistency

* Fix rules

* More rules

* REturn

* Push

* no else return

* prefer const

* destructing
This commit is contained in:
Lipis 2020-02-02 20:04:35 +02:00 committed by GitHub
parent 814299321e
commit 53994e71e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 201 additions and 77 deletions

View File

@ -17,6 +17,7 @@ jobs:
- name: Install and lint - name: Install and lint
run: | run: |
npm ci npm ci
npm run test:other
npm run test:code npm run test:code
env: env:
CI: true CI: true

View File

@ -7,9 +7,8 @@ const cli = new CLIEngine({});
module.exports = { module.exports = {
"*.{js,ts,tsx}": files => { "*.{js,ts,tsx}": files => {
return ( return (
"eslint --max-warnings=0 " + "eslint --fix" + files.filter(file => !cli.isPathIgnored(file)).join(" ")
files.filter(file => !cli.isPathIgnored(file)).join(" ")
); );
}, },
"*.{js,css,scss,json,md,ts,tsx,html,yml}": ["prettier --write"], "*.{css,scss,json,md,html,yml}": ["prettier --write"],
}; };

41
package-lock.json generated
View File

@ -5299,6 +5299,23 @@
} }
} }
}, },
"eslint-config-prettier": {
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.10.0.tgz",
"integrity": "sha512-AtndijGte1rPILInUdHjvKEGbIV06NuvPrqlIEaEaWtbtvJh464mDeyGMdZEQMsGvC0ZVkiex1fSNcC4HAbRGg==",
"dev": true,
"requires": {
"get-stdin": "^6.0.0"
},
"dependencies": {
"get-stdin": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz",
"integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==",
"dev": true
}
}
},
"eslint-config-react-app": { "eslint-config-react-app": {
"version": "5.1.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-5.1.0.tgz", "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-5.1.0.tgz",
@ -5583,6 +5600,15 @@
} }
} }
}, },
"eslint-plugin-prettier": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.2.tgz",
"integrity": "sha512-GlolCC9y3XZfv3RQfwGew7NnuFDKsfI4lbvRK+PIIo23SFH+LemGs4cKwzAaRa+Mdb+lQO/STaIayno8T5sJJA==",
"dev": true,
"requires": {
"prettier-linter-helpers": "^1.0.0"
}
},
"eslint-plugin-react": { "eslint-plugin-react": {
"version": "7.16.0", "version": "7.16.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.16.0.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.16.0.tgz",
@ -5976,6 +6002,12 @@
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz",
"integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==" "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA=="
}, },
"fast-diff": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz",
"integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==",
"dev": true
},
"fast-glob": { "fast-glob": {
"version": "2.2.7", "version": "2.2.7",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz",
@ -12119,6 +12151,15 @@
"integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==",
"dev": true "dev": true
}, },
"prettier-linter-helpers": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
"integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
"dev": true,
"requires": {
"fast-diff": "^1.1.2"
}
},
"pretty-bytes": { "pretty-bytes": {
"version": "5.3.0", "version": "5.3.0",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.3.0.tgz", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.3.0.tgz",

View File

@ -20,6 +20,9 @@
"@types/nanoid": "2.1.0", "@types/nanoid": "2.1.0",
"@types/react": "16.9.19", "@types/react": "16.9.19",
"@types/react-dom": "16.9.5", "@types/react-dom": "16.9.5",
"eslint": "6.8.0",
"eslint-config-prettier": "6.10.0",
"eslint-plugin-prettier": "3.1.2",
"husky": "4.2.1", "husky": "4.2.1",
"lint-staged": "10.0.3", "lint-staged": "10.0.3",
"node-sass": "4.13.1", "node-sass": "4.13.1",
@ -28,10 +31,17 @@
"typescript": "3.7.5" "typescript": "3.7.5"
}, },
"eslintConfig": { "eslintConfig": {
"extends": "react-app", "extends": [
"prettier",
"react-app"
],
"plugins": [
"prettier"
],
"rules": { "rules": {
"curly": "error",
"no-console": [ "no-console": [
"warn", "error",
{ {
"allow": [ "allow": [
"warn", "warn",
@ -39,7 +49,17 @@
"info" "info"
] ]
} }
] ],
"no-else-return": "error",
"no-useless-return": "error",
"prefer-const": [
"error",
{
"destructuring": "all"
}
],
"prefer-template": "error",
"prettier/prettier": "error"
} }
}, },
"homepage": "https://excalidraw.com", "homepage": "https://excalidraw.com",
@ -54,12 +74,15 @@
"build": "react-scripts build", "build": "react-scripts build",
"build-node": "./scripts/build-node.js", "build-node": "./scripts/build-node.js",
"eject": "react-scripts eject", "eject": "react-scripts eject",
"fix": "npm run prettier -- --write", "fix": "npm run fix:other && npm run fix:code",
"prettier": "prettier \"**/*.{js,css,scss,json,md,ts,tsx,html,yml}\" --ignore-path=.eslintignore", "fix:code": "npm run test:code -- --fix",
"fix:other": "npm run prettier -- --write",
"prettier": "prettier \"**/*.{css,scss,json,md,html,yml}\" --ignore-path=.eslintignore",
"start": "react-scripts start", "start": "react-scripts start",
"test": "npm run test:app", "test": "npm run test:app",
"test:app": "react-scripts test --env=jsdom --passWithNoTests", "test:app": "react-scripts test --env=jsdom --passWithNoTests",
"test:code": "npm run prettier -- --list-different" "test:code": "eslint --ignore-path .gitignore --ext .js,.ts,.tsx .",
"test:other": "npm run prettier -- --list-different"
}, },
"version": "1.0.0", "version": "1.0.0",
"license": "MIT", "license": "MIT",

View File

@ -37,7 +37,9 @@ export class ActionManager implements ActionsManagerInterface {
action => action.keyTest && action.keyTest(event, appState, elements), action => action.keyTest && action.keyTest(event, appState, elements),
); );
if (data.length === 0) return null; if (data.length === 0) {
return null;
}
event.preventDefault(); event.preventDefault();
return data[0].perform(elements, appState, null); return data[0].perform(elements, appState, null);

View File

@ -109,7 +109,9 @@ const Picker = function({
<div <div
className="colors-gallery" className="colors-gallery"
ref={el => { ref={el => {
if (el) gallery.current = el; if (el) {
gallery.current = el;
}
}} }}
> >
{colors.map((_color, i) => ( {colors.map((_color, i) => (
@ -124,8 +126,12 @@ const Picker = function({
style={{ backgroundColor: _color }} style={{ backgroundColor: _color }}
key={_color} key={_color}
ref={el => { ref={el => {
if (el && i === 0) firstItem.current = el; if (el && i === 0) {
if (el && _color === color) activeItem.current = el; firstItem.current = el;
}
if (el && _color === color) {
activeItem.current = el;
}
}} }}
onFocus={() => { onFocus={() => {
onChange(_color); onChange(_color);
@ -186,7 +192,7 @@ const ColorInput = React.forwardRef(
onChange={e => { onChange={e => {
const value = e.target.value.toLowerCase(); const value = e.target.value.toLowerCase();
if (value.match(colorRegex)) { if (value.match(colorRegex)) {
onChange(value === "transparent" ? "transparent" : "#" + value); onChange(value === "transparent" ? "transparent" : `#${value}`);
} }
setInnerValue(value); setInnerValue(value);
}} }}

View File

@ -178,7 +178,7 @@ function ExportModal({
key={s} key={s}
size="s" size="s"
type="radio" type="radio"
icon={"x" + s} icon={`x${s}`}
name="export-canvas-scale" name="export-canvas-scale"
aria-label={`Scale ${s} x`} aria-label={`Scale ${s} x`}
id="export-canvas-scale" id="export-canvas-scale"

View File

@ -12,7 +12,7 @@ export function FixedSideContainer({
side, side,
}: FixedSideContainerProps) { }: FixedSideContainerProps) {
return ( return (
<div className={"FixedSideContainer FixedSideContainer_side_" + side}> <div className={`FixedSideContainer FixedSideContainer_side_${side}`}>
{children} {children}
</div> </div>
); );

View File

@ -26,11 +26,11 @@ export function Popover({
const viewportWidth = window.innerWidth; const viewportWidth = window.innerWidth;
if (x + width > viewportWidth) { if (x + width > viewportWidth) {
element.style.left = viewportWidth - width + "px"; element.style.left = `${viewportWidth - width}px`;
} }
const viewportHeight = window.innerHeight; const viewportHeight = window.innerHeight;
if (y + height > viewportHeight) { if (y + height > viewportHeight) {
element.style.top = viewportHeight - height + "px"; element.style.top = `${viewportHeight - height}px`;
} }
} }
}, [fitInViewport]); }, [fitInViewport]);
@ -42,7 +42,9 @@ export function Popover({
onClick={onCloseRequest} onClick={onCloseRequest}
onContextMenu={e => { onContextMenu={e => {
e.preventDefault(); e.preventDefault();
if (onCloseRequest) onCloseRequest(); if (onCloseRequest) {
onCloseRequest();
}
}} }}
/> />
{children} {children}

View File

@ -16,7 +16,9 @@ export class ProjectName extends Component<Props> {
private handleBlur = (e: React.FocusEvent<HTMLElement>) => { private handleBlur = (e: React.FocusEvent<HTMLElement>) => {
const value = e.currentTarget.innerText.trim(); const value = e.currentTarget.innerText.trim();
if (value !== this.props.value) this.props.onChange(value); if (value !== this.props.value) {
this.props.onChange(value);
}
removeSelection(); removeSelection();
}; };

View File

@ -35,7 +35,7 @@ export const ToolButton = React.forwardRef(function(
React.useImperativeHandle(ref, () => innerRef.current); React.useImperativeHandle(ref, () => innerRef.current);
const sizeCn = `ToolIcon_size_${props.size || DEFAULT_SIZE}`; const sizeCn = `ToolIcon_size_${props.size || DEFAULT_SIZE}`;
if (props.type === "button") if (props.type === "button") {
return ( return (
<button <button
className={`ToolIcon_type_button ToolIcon ${sizeCn}`} className={`ToolIcon_type_button ToolIcon ${sizeCn}`}
@ -50,6 +50,7 @@ export const ToolButton = React.forwardRef(function(
</div> </div>
</button> </button>
); );
}
return ( return (
<label className="ToolIcon" title={props.title}> <label className="ToolIcon" title={props.title}>

View File

@ -61,9 +61,8 @@ export function hitTest(
return ( return (
a * tx - (px - lineThreshold) >= 0 && b * ty - (py - lineThreshold) >= 0 a * tx - (px - lineThreshold) >= 0 && b * ty - (py - lineThreshold) >= 0
); );
} else {
return Math.hypot(a * tx - px, b * ty - py) < lineThreshold;
} }
return Math.hypot(a * tx - px, b * ty - py) < lineThreshold;
} else if (element.type === "rectangle") { } else if (element.type === "rectangle") {
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element); const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
@ -88,7 +87,6 @@ export function hitTest(
} else if (element.type === "diamond") { } else if (element.type === "diamond") {
x -= element.x; x -= element.x;
y -= element.y; y -= element.y;
let [ let [
topX, topX,
topY, topY,
@ -102,8 +100,12 @@ export function hitTest(
if (isElementDraggableFromInside(element)) { if (isElementDraggableFromInside(element)) {
// TODO: remove this when we normalize coordinates globally // TODO: remove this when we normalize coordinates globally
if (topY > bottomY) [bottomY, topY] = [topY, bottomY]; if (topY > bottomY) {
if (rightX < leftX) [leftX, rightX] = [rightX, leftX]; [bottomY, topY] = [topY, bottomY];
}
if (rightX < leftX) {
[leftX, rightX] = [rightX, leftX];
}
topY -= lineThreshold; topY -= lineThreshold;
bottomY += lineThreshold; bottomY += lineThreshold;
@ -153,10 +155,14 @@ export function hitTest(
const shape = element.shape as Drawable[]; const shape = element.shape as Drawable[];
// If shape does not consist of curve and two line segments // If shape does not consist of curve and two line segments
// for arrow shape, return false // for arrow shape, return false
if (shape.length < 3) return false; if (shape.length < 3) {
return false;
}
const [x1, y1, x2, y2] = getArrowAbsoluteBounds(element); const [x1, y1, x2, y2] = getArrowAbsoluteBounds(element);
if (x < x1 || y < y1 - 10 || x > x2 || y > y2 + 10) return false; if (x < x1 || y < y1 - 10 || x > x2 || y > y2 + 10) {
return false;
}
const relX = x - element.x; const relX = x - element.x;
const relY = y - element.y; const relY = y - element.y;
@ -181,9 +187,8 @@ export function hitTest(
} else if (element.type === "selection") { } else if (element.type === "selection") {
console.warn("This should not happen, we need to investigate why it does."); console.warn("This should not happen, we need to investigate why it does.");
return false; return false;
} else {
throw new Error("Unimplemented type " + element.type);
} }
throw new Error(`Unimplemented type ${element.type}`);
} }
const pointInBezierEquation = ( const pointInBezierEquation = (
@ -249,7 +254,7 @@ const hitTestRoughShape = (opSet: OpSet[], x: number, y: number) => {
// check if points are on the curve // check if points are on the curve
// cubic bezier curves require four parameters // cubic bezier curves require four parameters
// the first parameter is the last stored position (p0) // the first parameter is the last stored position (p0)
let retVal = pointInBezierEquation(p0, p1, p2, p3, [x, y]); const retVal = pointInBezierEquation(p0, p1, p2, p3, [x, y]);
// set end point of bezier curve as the new starting point for // set end point of bezier curve as the new starting point for
// upcoming operations as each operation is based on the last drawn // upcoming operations as each operation is based on the last drawn

View File

@ -15,7 +15,7 @@ export function handlerRectangles(
let marginX = -8; let marginX = -8;
let marginY = -8; let marginY = -8;
let minimumSize = 40; const minimumSize = 40;
if (element.type === "arrow") { if (element.type === "arrow") {
[elementX1, elementY1, elementX2, elementY2] = getArrowAbsoluteBounds( [elementX1, elementY1, elementX2, elementY2] = getArrowAbsoluteBounds(
element, element,

View File

@ -11,13 +11,17 @@ export function resizeTest(
y: number, y: number,
{ scrollX, scrollY }: SceneScroll, { scrollX, scrollY }: SceneScroll,
): HandlerRectanglesRet | false { ): HandlerRectanglesRet | false {
if (!element.isSelected || element.type === "text") return false; if (!element.isSelected || element.type === "text") {
return false;
}
const handlers = handlerRectangles(element, { scrollX, scrollY }); const handlers = handlerRectangles(element, { scrollX, scrollY });
const filter = Object.keys(handlers).filter(key => { const filter = Object.keys(handlers).filter(key => {
const handler = handlers[key as HandlerRectanglesRet]!; const handler = handlers[key as HandlerRectanglesRet]!;
if (!handler) return false; if (!handler) {
return false;
}
return ( return (
x + scrollX >= handler[0] && x + scrollX >= handler[0] &&

View File

@ -41,8 +41,8 @@ export function textWysiwyg({
color: strokeColor, color: strokeColor,
position: "fixed", position: "fixed",
opacity: opacity / 100, opacity: opacity / 100,
top: y + "px", top: `${y}px`,
left: x + "px", left: `${x}px`,
transform: "translate(-50%, -50%)", transform: "translate(-50%, -50%)",
textAlign: "left", textAlign: "left",
display: "inline-block", display: "inline-block",

View File

@ -43,12 +43,12 @@ export function t(path: string, replacement?: { [key: string]: string }) {
findPartsForData(currentLanguage.data, parts) || findPartsForData(currentLanguage.data, parts) ||
findPartsForData(fallbackLanguage.data, parts); findPartsForData(fallbackLanguage.data, parts);
if (translation === undefined) { if (translation === undefined) {
throw new Error("Can't find translation for " + path); throw new Error(`Can't find translation for ${path}`);
} }
if (replacement) { if (replacement) {
for (var key in replacement) { for (var key in replacement) {
translation = translation.replace("{{" + key + "}}", replacement[key]); translation = translation.replace(`{{${key}}}`, replacement[key]);
} }
} }
return translation; return translation;

View File

@ -221,7 +221,9 @@ export class App extends React.Component<any, AppState> {
}; };
private onCut = (e: ClipboardEvent) => { private onCut = (e: ClipboardEvent) => {
if (isInputLike(e.target) && !isToolIcon(e.target)) return; if (isInputLike(e.target) && !isToolIcon(e.target)) {
return;
}
e.clipboardData?.setData( e.clipboardData?.setData(
"text/plain", "text/plain",
JSON.stringify( JSON.stringify(
@ -235,7 +237,9 @@ export class App extends React.Component<any, AppState> {
e.preventDefault(); e.preventDefault();
}; };
private onCopy = (e: ClipboardEvent) => { private onCopy = (e: ClipboardEvent) => {
if (isInputLike(e.target) && !isToolIcon(e.target)) return; if (isInputLike(e.target) && !isToolIcon(e.target)) {
return;
}
e.clipboardData?.setData( e.clipboardData?.setData(
"text/plain", "text/plain",
JSON.stringify( JSON.stringify(
@ -247,7 +251,9 @@ export class App extends React.Component<any, AppState> {
e.preventDefault(); e.preventDefault();
}; };
private onPaste = (e: ClipboardEvent) => { private onPaste = (e: ClipboardEvent) => {
if (isInputLike(e.target) && !isToolIcon(e.target)) return; if (isInputLike(e.target) && !isToolIcon(e.target)) {
return;
}
const paste = e.clipboardData?.getData("text") || ""; const paste = e.clipboardData?.getData("text") || "";
this.addElementsFromPaste(paste); this.addElementsFromPaste(paste);
e.preventDefault(); e.preventDefault();
@ -339,7 +345,9 @@ export class App extends React.Component<any, AppState> {
}; };
private onKeyDown = (event: KeyboardEvent) => { private onKeyDown = (event: KeyboardEvent) => {
if (isInputLike(event.target) && event.key !== KEYS.ESCAPE) return; if (isInputLike(event.target) && event.key !== KEYS.ESCAPE) {
return;
}
const actionResult = this.actionManager.handleKeyDown( const actionResult = this.actionManager.handleKeyDown(
event, event,
@ -349,7 +357,9 @@ export class App extends React.Component<any, AppState> {
if (actionResult) { if (actionResult) {
this.syncActionResult(actionResult); this.syncActionResult(actionResult);
if (actionResult) return; if (actionResult) {
return;
}
} }
const shape = findShapeByKey(event.key); const shape = findShapeByKey(event.key);
@ -361,10 +371,15 @@ export class App extends React.Component<any, AppState> {
elements = elements.map(el => { elements = elements.map(el => {
if (el.isSelected) { if (el.isSelected) {
const element = { ...el }; const element = { ...el };
if (event.key === KEYS.ARROW_LEFT) element.x -= step; if (event.key === KEYS.ARROW_LEFT) {
else if (event.key === KEYS.ARROW_RIGHT) element.x += step; element.x -= step;
else if (event.key === KEYS.ARROW_UP) element.y -= step; } else if (event.key === KEYS.ARROW_RIGHT) {
else if (event.key === KEYS.ARROW_DOWN) element.y += step; element.x += step;
} else if (event.key === KEYS.ARROW_UP) {
element.y -= step;
} else if (event.key === KEYS.ARROW_DOWN) {
element.y += step;
}
return element; return element;
} }
return el; return el;
@ -602,13 +617,14 @@ export class App extends React.Component<any, AppState> {
actionManager={this.actionManager} actionManager={this.actionManager}
syncActionResult={this.syncActionResult} syncActionResult={this.syncActionResult}
onExportToPng={(exportedElements, scale) => { onExportToPng={(exportedElements, scale) => {
if (this.canvas) if (this.canvas) {
exportCanvas("png", exportedElements, this.canvas, { exportCanvas("png", exportedElements, this.canvas, {
exportBackground: this.state.exportBackground, exportBackground: this.state.exportBackground,
name: this.state.name, name: this.state.name,
viewBackgroundColor: this.state.viewBackgroundColor, viewBackgroundColor: this.state.viewBackgroundColor,
scale, scale,
}); });
}
}} }}
onExportToSvg={(exportedElements, scale) => { onExportToSvg={(exportedElements, scale) => {
if (this.canvas) { if (this.canvas) {
@ -621,16 +637,17 @@ export class App extends React.Component<any, AppState> {
} }
}} }}
onExportToClipboard={(exportedElements, scale) => { onExportToClipboard={(exportedElements, scale) => {
if (this.canvas) if (this.canvas) {
exportCanvas("clipboard", exportedElements, this.canvas, { exportCanvas("clipboard", exportedElements, this.canvas, {
exportBackground: this.state.exportBackground, exportBackground: this.state.exportBackground,
name: this.state.name, name: this.state.name,
viewBackgroundColor: this.state.viewBackgroundColor, viewBackgroundColor: this.state.viewBackgroundColor,
scale, scale,
}); });
}
}} }}
onExportToBackend={exportedElements => { onExportToBackend={exportedElements => {
if (this.canvas) if (this.canvas) {
exportCanvas( exportCanvas(
"backend", "backend",
exportedElements.map(element => ({ exportedElements.map(element => ({
@ -640,6 +657,7 @@ export class App extends React.Component<any, AppState> {
this.canvas, this.canvas,
this.state, this.state,
); );
}
}} }}
/> />
{this.actionManager.renderAction( {this.actionManager.renderAction(
@ -813,7 +831,9 @@ export class App extends React.Component<any, AppState> {
lastMouseUp(e); lastMouseUp(e);
} }
if (isPanning) return; if (isPanning) {
return;
}
// pan canvas on wheel button drag or space+drag // pan canvas on wheel button drag or space+drag
if ( if (
@ -826,8 +846,8 @@ export class App extends React.Component<any, AppState> {
document.documentElement.style.cursor = CURSOR_TYPE.GRABBING; document.documentElement.style.cursor = CURSOR_TYPE.GRABBING;
let { clientX: lastX, clientY: lastY } = e; let { clientX: lastX, clientY: lastY } = e;
const onMouseMove = (e: MouseEvent) => { const onMouseMove = (e: MouseEvent) => {
let deltaX = lastX - e.clientX; const deltaX = lastX - e.clientX;
let deltaY = lastY - e.clientY; const deltaY = lastY - e.clientY;
lastX = e.clientX; lastX = e.clientX;
lastY = e.clientY; lastY = e.clientY;
// We don't want to save history when panning around // We don't want to save history when panning around
@ -857,7 +877,9 @@ export class App extends React.Component<any, AppState> {
} }
// only handle left mouse button // only handle left mouse button
if (e.button !== MOUSE_BUTTON.MAIN) return; if (e.button !== MOUSE_BUTTON.MAIN) {
return;
}
// fixes mousemove causing selection of UI texts #32 // fixes mousemove causing selection of UI texts #32
e.preventDefault(); e.preventDefault();
// Preventing the event above disables default behavior // Preventing the event above disables default behavior
@ -1080,7 +1102,7 @@ export class App extends React.Component<any, AppState> {
const absPx = p1[0] + element.x; const absPx = p1[0] + element.x;
const absPy = p1[1] + element.y; const absPy = p1[1] + element.y;
let { width, height } = getPerfectElementSize( const { width, height } = getPerfectElementSize(
"arrow", "arrow",
mouseX - element.x - p1[0], mouseX - element.x - p1[0],
mouseY - element.y - p1[1], mouseY - element.y - p1[1],
@ -1155,8 +1177,9 @@ export class App extends React.Component<any, AppState> {
// triggering mousemove) // triggering mousemove)
if (!draggingOccurred && this.state.elementType === "arrow") { if (!draggingOccurred && this.state.elementType === "arrow") {
const { x, y } = viewportCoordsToSceneCoords(e, this.state); const { x, y } = viewportCoordsToSceneCoords(e, this.state);
if (distance2d(x, y, originX, originY) < DRAGGING_THRESHOLD) if (distance2d(x, y, originX, originY) < DRAGGING_THRESHOLD) {
return; return;
}
} }
if (isResizingElements && this.state.resizingElement) { if (isResizingElements && this.state.resizingElement) {
@ -1432,7 +1455,9 @@ export class App extends React.Component<any, AppState> {
// It is very important to read this.state within each move event, // It is very important to read this.state within each move event,
// otherwise we would read a stale one! // otherwise we would read a stale one!
const draggingElement = this.state.draggingElement; const draggingElement = this.state.draggingElement;
if (!draggingElement) return; if (!draggingElement) {
return;
}
const { x, y } = viewportCoordsToSceneCoords(e, this.state); const { x, y } = viewportCoordsToSceneCoords(e, this.state);
@ -1443,8 +1468,12 @@ export class App extends React.Component<any, AppState> {
this.state.elementType === "line" || this.state.elementType === "line" ||
this.state.elementType === "arrow"; this.state.elementType === "arrow";
if (isLinear && x < originX) width = -width; if (isLinear && x < originX) {
if (isLinear && y < originY) height = -height; width = -width;
}
if (isLinear && y < originY) {
height = -height;
}
if (e.shiftKey) { if (e.shiftKey) {
({ width, height } = getPerfectElementSize( ({ width, height } = getPerfectElementSize(
@ -1453,7 +1482,9 @@ export class App extends React.Component<any, AppState> {
!isLinear && y < originY ? -height : height, !isLinear && y < originY ? -height : height,
)); ));
if (!isLinear && height < 0) height = -height; if (!isLinear && height < 0) {
height = -height;
}
} }
if (!isLinear) { if (!isLinear) {
@ -1724,7 +1755,9 @@ export class App extends React.Component<any, AppState> {
}); });
}} }}
onMouseMove={e => { onMouseMove={e => {
if (isHoldingSpace || isPanning) return; if (isHoldingSpace || isPanning) {
return;
}
const hasDeselectedButton = Boolean(e.buttons); const hasDeselectedButton = Boolean(e.buttons);
if ( if (
hasDeselectedButton || hasDeselectedButton ||

View File

@ -176,7 +176,7 @@ export function renderElement(
context.font = font; context.font = font;
context.globalAlpha = 1; context.globalAlpha = 1;
} else { } else {
throw new Error("Unimplemented type " + element.type); throw new Error(`Unimplemented type ${element.type}`);
} }
} }
} }
@ -267,7 +267,7 @@ export function renderElementToSvg(
} }
svgRoot.appendChild(node); svgRoot.appendChild(node);
} else { } else {
throw new Error("Unimplemented type " + element.type); throw new Error(`Unimplemented type ${element.type}`);
} }
} }
} }

View File

@ -32,7 +32,9 @@ export function renderScene(
renderSelection?: boolean; renderSelection?: boolean;
} = {}, } = {},
) { ) {
if (!canvas) return false; if (!canvas) {
return false;
}
const context = canvas.getContext("2d")!; const context = canvas.getContext("2d")!;
const fillStyle = context.fillStyle; const fillStyle = context.fillStyle;
@ -130,7 +132,7 @@ export function renderScene(
context.fillStyle = SCROLLBAR_COLOR; context.fillStyle = SCROLLBAR_COLOR;
context.strokeStyle = "rgba(255,255,255,0.8)"; context.strokeStyle = "rgba(255,255,255,0.8)";
[scrollBars.horizontal, scrollBars.vertical].forEach(scrollBar => { [scrollBars.horizontal, scrollBars.vertical].forEach(scrollBar => {
if (scrollBar) if (scrollBar) {
roundRect( roundRect(
context, context,
scrollBar.x, scrollBar.x,
@ -139,6 +141,7 @@ export function renderScene(
scrollBar.height, scrollBar.height,
SCROLLBAR_WIDTH / 2, SCROLLBAR_WIDTH / 2,
); );
}
}); });
context.strokeStyle = strokeStyle; context.strokeStyle = strokeStyle;
context.fillStyle = fillStyle; context.fillStyle = fillStyle;
@ -161,14 +164,13 @@ function isVisibleElement(
x2 += scrollX; x2 += scrollX;
y2 += scrollY; y2 += scrollY;
return x2 >= 0 && x1 <= canvasWidth && y2 >= 0 && y1 <= canvasHeight; return x2 >= 0 && x1 <= canvasWidth && y2 >= 0 && y1 <= canvasHeight;
} else {
return (
x2 + scrollX >= 0 &&
x1 + scrollX <= canvasWidth &&
y2 + scrollY >= 0 &&
y1 + scrollY <= canvasHeight
);
} }
return (
x2 + scrollX >= 0 &&
x1 + scrollX <= canvasWidth &&
y2 + scrollY >= 0 &&
y1 + scrollY <= canvasHeight
);
} }
// This should be only called for exporting purposes // This should be only called for exporting purposes

View File

@ -49,7 +49,7 @@ export function serializeAsJSON(
export function calculateScrollCenter( export function calculateScrollCenter(
elements: readonly ExcalidrawElement[], elements: readonly ExcalidrawElement[],
): { scrollX: number; scrollY: number } { ): { scrollX: number; scrollY: number } {
let [x1, y1, x2, y2] = getCommonBounds(elements); const [x1, y1, x2, y2] = getCommonBounds(elements);
const centerX = (x1 + x2) / 2; const centerX = (x1 + x2) / 2;
const centerY = (y1 + y2) / 2; const centerY = (y1 + y2) / 2;
@ -149,7 +149,6 @@ export async function exportToBackend(
} }
} catch (e) { } catch (e) {
window.alert(t("alerts.couldNotCreateShareableLink")); window.alert(t("alerts.couldNotCreateShareableLink"));
return;
} }
} }
@ -194,8 +193,9 @@ export async function exportCanvas(
scale?: number; scale?: number;
}, },
) { ) {
if (!elements.length) if (!elements.length) {
return window.alert(t("alerts.cannotExportEmptyCanvas")); return window.alert(t("alerts.cannotExportEmptyCanvas"));
}
// calculate smallest area to fit the contents in // calculate smallest area to fit the contents in
if (type === "svg") { if (type === "svg") {
@ -252,7 +252,9 @@ export async function exportCanvas(
} }
// clean up the DOM // clean up the DOM
if (tempCanvas !== canvas) tempCanvas.remove(); if (tempCanvas !== canvas) {
tempCanvas.remove();
}
} }
function restore( function restore(

View File

@ -75,8 +75,9 @@ export const shapesShortcutKeys = SHAPES.map((shape, index) => [
export function findShapeByKey(key: string) { export function findShapeByKey(key: string) {
const defaultElement = "selection"; const defaultElement = "selection";
return SHAPES.reduce((element, shape, index) => { return SHAPES.reduce((element, shape, index) => {
if (shape.value[0] !== key && key !== (index + 1).toString()) if (shape.value[0] !== key && key !== (index + 1).toString()) {
return element; return element;
}
return shape.value; return shape.value;
}, defaultElement); }, defaultElement);