(arr: T[], iteration: (a: T, b: T) => void) {
+ const len = arr.length
+ const lenMinusOne = len - 1
+ for (let i = 0; i < lenMinusOne; ++i) {
+ const el = arr[i]
+ for (let j = i + 1; j < len; ++j) {
+ iteration(el, arr[j])
+ }
+ }
+}
diff --git a/src/utils/comparisonLevel.ts b/src/utils/comparisonLevel.ts
new file mode 100644
index 000000000..e5bb15d37
--- /dev/null
+++ b/src/utils/comparisonLevel.ts
@@ -0,0 +1,21 @@
+import { Component, ComponentClass } from 'react'
+
+import equalsByLevel from './equalsByLevel'
+
+const comparisonLevel = (level: number) =>
+ >(Class: C) => {
+ Class.prototype.shouldComponentUpdate = function (this: Component, nextProps: P, nextState: S) {
+ return !equalsByLevel(this.props, nextProps, level)
+ || !equalsByLevel(this.state, nextState, level)
+ }
+
+ const composedComponentName = Class.displayName
+ || Class.name
+ || 'Component'
+
+ Class.displayName = `comparisonLevel(${level})(${composedComponentName})`
+
+ return Class
+ }
+
+export default comparisonLevel
diff --git a/src/utils/equalsByLevel.ts b/src/utils/equalsByLevel.ts
new file mode 100644
index 000000000..1409608f6
--- /dev/null
+++ b/src/utils/equalsByLevel.ts
@@ -0,0 +1,31 @@
+const sameContent = (arr1: T[], arr2: T[]) =>
+ arr1.length === arr2.length && arr1.every(item => arr2.includes(item))
+
+function objectsAreTheSame(o1: object, o2: object, currentLevel: number): boolean {
+ const keys1 = Object.keys(o1)
+ const keys2 = Object.keys(o2)
+
+ return sameContent(keys1, keys2)
+ && keys1.every(k => equalsByLevel(o1[k], o2[k], currentLevel - 1))
+}
+
+const equalsByLevel = (o1: any, o2: any, currentLevel: number) =>
+ o1 === o2
+ || currentLevel > 0
+ && o1
+ && o2
+ && typeof o1 === 'object'
+ && typeof o2 === 'object'
+ && objectsAreTheSame(o1, o2, currentLevel)
+
+/**
+ * 0: ===
+ * 1: shallow
+ * Infinity: deep
+ */
+export default (o1: any, o2: any, level: number) => {
+ if (!Number.isSafeInteger(level) || level < 0) {
+ throw new Error(`level must be a non-negative safe integer (i.e. from 0 to 9007199254740991 inclusive) or Infinity, but got ${level} instead`)
+ }
+ return equalsByLevel(o1, o2, level)
+}
diff --git a/src/utils/math/index.ts b/src/utils/math/index.ts
index 967ad916a..fe28b0e07 100644
--- a/src/utils/math/index.ts
+++ b/src/utils/math/index.ts
@@ -57,7 +57,9 @@ export function offsetLine(points: Point[], d: number): Point[] {
throw new Error('line must have 2 points')
}
if (points[0].equals(points[1])) {
- throw new Error('points are overlapped')
+ console.error('points are overlapped:')
+ return points
+ // throw new Error('points are overlapped')
}
const vec = points[1].subtract(points[0])
const [o] = orthogonal(vec)
diff --git a/src/utils/math/vector.ts b/src/utils/math/vector.ts
index 639aaaa4a..d4bcdd204 100644
--- a/src/utils/math/vector.ts
+++ b/src/utils/math/vector.ts
@@ -2,8 +2,8 @@ import { Point, point } from 'leaflet'
import { isArbitrarilySmall as isNumberSmall } from '.'
-export type Ray = [Point, Point]
-type Segment = [Point, Point]
+export type Ray = Readonly<[Point, Point]>
+type Segment = Readonly<[Point, Point]>
enum Orientation {
COLLINEAR,
diff --git a/src/utils/memoizeObject.ts b/src/utils/memoizeObject.ts
new file mode 100644
index 000000000..fa56da41b
--- /dev/null
+++ b/src/utils/memoizeObject.ts
@@ -0,0 +1,10 @@
+import memoizeOne from 'memoize-one'
+import { identity } from 'lodash'
+
+import shallowEqual from './shallowEqual'
+
+const comparator = (newArgs: [any], oldArgs: [any]) =>
+ shallowEqual(newArgs[0], oldArgs[0])
+
+export default () =>
+ memoizeOne(identity, comparator)
diff --git a/src/utils/shallowEqual.ts b/src/utils/shallowEqual.ts
new file mode 100644
index 000000000..aa297b934
--- /dev/null
+++ b/src/utils/shallowEqual.ts
@@ -0,0 +1,4 @@
+import equalsByLevel from './equalsByLevel'
+
+export default (a: any, b: any) =>
+ equalsByLevel(a, b, 1)
diff --git a/src/utils/svg/filters.ts b/src/utils/svg/filters.ts
index d57fc74c4..bec97e485 100644
--- a/src/utils/svg/filters.ts
+++ b/src/utils/svg/filters.ts
@@ -1,104 +1,5 @@
-import { createSVGElement } from '.'
-
const GLOW_FILTER_ID = 'black-glow'
-export function makeDrop(): SVGFilterElement {
- const filter = createSVGElement('filter')
- filter.id = 'shadow'
- filter.setAttribute('width', '200%')
- filter.setAttribute('height', '200%')
- filter.innerHTML = `
-
-
-
-
- `
- return filter
-}
-
-export function makeGlow(): SVGFilterElement {
- const filter = createSVGElement('filter')
- filter.id = GLOW_FILTER_ID
- filter.innerHTML = `
-
-
-
-
-
-
- `
- return filter
-}
-
-export function makeOpacity(): SVGFilterElement {
- const filter = createSVGElement('filter')
- filter.id = 'opacity'
- filter.innerHTML = `
-
-
-
- `
- return filter
-}
-
-export function makeGray(): SVGFilterElement {
- const filter = createSVGElement('filter')
- filter.id = 'gray'
- filter.innerHTML = `
-
- `
- return filter
-}
-
-export function appendAll(defs: SVGDefsElement) {
- defs.appendChild(makeDrop())
- defs.appendChild(makeGlow())
- defs.appendChild(makeOpacity())
- defs.appendChild(makeGray())
-}
-
export function applyDrop(path: SVGPathElement | SVGLineElement) {
// fixing disappearing lines
const box = path.getBoundingClientRect()
diff --git a/src/utils/types.ts b/src/utils/types.ts
new file mode 100644
index 000000000..6fee95131
--- /dev/null
+++ b/src/utils/types.ts
@@ -0,0 +1,3 @@
+export type Omit = Pick>
+
+export type Subtract = Omit
diff --git a/tsconfig.json b/tsconfig.json
index d48aa00af..7b86bd186 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -11,10 +11,12 @@
"alwaysStrict": true,
"strictBindCallApply": true,
"experimentalDecorators": true,
+ "jsx": "react",
"strictNullChecks": true,
+ "esModuleInterop": true,
"noImplicitThis": true,
- "noUnusedLocals": true,
- // "downlevelIteration": true,
+ "noImplicitAny": true,
+ "downlevelIteration": true,
"resolveJsonModule": true,
"noEmitHelpers": true,
"importHelpers": true,
diff --git a/webpack/rules.js b/webpack/rules.js
index 9ceb9a34b..932dcc11c 100644
--- a/webpack/rules.js
+++ b/webpack/rules.js
@@ -16,6 +16,7 @@ const tsOptions = (isDev) => isDev ? {} : {
const getCssLoader = global => global ? 'css-loader' : {
loader: 'css-loader',
options: {
+ esModule: false,
modules: {
localIdentName: '[path]___[name]__[local]___[hash:base64:5]',
},
@@ -36,7 +37,7 @@ module.exports = (isDev) => compact([
// use: 'source-map-loader',
// },
{
- test: /\.ts$/,
+ test: /\.tsx?$/,
use: {
loader: 'ts-loader',
options: tsOptions(isDev),
diff --git a/webpack/webpack.config.js b/webpack/webpack.config.js
index b58239ede..df925bd71 100644
--- a/webpack/webpack.config.js
+++ b/webpack/webpack.config.js
@@ -27,7 +27,7 @@ module.exports = (env) => {
mode: isDev ? 'development' : 'production',
target: 'web',
entry: {
- app: './src/main.ts',
+ app: './src/index.ts',
},
output: {
clean: true,