From 94daffa2d211a4d22071c8c120e2f01f19709d07 Mon Sep 17 00:00:00 2001 From: Narendra Syahrasyad Date: Fri, 4 Dec 2020 00:03:28 -0800 Subject: [PATCH 01/15] refactor: migrate core logic to ts --- .eslintrc.js | 4 +- components/{Algorithm.js => Algorithm.tsx} | 38 ++- components/{Iterable.js => Iterable.tsx} | 12 +- jsconfig.json | 10 - lib/makeAlgorithmPage.js | 21 -- lib/makeAlgorithmPage.tsx | 31 +++ lib/types.ts | 91 +++++++ lib/{useAlgorithm.js => useAlgorithm.ts} | 43 +-- next-env.d.ts | 2 + package.json | 7 +- ...-all-averages.js => find-all-averages.tsx} | 55 ++-- tsconfig.json | 25 ++ yarn.lock | 250 +++++++++++++++++- 13 files changed, 496 insertions(+), 93 deletions(-) rename components/{Algorithm.js => Algorithm.tsx} (82%) rename components/{Iterable.js => Iterable.tsx} (77%) delete mode 100644 jsconfig.json delete mode 100644 lib/makeAlgorithmPage.js create mode 100644 lib/makeAlgorithmPage.tsx create mode 100644 lib/types.ts rename lib/{useAlgorithm.js => useAlgorithm.ts} (51%) create mode 100644 next-env.d.ts rename pages/patterns/sliding-window/{find-all-averages.js => find-all-averages.tsx} (73%) create mode 100644 tsconfig.json diff --git a/.eslintrc.js b/.eslintrc.js index 819e5d0..adae687 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,4 +1,5 @@ module.exports = { + parser: '@typescript-eslint/parser', root: true, // Make sure eslint picks up the config at the root of the directory parserOptions: { ecmaVersion: 2020, // Use the latest ecmascript standard @@ -19,7 +20,7 @@ module.exports = { es6: true, }, extends: [ - 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', 'plugin:react/recommended', 'plugin:jsx-a11y/recommended', 'plugin:prettier/recommended', // Make this the last element so prettier config overrides other formatting rules @@ -28,5 +29,6 @@ module.exports = { 'react/prop-types': 'off', 'prettier/prettier': ['error', {}, { usePrettierrc: true }], // Use our .prettierrc file as source 'jsx-a11y/anchor-is-valid': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', }, } diff --git a/components/Algorithm.js b/components/Algorithm.tsx similarity index 82% rename from components/Algorithm.js rename to components/Algorithm.tsx index f8f804c..9f9b604 100644 --- a/components/Algorithm.js +++ b/components/Algorithm.tsx @@ -1,5 +1,5 @@ -import React, { useState } from 'react' -import clsx from 'clsx' +import React from 'react' +import clsx, { ClassValue } from 'clsx' import { motion, AnimateSharedLayout } from 'framer-motion' import { BsFillPlayFill, BsPauseFill } from 'react-icons/bs' import { FaUndoAlt, FaCheck, FaTimes, FaCog } from 'react-icons/fa' @@ -9,11 +9,12 @@ import { BiLeftArrowAlt, BiRightArrowAlt } from 'react-icons/bi' import { Button } from '~components/Button' import { Input } from '~components/Input' import { useAlgorithm } from '~lib/useAlgorithm' +import { AlgorithmContext } from '~lib/types' import styles from './styles/Algorithm.module.scss' -export function Algorithm({ children, algorithm, inputs, parseArgs, ...opts }) { - const context = useAlgorithm(algorithm, inputs, parseArgs) +export function Algorithm({ children, algorithm, inputs, ...opts }) { + const context = useAlgorithm(algorithm, inputs) return ( @@ -26,17 +27,24 @@ export function Algorithm({ children, algorithm, inputs, parseArgs, ...opts }) { // --- +type ControlsProps = { + context: AlgorithmContext + serialize?: (key: string, val: unknown) => readonly [string, string] + unserialize?: (key: string, val: string) => readonly [string, unknown] + className?: ClassValue +} + const forms = { Inputs: 'inputs', Settings: 'settings', -} +} as const -function defaultSerializer(key, val) { - return [key, JSON.stringify(val)] +function defaultSerializer(key: string, val: unknown) { + return [key, JSON.stringify(val)] as const } -function defaultUnserializer(key, val) { - return [key, val.length && JSON.parse(val)] +function defaultUnserializer(key: string, val: string) { + return [key, val.length && JSON.parse(val)] as const } function Controls({ @@ -44,13 +52,15 @@ function Controls({ serialize = defaultSerializer, unserialize = defaultUnserializer, className = '', -}) { - const [editing, setEditing] = useState(null) - const [inputs, setInputs] = useState({}) +}: ControlsProps) { + const [editing, setEditing] = React.useState(null) + const [inputs, setInputs] = React.useState({}) const save = () => { const newInputs = Object.fromEntries( - Object.entries(inputs).map(([name, value]) => unserialize(name, value)) + Object.entries(inputs).map(([name, value]: [string, string]) => + unserialize(name, value) + ) ) actions.reset() editing === forms.Inputs @@ -59,7 +69,7 @@ function Controls({ setEditing(null) } - const toggle = (form) => { + const toggle = (form: 'inputs' | 'settings') => { if (editing === null) { setEditing(form) } else { diff --git a/components/Iterable.js b/components/Iterable.tsx similarity index 77% rename from components/Iterable.js rename to components/Iterable.tsx index 781a4cb..6f453bd 100644 --- a/components/Iterable.js +++ b/components/Iterable.tsx @@ -1,6 +1,6 @@ import React from 'react' -import { motion } from 'framer-motion' -import clsx from 'clsx' +import { motion, HTMLMotionProps } from 'framer-motion' +import clsx, { ClassValue } from 'clsx' import { Pointer } from './Pointer' @@ -23,13 +23,19 @@ export function Iterable({ children }) { return
{children}
} +type IterableItemProps = { + className: ClassValue + active?: boolean + pointer?: boolean +} & Omit, 'className'> + export function IterableItem({ children, className, active, pointer, ...motionProps -}) { +}: IterableItemProps) { return ( - - - - - ) - } - Page.displayName = `${Component.name}Page` - return Page -} diff --git a/lib/makeAlgorithmPage.tsx b/lib/makeAlgorithmPage.tsx new file mode 100644 index 0000000..a84d017 --- /dev/null +++ b/lib/makeAlgorithmPage.tsx @@ -0,0 +1,31 @@ +import React from 'react' + +import { Algorithm } from '~components/Algorithm' +import { AlgorithmPage } from '~components/AlgorithmPage' +import { RecordableFunction, Params } from '~lib/types' + +type AlgorithmOptions = { + title: string + description: string + pattern: string + algorithm: T + inputs: Params +} + +export function makeAlgorithmPage( + options: AlgorithmOptions, + Component: (props: unknown) => JSX.Element +) { + const { title, pattern, description, algorithm, inputs } = options + const Page = function () { + return ( + + + + + + ) + } + Page.displayName = `${Component.name}Page` + return Page +} diff --git a/lib/types.ts b/lib/types.ts new file mode 100644 index 0000000..9743fbe --- /dev/null +++ b/lib/types.ts @@ -0,0 +1,91 @@ +export type Recorder = { + record: (data: Partial) => void +} + +export type RecordableFunction< + Params extends Record = Record, + State = unknown +> = (recorder: Recorder, args: Params) => unknown + +export type Params = Func extends RecordableFunction + ? Params + : Record + +export type DisplayProps = T extends RecordableFunction< + infer Params, + infer State +> + ? { + state: Partial + inputs: Params + } + : unknown + +export type Settings = { + /** + * Determines the running speed of the animation. This number is passed directly + * to setInterval - defaults to 500ms. + */ + delay: number +} + +export type Snapshot = { + /** + * A snapshot of the current state of the algorithm. Typically represents the + * value of specific variables at a particular point in time. + */ + state: State + /** + * An array of all states recorded by the algorithm. + */ + steps: State[] + /** + * The inputs passed to the running algorithm. + */ + inputs: Parameters + /** + * Whether the current animation is playing. + */ + isPlaying: boolean + /** + * Settings for the current animation. + */ + settings: Settings +} + +export type AlgorithmContext< + Parameters = Record, + State = unknown +> = { + models: Snapshot + actions: { + /** + * Set arguments for the algorithm. Triggers a re-run of the algorithm, in turn + * updating `steps` and `state`. + */ + setInputs(newArguments: Parameters): void + /** + * Set new settings for the running algorithm. Does NOT trigger a re-run. + * - NOTE: Might convert this to a global setting instead. + */ + setSettings(settings: Partial): void + /** + * Moves to the next recorded step of the algorithm. Does nothing if at the end. + */ + next(): void + /** + * Moves to the previous recorded step of the algorithm. Does nothing if at + * start. + */ + prev(): void + /** + * Resets the current active state back to the start. + */ + reset(): void + /** + * Starts the animation if paused or stopped, pauses the animation otherwise. + * When the animation is running, `state` is updated every `delay` ms. + */ + toggle(): void + } +} diff --git a/lib/useAlgorithm.js b/lib/useAlgorithm.ts similarity index 51% rename from lib/useAlgorithm.js rename to lib/useAlgorithm.ts index b84c86d..ec27c15 100644 --- a/lib/useAlgorithm.js +++ b/lib/useAlgorithm.ts @@ -1,19 +1,31 @@ -import { useState, useMemo } from 'react' +import React from 'react' import useInterval from '@use-it/interval' -import { identity } from '~utils/helpers' -const defaultSettings = { - delay: 400, -} +import { AlgorithmContext, Settings } from '~lib/types' -export function useAlgorithm(algorithm, defaultInputs, parser = identity) { - const [activeStepIndex, setActiveStepIndex] = useState(0) - const [isPlaying, setIsPlaying] = useState(false) - const [inputs, setInputs] = useState(parser(defaultInputs)) - const [settings, setSettings] = useState(defaultSettings) +/** + * Given an algorithm and arguments, this hook runs the algorithm with the given + * arguments and returns a series of algorithm "states" and animation controls. + */ +export function useAlgorithm< + Parameters extends Record, + State = unknown +>( + algorithm: ( + { record }: { record: (data: State) => void }, + args: Parameters + ) => unknown, + initialArguments: Parameters +): AlgorithmContext { + const [activeStepIndex, setActiveStepIndex] = React.useState(0) + const [isPlaying, setIsPlaying] = React.useState(false) + const [inputs, setInputs] = React.useState(initialArguments) + const [settings, setSettings] = React.useState({ + delay: 500, + }) - const steps = useMemo(() => { - const recordedSteps = [] + const steps = React.useMemo(() => { + const recordedSteps: State[] = [] algorithm({ record: (data) => recordedSteps.push(data) }, inputs) return recordedSteps }, [inputs, algorithm]) @@ -56,9 +68,9 @@ export function useAlgorithm(algorithm, defaultInputs, parser = identity) { return { models: { state: steps[activeStepIndex], - isPlaying, steps, inputs, + isPlaying, settings, }, actions: { @@ -66,8 +78,9 @@ export function useAlgorithm(algorithm, defaultInputs, parser = identity) { toggle, next, prev, - setInputs: (inputs) => setInputs(parser(inputs)), - setSettings, + setInputs, + setSettings: (partialSettings) => + setSettings({ ...settings, ...partialSettings }), }, } } diff --git a/next-env.d.ts b/next-env.d.ts new file mode 100644 index 0000000..7b7aa2c --- /dev/null +++ b/next-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/package.json b/package.json index 0352836..6c4f453 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,10 @@ "uuid": "^8.3.0" }, "devDependencies": { + "@types/node": "^14.14.10", + "@types/react": "^17.0.0", + "@typescript-eslint/eslint-plugin": "^4.9.0", + "@typescript-eslint/parser": "^4.9.0", "eslint": "^7.8.1", "eslint-config-prettier": "^6.11.0", "eslint-plugin-jsx-a11y": "^6.3.1", @@ -32,6 +36,7 @@ "eslint-plugin-react-hooks": "^4.1.0", "postcss-preset-env": "^6.7.0", "prettier": "^2.1.1", - "tailwindcss": "^1.8.2" + "tailwindcss": "^1.8.2", + "typescript": "^4.1.2" } } diff --git a/pages/patterns/sliding-window/find-all-averages.js b/pages/patterns/sliding-window/find-all-averages.tsx similarity index 73% rename from pages/patterns/sliding-window/find-all-averages.js rename to pages/patterns/sliding-window/find-all-averages.tsx index 1802a28..bd8b530 100644 --- a/pages/patterns/sliding-window/find-all-averages.js +++ b/pages/patterns/sliding-window/find-all-averages.tsx @@ -1,13 +1,24 @@ import React from 'react' import { Iterable, IterableItem } from '~components/Iterable' -import { makeAlgorithmPage } from '~lib/makeAlgorithmPage' import { Window } from '~components/Window' +import { makeAlgorithmPage } from '~lib/makeAlgorithmPage' +import { DisplayProps, RecordableFunction } from '~lib/types' + +const meta = { + title: 'Find All Averages', + pattern: 'Sliding Window', + description: 'Given an array, find the averages of all subarrays of size k.', +} -function FindAllAverages({ state, inputs }) { +function FindAllAverages({ + state, + inputs, +}: DisplayProps) { const { done, start, end, result } = state const { arr, k } = inputs - const isActive = (index) => (done ? true : index >= start && index <= end) + const isActive = (index: number) => + done ? true : index >= start && index <= end return ( <> @@ -37,24 +48,18 @@ function FindAllAverages({ state, inputs }) { ) } -export default makeAlgorithmPage( - { - title: 'Find All Averages', - pattern: 'Sliding Window', - description: - 'Given an array, find the averages of all subarrays of size k.', - algorithm: findAllAverages, - inputs: { - arr: [1, 3, 2, 6, -1, 4, 1, 8, 2], - k: 3, - }, - }, - FindAllAverages -) - // -- -function findAllAverages({ record }, { arr, k }) { +const findAllAverages: RecordableFunction< + { arr: number[]; k: number }, + { + start: number + end: number + sum: number + result: number[] + done: boolean + } +> = ({ record }, { arr, k }) => { const result = [] let windowStart = 0 let windowSum = 0 @@ -81,3 +86,15 @@ function findAllAverages({ record }, { arr, k }) { return result } + +export default makeAlgorithmPage( + { + algorithm: findAllAverages, + inputs: { + arr: [1, 3, 2, 6, -1, 4, 1, 8, 2], + k: 3, + }, + ...meta, + }, + FindAllAverages +) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..ede0f25 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "es6", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": false, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "baseUrl": ".", + "paths": { + "~lib/*": ["lib/*"], + "~components/*": ["components/*"], + "~utils/*": ["utils/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] +} diff --git a/yarn.lock b/yarn.lock index 8a1073e..52e347d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1084,21 +1084,130 @@ resolved "https://registry.yarnpkg.com/@next/react-refresh-utils/-/react-refresh-utils-9.5.3.tgz#a14fb6489d412b201b98aa44716fb8727ca4c6ae" integrity sha512-W3VKOqbg+4Kw+k6M/SODf+WIzwcx60nAemGV1nNPa/yrDtAS2YcJfqiswrJ3+2nJHzqefAFWn4XOfM0fy8ww2Q== +"@nodelib/fs.scandir@2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" + integrity sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw== + dependencies: + "@nodelib/fs.stat" "2.0.3" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.3", "@nodelib/fs.stat@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz#34dc5f4cabbc720f4e60f75a747e7ecd6c175bd3" + integrity sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz#011b9202a70a6366e436ca5c065844528ab04976" + integrity sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ== + dependencies: + "@nodelib/fs.scandir" "2.1.3" + fastq "^1.6.0" + "@types/color-name@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== -"@types/json-schema@^7.0.5": +"@types/json-schema@^7.0.3", "@types/json-schema@^7.0.5": version "7.0.6" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0" integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw== +"@types/node@^14.14.10": + version "14.14.10" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.10.tgz#5958a82e41863cfc71f2307b3748e3491ba03785" + integrity sha512-J32dgx2hw8vXrSbu4ZlVhn1Nm3GbeCFNw2FWL8S5QKucHGY0cyNwjdQdO+KMBZ4wpmC7KhLCiNsdk1RFRIYUQQ== + +"@types/prop-types@*": + version "15.7.3" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" + integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== + +"@types/react@^17.0.0": + version "17.0.0" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.0.tgz#5af3eb7fad2807092f0046a1302b7823e27919b8" + integrity sha512-aj/L7RIMsRlWML3YB6KZiXB3fV2t41+5RBGYF8z+tAKU43Px8C3cYUZsDvf1/+Bm4FK21QWBrDutu8ZJ/70qOw== + dependencies: + "@types/prop-types" "*" + csstype "^3.0.2" + "@types/unist@^2.0.0", "@types/unist@^2.0.2": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e" integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ== +"@typescript-eslint/eslint-plugin@^4.9.0": + version "4.9.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.9.0.tgz#8fde15743413661fdc086c9f1f5d74a80b856113" + integrity sha512-WrVzGMzzCrgrpnQMQm4Tnf+dk+wdl/YbgIgd5hKGa2P+lnJ2MON+nQnbwgbxtN9QDLi8HO+JAq0/krMnjQK6Cw== + dependencies: + "@typescript-eslint/experimental-utils" "4.9.0" + "@typescript-eslint/scope-manager" "4.9.0" + debug "^4.1.1" + functional-red-black-tree "^1.0.1" + regexpp "^3.0.0" + semver "^7.3.2" + tsutils "^3.17.1" + +"@typescript-eslint/experimental-utils@4.9.0": + version "4.9.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.9.0.tgz#23a296b85d243afba24e75a43fd55aceda5141f0" + integrity sha512-0p8GnDWB3R2oGhmRXlEnCvYOtaBCijtA5uBfH5GxQKsukdSQyI4opC4NGTUb88CagsoNQ4rb/hId2JuMbzWKFQ== + dependencies: + "@types/json-schema" "^7.0.3" + "@typescript-eslint/scope-manager" "4.9.0" + "@typescript-eslint/types" "4.9.0" + "@typescript-eslint/typescript-estree" "4.9.0" + eslint-scope "^5.0.0" + eslint-utils "^2.0.0" + +"@typescript-eslint/parser@^4.9.0": + version "4.9.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.9.0.tgz#bb65f1214b5e221604996db53ef77c9d62b09249" + integrity sha512-QRSDAV8tGZoQye/ogp28ypb8qpsZPV6FOLD+tbN4ohKUWHD2n/u0Q2tIBnCsGwQCiD94RdtLkcqpdK4vKcLCCw== + dependencies: + "@typescript-eslint/scope-manager" "4.9.0" + "@typescript-eslint/types" "4.9.0" + "@typescript-eslint/typescript-estree" "4.9.0" + debug "^4.1.1" + +"@typescript-eslint/scope-manager@4.9.0": + version "4.9.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.9.0.tgz#5eefe305d6b71d1c85af6587b048426bfd4d3708" + integrity sha512-q/81jtmcDtMRE+nfFt5pWqO0R41k46gpVLnuefqVOXl4QV1GdQoBWfk5REcipoJNQH9+F5l+dwa9Li5fbALjzg== + dependencies: + "@typescript-eslint/types" "4.9.0" + "@typescript-eslint/visitor-keys" "4.9.0" + +"@typescript-eslint/types@4.9.0": + version "4.9.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.9.0.tgz#3fe8c3632abd07095c7458f7451bd14c85d0033c" + integrity sha512-luzLKmowfiM/IoJL/rus1K9iZpSJK6GlOS/1ezKplb7MkORt2dDcfi8g9B0bsF6JoRGhqn0D3Va55b+vredFHA== + +"@typescript-eslint/typescript-estree@4.9.0": + version "4.9.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.9.0.tgz#38a98df6ee281cfd6164d6f9d91795b37d9e508c" + integrity sha512-rmDR++PGrIyQzAtt3pPcmKWLr7MA+u/Cmq9b/rON3//t5WofNR4m/Ybft2vOLj0WtUzjn018ekHjTsnIyBsQug== + dependencies: + "@typescript-eslint/types" "4.9.0" + "@typescript-eslint/visitor-keys" "4.9.0" + debug "^4.1.1" + globby "^11.0.1" + is-glob "^4.0.1" + lodash "^4.17.15" + semver "^7.3.2" + tsutils "^3.17.1" + +"@typescript-eslint/visitor-keys@4.9.0": + version "4.9.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.9.0.tgz#f284e9fac43f2d6d35094ce137473ee321f266c8" + integrity sha512-sV45zfdRqQo1A97pOSx3fsjR+3blmwtdCt8LDrXgCX36v4Vmz4KHrhpV6Fo2cRdXmyumxx11AHw0pNJqCNpDyg== + dependencies: + "@typescript-eslint/types" "4.9.0" + eslint-visitor-keys "^2.0.0" + "@use-it/interval@^0.1.3": version "0.1.3" resolved "https://registry.yarnpkg.com/@use-it/interval/-/interval-0.1.3.tgz#5d1096b2295d7a5dda8e8022f3abb5f9d9ef27f8" @@ -1449,6 +1558,11 @@ array-includes@^3.1.1: es-abstract "^1.17.0" is-string "^1.0.5" +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + array-unique@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" @@ -1663,7 +1777,7 @@ braces@^2.3.1, braces@^2.3.2: split-string "^3.0.2" to-regex "^3.0.1" -braces@~3.0.2: +braces@^3.0.1, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -2374,6 +2488,11 @@ cssnano-simple@1.2.0: cssnano-preset-simple "1.2.0" postcss "^7.0.32" +csstype@^3.0.2: + version "3.0.5" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.5.tgz#7fdec6a28a67ae18647c51668a9ff95bb2fa7bb8" + integrity sha512-uVDi8LpBUKQj6sdxNaTetL6FpeCqTjOvAQuQUa/qAqq8oOd4ivkbhgnqayl0dnPal8Tb/yB1tF+gOvCBiicaiQ== + cyclist@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" @@ -2495,6 +2614,13 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + doctrine@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" @@ -2785,6 +2911,14 @@ eslint-scope@^4.0.3: esrecurse "^4.1.0" estraverse "^4.1.1" +eslint-scope@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + eslint-scope@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.0.tgz#d0f971dfe59c69e0cada684b23d49dbf82600ce5" @@ -2793,7 +2927,7 @@ eslint-scope@^5.1.0: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-utils@^2.1.0: +eslint-utils@^2.0.0, eslint-utils@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== @@ -2805,6 +2939,11 @@ eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== +eslint-visitor-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" + integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== + eslint@^7.8.1: version "7.8.1" resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.8.1.tgz#e59de3573fb6a5be8ff526c791571646d124a8fa" @@ -2869,7 +3008,7 @@ esquery@^1.2.0: dependencies: estraverse "^5.1.0" -esrecurse@^4.1.0: +esrecurse@^4.1.0, esrecurse@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== @@ -2973,6 +3112,18 @@ fast-diff@^1.1.2: resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== +fast-glob@^3.1.1: + version "3.2.4" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3" + integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.0" + merge2 "^1.3.0" + micromatch "^4.0.2" + picomatch "^2.2.1" + fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -2983,6 +3134,13 @@ fast-levenshtein@^2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= +fastq@^1.6.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.9.0.tgz#e16a72f338eaca48e91b5c23593bcc2ef66b7947" + integrity sha512-i7FVWL8HhVY+CTkwFxkN2mk3h+787ixS5S63eb78diVRc1MCssarHq3W5cj0av7YDSwmaV928RNag+U1etRQ7w== + dependencies: + reusify "^1.0.4" + figgy-pudding@^3.5.1: version "3.5.2" resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" @@ -3189,7 +3347,7 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob-parent@^5.0.0, glob-parent@~5.1.0: +glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@~5.1.0: version "5.1.1" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== @@ -3225,6 +3383,18 @@ globals@^12.1.0: dependencies: type-fest "^0.8.1" +globby@^11.0.1: + version "11.0.1" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357" + integrity sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.1.1" + ignore "^5.1.4" + merge2 "^1.3.0" + slash "^3.0.0" + graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2: version "4.2.4" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" @@ -3417,6 +3587,11 @@ ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== +ignore@^5.1.4: + version "5.1.8" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" + integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== + import-fresh@^3.0.0, import-fresh@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" @@ -3941,7 +4116,7 @@ lodash.toarray@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561" integrity sha1-JMS/zWsvuji/0FlNsRedjptlZWE= -lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.19, lodash@^4.17.20: +lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20: version "4.17.20" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== @@ -3958,7 +4133,7 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" -lru-cache@6.0.0: +lru-cache@6.0.0, lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== @@ -4075,6 +4250,11 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + micromatch@^3.1.10, micromatch@^3.1.4: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" @@ -4094,6 +4274,14 @@ micromatch@^3.1.10, micromatch@^3.1.4: snapdragon "^0.8.1" to-regex "^3.0.2" +micromatch@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" + integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== + dependencies: + braces "^3.0.1" + picomatch "^2.0.5" + miller-rabin@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" @@ -4644,6 +4832,11 @@ path-parse@^1.0.6: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + pbkdf2@^3.0.3: version "3.1.1" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.1.tgz#cb8724b0fada984596856d1a6ebafd3584654b94" @@ -4655,7 +4848,7 @@ pbkdf2@^3.0.3: safe-buffer "^5.0.1" sha.js "^2.4.8" -picomatch@^2.0.4, picomatch@^2.2.1: +picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1: version "2.2.2" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== @@ -5387,7 +5580,7 @@ regexp.prototype.flags@^1.3.0: define-properties "^1.1.3" es-abstract "^1.17.0-next.1" -regexpp@^3.1.0: +regexpp@^3.0.0, regexpp@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== @@ -5535,6 +5728,11 @@ ret@~0.1.10: resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + rework-visit@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/rework-visit/-/rework-visit-1.0.0.tgz#9945b2803f219e2f7aca00adb8bc9f640f842c9a" @@ -5570,6 +5768,11 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" +run-parallel@^1.1.9: + version "1.1.10" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.10.tgz#60a51b2ae836636c81377df16cb107351bcd13ef" + integrity sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw== + run-queue@^1.0.0, run-queue@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47" @@ -5679,6 +5882,13 @@ semver@^7.2.1: resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== +semver@^7.3.2: + version "7.3.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" + integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== + dependencies: + lru-cache "^6.0.0" + serialize-javascript@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa" @@ -5748,6 +5958,11 @@ simple-swizzle@^0.2.2: dependencies: is-arrayish "^0.3.1" +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + slice-ansi@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" @@ -6253,6 +6468,18 @@ tslib@^1.10.0, tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== +tslib@^1.8.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tsutils@^3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" + integrity sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g== + dependencies: + tslib "^1.8.1" + tty-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" @@ -6290,6 +6517,11 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= +typescript@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.2.tgz#6369ef22516fe5e10304aae5a5c4862db55380e9" + integrity sha512-thGloWsGH3SOxv1SoY7QojKi0tc+8FnOmiarEGMbd/lar7QOEd3hvlx3Fp5y6FlDUGl9L+pd4n2e+oToGMmhRQ== + unherit@^1.0.4: version "1.1.3" resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.3.tgz#6c9b503f2b41b262330c80e91c8614abdaa69c22" From 3d6d416eda989177ed613c823409c2269738b1b0 Mon Sep 17 00:00:00 2001 From: Narendra Syahrasyad Date: Wed, 23 Dec 2020 20:19:40 -0800 Subject: [PATCH 02/15] feat: use snapshot with babel macro --- .babelrc | 4 + .eslintrc.js | 2 + components/Algorithm.tsx | 83 +--------- ...eAlgorithmPage.tsx => defineAlgorithm.tsx} | 15 +- lib/snapshot.macro.d.ts | 3 + lib/snapshot.macro.js | 114 ++++++++++++++ lib/snapshot.ts | 22 +++ lib/types.ts | 28 +--- lib/useAlgorithm.ts | 29 ++-- migration.md | 13 ++ package.json | 3 + .../sliding-window/find-all-averages.tsx | 148 +++++++----------- yarn.lock | 94 +++++++++++ 13 files changed, 331 insertions(+), 227 deletions(-) create mode 100644 .babelrc rename lib/{makeAlgorithmPage.tsx => defineAlgorithm.tsx} (58%) create mode 100644 lib/snapshot.macro.d.ts create mode 100644 lib/snapshot.macro.js create mode 100644 lib/snapshot.ts create mode 100644 migration.md diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..646cc8f --- /dev/null +++ b/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["next/babel"], + "plugins": ["macros"] +} diff --git a/.eslintrc.js b/.eslintrc.js index adae687..2f25472 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -30,5 +30,7 @@ module.exports = { 'prettier/prettier': ['error', {}, { usePrettierrc: true }], // Use our .prettierrc file as source 'jsx-a11y/anchor-is-valid': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-explicit-any': 'off', }, + ignorePatterns: ['*.macro.js'], } diff --git a/components/Algorithm.tsx b/components/Algorithm.tsx index 9f9b604..c220ece 100644 --- a/components/Algorithm.tsx +++ b/components/Algorithm.tsx @@ -2,12 +2,10 @@ import React from 'react' import clsx, { ClassValue } from 'clsx' import { motion, AnimateSharedLayout } from 'framer-motion' import { BsFillPlayFill, BsPauseFill } from 'react-icons/bs' -import { FaUndoAlt, FaCheck, FaTimes, FaCog } from 'react-icons/fa' -import { RiPencilFill } from 'react-icons/ri' +import { FaUndoAlt } from 'react-icons/fa' import { BiLeftArrowAlt, BiRightArrowAlt } from 'react-icons/bi' import { Button } from '~components/Button' -import { Input } from '~components/Input' import { useAlgorithm } from '~lib/useAlgorithm' import { AlgorithmContext } from '~lib/types' @@ -29,64 +27,13 @@ export function Algorithm({ children, algorithm, inputs, ...opts }) { type ControlsProps = { context: AlgorithmContext - serialize?: (key: string, val: unknown) => readonly [string, string] - unserialize?: (key: string, val: string) => readonly [string, unknown] className?: ClassValue } -const forms = { - Inputs: 'inputs', - Settings: 'settings', -} as const - -function defaultSerializer(key: string, val: unknown) { - return [key, JSON.stringify(val)] as const -} - -function defaultUnserializer(key: string, val: string) { - return [key, val.length && JSON.parse(val)] as const -} - function Controls({ context: { actions, models }, - serialize = defaultSerializer, - unserialize = defaultUnserializer, className = '', }: ControlsProps) { - const [editing, setEditing] = React.useState(null) - const [inputs, setInputs] = React.useState({}) - - const save = () => { - const newInputs = Object.fromEntries( - Object.entries(inputs).map(([name, value]: [string, string]) => - unserialize(name, value) - ) - ) - actions.reset() - editing === forms.Inputs - ? actions.setInputs(newInputs) - : actions.setSettings(newInputs) - setEditing(null) - } - - const toggle = (form: 'inputs' | 'settings') => { - if (editing === null) { - setEditing(form) - } else { - setEditing(null) - } - const editableInputs = Object.fromEntries( - Object.entries( - form === forms.Inputs ? models.inputs : models.settings - ).map(([name, value]) => - form === forms.Inputs - ? serialize(name, value) - : [name, JSON.stringify(value)] - ) - ) - setInputs(editableInputs) - } - return ( - -
- {editing !== null && ( - - {Object.entries(inputs).map(([name, value]) => ( - - setInputs({ ...inputs, [name]: evt.target.value }) - } - /> - ))} - - )}
) } diff --git a/lib/makeAlgorithmPage.tsx b/lib/defineAlgorithm.tsx similarity index 58% rename from lib/makeAlgorithmPage.tsx rename to lib/defineAlgorithm.tsx index a84d017..6d3d980 100644 --- a/lib/makeAlgorithmPage.tsx +++ b/lib/defineAlgorithm.tsx @@ -2,25 +2,16 @@ import React from 'react' import { Algorithm } from '~components/Algorithm' import { AlgorithmPage } from '~components/AlgorithmPage' -import { RecordableFunction, Params } from '~lib/types' -type AlgorithmOptions = { - title: string - description: string - pattern: string - algorithm: T - inputs: Params -} - -export function makeAlgorithmPage( - options: AlgorithmOptions, +export default function defineAlgorithm( + options: any, Component: (props: unknown) => JSX.Element ) { const { title, pattern, description, algorithm, inputs } = options const Page = function () { return ( - + diff --git a/lib/snapshot.macro.d.ts b/lib/snapshot.macro.d.ts new file mode 100644 index 0000000..c055002 --- /dev/null +++ b/lib/snapshot.macro.d.ts @@ -0,0 +1,3 @@ +export default function snapshot any>( + algo: T +): (snapshots) => (...args: Parameters) => any diff --git a/lib/snapshot.macro.js b/lib/snapshot.macro.js new file mode 100644 index 0000000..1d8f2e9 --- /dev/null +++ b/lib/snapshot.macro.js @@ -0,0 +1,114 @@ +const { createMacro } = require('babel-plugin-macros') + +module.exports = createMacro(snapshot) + +function snapshot({ references, babel: { types: t } }) { + if (!references.default) { + return + } + references.default.forEach((path) => { + const func = path.parentPath.node.arguments[0] + path.parentPath.traverse( + { + FunctionDeclaration(path) { + getFunctionParams(t, path.node).forEach((name) => + this.declared.add(name) + ) + }, + ArrowFunctionExpression(path) { + getFunctionParams(t, path.node).forEach((name) => + this.declared.add(name) + ) + }, + FunctionExpression(path) { + getFunctionParams(t, path.node).forEach((name) => + this.declared.add(name) + ) + }, + DebuggerStatement(path) { + const scope = Object.keys( + path.scope.getAllBindings() + ).filter((name) => this.declared.has(name)) + path.replaceWith( + createSnapshot(t, [ + ...scope, + { line: String(path.node.loc?.start.line) }, + ]) + ) + }, + VariableDeclarator(path) { + const names = getNames(t, path.node.id) + names.forEach((name) => this.declared.add(name)) + }, + }, + { declared: new Set() } + ) + const params = getFunctionParams(t, func) + path.parentPath.replaceWith( + t.objectExpression([ + t.objectProperty( + t.identifier('entryPoint'), + t.arrowFunctionExpression([t.identifier('__snapshots')], func) + ), + t.objectProperty( + t.identifier('params'), + t.stringLiteral(JSON.stringify(params)) + ), + ]) + ) + }) +} + +// -- + +const SNAPSHOT = '__snapshots' + +function getFunctionParams(t, func) { + return func.params.flatMap((node) => getNames(t, node)) +} + +function getNames(t, node) { + if (t.isIdentifier(node)) { + return [node.name] + } + if (t.isArrayPattern(node)) { + return node.elements.flatMap((node) => + node === null ? [] : getNames(t, node) + ) + } + if (t.isObjectPattern(node)) { + return node.properties.flatMap((prop) => getNames(t, prop)) + } + if (t.isObjectProperty(node)) { + return getNames(t, node.key) + } + return [] +} + +function createSnapshot(t, scope) { + const parsedScope = scope + .filter((scope) => (typeof scope === 'string' ? scope !== SNAPSHOT : scope)) + .map((stringOrVal) => { + if (typeof stringOrVal === 'string') { + return [stringOrVal, stringOrVal] + } else { + const [key, val] = Object.entries(stringOrVal)[0] + return [key, val] + } + }) + /* __snapshot.push({ ...scope }) */ + return t.expressionStatement( + t.callExpression( + t.memberExpression(t.identifier(SNAPSHOT), t.identifier('push')), + [createObjectExpression(t, parsedScope)] + ) + ) +} + +function createObjectExpression(t, entries) { + return t.objectExpression( + entries.map(([key, val]) => + t.objectProperty(t.identifier(key), t.identifier(val)) + ) + ) +} diff --git a/lib/snapshot.ts b/lib/snapshot.ts new file mode 100644 index 0000000..b008c66 --- /dev/null +++ b/lib/snapshot.ts @@ -0,0 +1,22 @@ +import rfdc from 'rfdc' + +export type Snapshot = { + data: Partial[] + push(val: Partial): void +} + +const clone = rfdc() + +const snapshot = { + createSnapshot() { + const data = [] + return { + data, + push(snapshot) { + data.push(clone(snapshot)) + }, + } + }, +} + +export default snapshot diff --git a/lib/types.ts b/lib/types.ts index 9743fbe..ab15bc5 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -1,26 +1,3 @@ -export type Recorder = { - record: (data: Partial) => void -} - -export type RecordableFunction< - Params extends Record = Record, - State = unknown -> = (recorder: Recorder, args: Params) => unknown - -export type Params = Func extends RecordableFunction - ? Params - : Record - -export type DisplayProps = T extends RecordableFunction< - infer Params, - infer State -> - ? { - state: Partial - inputs: Params - } - : unknown - export type Settings = { /** * Determines the running speed of the animation. This number is passed directly @@ -53,10 +30,7 @@ export type Snapshot = { settings: Settings } -export type AlgorithmContext< - Parameters = Record, - State = unknown -> = { +export type AlgorithmContext = { models: Snapshot actions: { /** diff --git a/lib/useAlgorithm.ts b/lib/useAlgorithm.ts index ec27c15..1b46732 100644 --- a/lib/useAlgorithm.ts +++ b/lib/useAlgorithm.ts @@ -1,22 +1,17 @@ import React from 'react' import useInterval from '@use-it/interval' -import { AlgorithmContext, Settings } from '~lib/types' +import { AlgorithmContext, Settings } from './types' +import snapshot from './snapshot' /** * Given an algorithm and arguments, this hook runs the algorithm with the given * arguments and returns a series of algorithm "states" and animation controls. */ -export function useAlgorithm< - Parameters extends Record, - State = unknown ->( - algorithm: ( - { record }: { record: (data: State) => void }, - args: Parameters - ) => unknown, - initialArguments: Parameters -): AlgorithmContext { +export function useAlgorithm( + algorithm: any, + initialArguments: any[] +): AlgorithmContext { const [activeStepIndex, setActiveStepIndex] = React.useState(0) const [isPlaying, setIsPlaying] = React.useState(false) const [inputs, setInputs] = React.useState(initialArguments) @@ -25,9 +20,15 @@ export function useAlgorithm< }) const steps = React.useMemo(() => { - const recordedSteps: State[] = [] - algorithm({ record: (data) => recordedSteps.push(data) }, inputs) - return recordedSteps + const snapshots = snapshot.createSnapshot() + algorithm(snapshots)(...inputs) + + const last = snapshots.data[snapshots.data.length - 1] + if (last) { + last.__done = true + } + + return snapshots.data }, [inputs, algorithm]) useInterval( diff --git a/migration.md b/migration.md new file mode 100644 index 0000000..a8c6f66 --- /dev/null +++ b/migration.md @@ -0,0 +1,13 @@ +# Refactor Steps + +1. Create a macro using [babel-plugin-macros](https://github.com/kentcdodds/babel-plugin-macros) to convert functions to an object containing: + +``` +{ + entryPoint: instrumented algorithm + params: list of function parameters +} +``` + +2. Update the `makeAlgorithmPage` util to use this macro for the selected algorithm +3. Somehow make all of this work with typescript diff --git a/package.json b/package.json index 6c4f453..b40063b 100644 --- a/package.json +++ b/package.json @@ -20,14 +20,17 @@ "react-icons": "^3.11.0", "remark": "^12.0.0", "remark-html": "^11.0.1", + "rfdc": "^1.1.4", "sass": "^1.29.0", "uuid": "^8.3.0" }, "devDependencies": { "@types/node": "^14.14.10", "@types/react": "^17.0.0", + "@types/rfdc": "^1.1.0", "@typescript-eslint/eslint-plugin": "^4.9.0", "@typescript-eslint/parser": "^4.9.0", + "babel-plugin-macros": "^3.0.1", "eslint": "^7.8.1", "eslint-config-prettier": "^6.11.0", "eslint-plugin-jsx-a11y": "^6.3.1", diff --git a/pages/patterns/sliding-window/find-all-averages.tsx b/pages/patterns/sliding-window/find-all-averages.tsx index bd8b530..29b7307 100644 --- a/pages/patterns/sliding-window/find-all-averages.tsx +++ b/pages/patterns/sliding-window/find-all-averages.tsx @@ -2,99 +2,63 @@ import React from 'react' import { Iterable, IterableItem } from '~components/Iterable' import { Window } from '~components/Window' -import { makeAlgorithmPage } from '~lib/makeAlgorithmPage' -import { DisplayProps, RecordableFunction } from '~lib/types' +import defineAlgorithm from '~lib/defineAlgorithm' +import snapshot from '../../../lib/snapshot.macro' -const meta = { - title: 'Find All Averages', - pattern: 'Sliding Window', - description: 'Given an array, find the averages of all subarrays of size k.', -} - -function FindAllAverages({ - state, - inputs, -}: DisplayProps) { - const { done, start, end, result } = state - const { arr, k } = inputs - const isActive = (index: number) => - done ? true : index >= start && index <= end - - return ( - <> -
- - {Array.from(arr).map((item, index) => ( - - {item} - - ))} - - -
- Subarray size: {k} - - Result: {JSON.stringify(result.map(Number), null, 2)} - -
-
- - ) -} - -// -- - -const findAllAverages: RecordableFunction< - { arr: number[]; k: number }, - { - start: number - end: number - sum: number - result: number[] - done: boolean - } -> = ({ record }, { arr, k }) => { - const result = [] - let windowStart = 0 - let windowSum = 0 - for (let windowEnd = 0; windowEnd < arr.length; windowEnd++) { - windowSum += arr[windowEnd] - record({ - start: windowStart, - end: windowEnd, - sum: windowSum, - result: [...result], - }) - - if (windowEnd >= k - 1) { - result.push((windowSum / k).toFixed(2)) - windowSum -= arr[windowStart] - windowStart++ - } - } - - record({ - done: true, - result: [...result], - }) - - return result -} - -export default makeAlgorithmPage( +export default defineAlgorithm( { - algorithm: findAllAverages, - inputs: { - arr: [1, 3, 2, 6, -1, 4, 1, 8, 2], - k: 3, - }, - ...meta, + title: 'Find All Averages', + pattern: 'Sliding Window', + description: + 'Given an array, find the averages of all subarrays of size k.', + inputs: [[1, 3, 2, 6, -1, 4, 1, 8, 2], 3], + algorithm: snapshot((arr: number[], k: number) => { + const result = [] + let windowStart = 0 + let windowSum = 0 + for (let windowEnd = 0; windowEnd < arr.length; windowEnd++) { + windowSum += arr[windowEnd] + debugger + if (windowEnd >= k - 1) { + result.push((windowSum / k).toFixed(2)) + windowSum -= arr[windowStart] + windowStart++ + } + } + debugger + return result + }), }, - FindAllAverages + function FindAllAverages({ state, inputs }) { + const { __done: done, windowStart, windowEnd, result } = state + const [arr, k] = inputs + const isActive = (index: number) => + done ? true : index >= windowStart && index <= windowEnd + return ( + <> +
+ + {Array.from(arr).map((item, index) => ( + + {item} + + ))} + + +
+ Subarray size: {k} + + Result: {JSON.stringify(result.map(Number), null, 2)} + +
+
+ + ) + } ) diff --git a/yarn.lock b/yarn.lock index 52e347d..df29a67 100644 --- a/yarn.lock +++ b/yarn.lock @@ -980,6 +980,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.12.5": + version "7.12.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e" + integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@^7.10.4", "@babel/template@^7.7.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" @@ -1120,6 +1127,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.10.tgz#5958a82e41863cfc71f2307b3748e3491ba03785" integrity sha512-J32dgx2hw8vXrSbu4ZlVhn1Nm3GbeCFNw2FWL8S5QKucHGY0cyNwjdQdO+KMBZ4wpmC7KhLCiNsdk1RFRIYUQQ== +"@types/parse-json@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" + integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== + "@types/prop-types@*": version "15.7.3" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" @@ -1133,6 +1145,11 @@ "@types/prop-types" "*" csstype "^3.0.2" +"@types/rfdc@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@types/rfdc/-/rfdc-1.1.0.tgz#1fc5ffdc679575e2ca31399d4ee75f353afdd37b" + integrity sha512-Ez0Pc0H6m8C2L3Wif9SR5YlJTB/UnZIq0N9G/dPB2fmGo42oLo95o73hHHtoGvUucMD4OdlquscflSuKCZE8qA== + "@types/unist@^2.0.0", "@types/unist@^2.0.2": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e" @@ -1662,6 +1679,15 @@ babel-plugin-dynamic-import-node@^2.3.3: dependencies: object.assign "^4.1.0" +babel-plugin-macros@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.0.1.tgz#0d412d68f5b3d1b64358f24ab099bd148724e2a9" + integrity sha512-CKt4+Oy9k2wiN+hT1uZzOw7d8zb1anbQpf7KLwaaXRCi/4pzKdFKHf7v5mvoPmjkmxshh7eKZQuRop06r5WP4w== + dependencies: + "@babel/runtime" "^7.12.5" + cosmiconfig "^7.0.0" + resolve "^1.19.0" + babel-plugin-syntax-jsx@6.18.0: version "6.18.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" @@ -2317,6 +2343,17 @@ core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= +cosmiconfig@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.0.tgz#ef9b44d773959cae63ddecd122de23853b60f8d3" + integrity sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + create-ecdh@^4.0.0: version "4.0.4" resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" @@ -2770,6 +2807,13 @@ errno@^0.1.3, errno@~0.1.7: dependencies: prr "~1.0.1" +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.5: version "1.17.6" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a" @@ -3691,6 +3735,11 @@ is-alphanumerical@^1.0.0: is-alphabetical "^1.0.0" is-decimal "^1.0.0" +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + is-arrayish@^0.3.1: version "0.3.2" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" @@ -3725,6 +3774,13 @@ is-callable@^1.1.4, is-callable@^1.2.0: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb" integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw== +is-core-module@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a" + integrity sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ== + dependencies: + has "^1.0.3" + is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -3934,6 +3990,11 @@ json-parse-better-errors@^1.0.2: resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" @@ -4029,6 +4090,11 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +lines-and-columns@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" + integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= + loader-runner@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" @@ -4787,6 +4853,16 @@ parse-entities@^2.0.0: is-decimal "^1.0.0" is-hexadecimal "^1.0.0" +parse-json@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.1.0.tgz#f96088cdf24a8faa9aea9a009f2d9d942c999646" + integrity sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + pascalcase@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" @@ -5723,6 +5799,14 @@ resolve@^1.14.2, resolve@^1.17.0, resolve@^1.3.2, resolve@^1.8.1: dependencies: path-parse "^1.0.6" +resolve@^1.19.0: + version "1.19.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c" + integrity sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg== + dependencies: + is-core-module "^2.1.0" + path-parse "^1.0.6" + ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" @@ -5746,6 +5830,11 @@ rework@1.0.1: convert-source-map "^0.3.3" css "^2.0.0" +rfdc@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.1.4.tgz#ba72cc1367a0ccd9cf81a870b3b58bd3ad07f8c2" + integrity sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug== + rimraf@2.6.3: version "2.6.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" @@ -6885,3 +6974,8 @@ yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yaml@^1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e" + integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg== From 37319ad21779028423375cb6e73b2d96603ac7ed Mon Sep 17 00:00:00 2001 From: Narendra Syahrasyad Date: Wed, 23 Dec 2020 23:21:07 -0800 Subject: [PATCH 03/15] feat: convert all pages to use debugger statements --- components/Iterable.tsx | 2 +- lib/useAlgorithm.ts | 8 +- package.json | 3 +- .../sliding-window/non-repeat-substring.js | 113 --------------- .../sliding-window/non-repeat-substring.tsx | 94 +++++++++++++ .../patterns/two-pointers/closest-triplet.js | 110 --------------- .../patterns/two-pointers/closest-triplet.tsx | 101 ++++++++++++++ .../patterns/two-pointers/dutch-flag-sort.js | 86 ------------ .../patterns/two-pointers/dutch-flag-sort.tsx | 82 +++++++++++ .../{pair-sum.js => pair-sum.tsx} | 102 ++++++-------- ...ve-duplicates.js => remove-duplicates.tsx} | 74 ++++------ .../{sorted-squares.js => sorted-squares.tsx} | 106 ++++++-------- .../two-pointers/triplet-sum-to-zero.js | 132 ------------------ .../two-pointers/triplet-sum-to-zero.tsx | 104 ++++++++++++++ utils/helpers.js | 9 -- utils/helpers.ts | 14 ++ yarn.lock | 13 +- 17 files changed, 530 insertions(+), 623 deletions(-) delete mode 100644 pages/patterns/sliding-window/non-repeat-substring.js create mode 100644 pages/patterns/sliding-window/non-repeat-substring.tsx delete mode 100644 pages/patterns/two-pointers/closest-triplet.js create mode 100644 pages/patterns/two-pointers/closest-triplet.tsx delete mode 100644 pages/patterns/two-pointers/dutch-flag-sort.js create mode 100644 pages/patterns/two-pointers/dutch-flag-sort.tsx rename pages/patterns/two-pointers/{pair-sum.js => pair-sum.tsx} (55%) rename pages/patterns/two-pointers/{remove-duplicates.js => remove-duplicates.tsx} (60%) rename pages/patterns/two-pointers/{sorted-squares.js => sorted-squares.tsx} (64%) delete mode 100644 pages/patterns/two-pointers/triplet-sum-to-zero.js create mode 100644 pages/patterns/two-pointers/triplet-sum-to-zero.tsx delete mode 100644 utils/helpers.js create mode 100644 utils/helpers.ts diff --git a/components/Iterable.tsx b/components/Iterable.tsx index 6f453bd..98d3a68 100644 --- a/components/Iterable.tsx +++ b/components/Iterable.tsx @@ -24,7 +24,7 @@ export function Iterable({ children }) { } type IterableItemProps = { - className: ClassValue + className?: ClassValue active?: boolean pointer?: boolean } & Omit, 'className'> diff --git a/lib/useAlgorithm.ts b/lib/useAlgorithm.ts index 1b46732..76aeb70 100644 --- a/lib/useAlgorithm.ts +++ b/lib/useAlgorithm.ts @@ -1,9 +1,12 @@ import React from 'react' import useInterval from '@use-it/interval' +import rfdc from 'rfdc' import { AlgorithmContext, Settings } from './types' import snapshot from './snapshot' +const clone = rfdc() + /** * Given an algorithm and arguments, this hook runs the algorithm with the given * arguments and returns a series of algorithm "states" and animation controls. @@ -14,18 +17,19 @@ export function useAlgorithm( ): AlgorithmContext { const [activeStepIndex, setActiveStepIndex] = React.useState(0) const [isPlaying, setIsPlaying] = React.useState(false) - const [inputs, setInputs] = React.useState(initialArguments) + const [inputs, setInputs] = React.useState(clone(initialArguments)) const [settings, setSettings] = React.useState({ delay: 500, }) const steps = React.useMemo(() => { const snapshots = snapshot.createSnapshot() - algorithm(snapshots)(...inputs) + const returnVal = algorithm(snapshots)(...inputs) const last = snapshots.data[snapshots.data.length - 1] if (last) { last.__done = true + last.__returnValue = returnVal } return snapshots.data diff --git a/package.json b/package.json index b40063b..b45b58f 100644 --- a/package.json +++ b/package.json @@ -22,12 +22,13 @@ "remark-html": "^11.0.1", "rfdc": "^1.1.4", "sass": "^1.29.0", - "uuid": "^8.3.0" + "uuid": "^8.3.2" }, "devDependencies": { "@types/node": "^14.14.10", "@types/react": "^17.0.0", "@types/rfdc": "^1.1.0", + "@types/uuid": "^8.3.0", "@typescript-eslint/eslint-plugin": "^4.9.0", "@typescript-eslint/parser": "^4.9.0", "babel-plugin-macros": "^3.0.1", diff --git a/pages/patterns/sliding-window/non-repeat-substring.js b/pages/patterns/sliding-window/non-repeat-substring.js deleted file mode 100644 index 481a1e9..0000000 --- a/pages/patterns/sliding-window/non-repeat-substring.js +++ /dev/null @@ -1,113 +0,0 @@ -import React from 'react' - -import { Iterable, IterableItem } from '~components/Iterable' -import { makeAlgorithmPage } from '~lib/makeAlgorithmPage' -import { Window } from '~components/Window' - -function NonRepeatSubstring({ state, inputs }) { - const { done, start, end, maxStr } = state - const { str } = inputs - - const isActive = (index) => - inResult(index) || (index >= start && index <= end) - const inResult = (index) => { - if (done) { - const [head, tail] = maxStr - return index >= head && index <= tail - } - return false - } - - return ( - <> -
- - {Array.from(str).map((item, index) => ( - - {item} - - ))} - - -
-
- Max size: {maxStr[1] - maxStr[0] + 1} -
- - ) -} - -export default makeAlgorithmPage( - { - title: 'Non-Repeat Substring', - pattern: 'Sliding Window', - description: - 'Given a string, find the longest substring with no repeating characters.', - algorithm: nonRepeatSubstring, - inputs: { - str: 'aabccbb', - }, - }, - NonRepeatSubstring -) - -// -- - -function nonRepeatSubstring({ record }, { str }) { - const seen = {} - let windowStart = 0 - let windowEnd = 0 - let windowUniqueCount = 0 - - let maxStr = [0, 0] - - while (windowEnd < str.length) { - const char = str[windowEnd] - if (seen[char]) { - seen[char]++ - } else { - seen[char] = 1 - windowUniqueCount++ - } - - record({ - start: windowStart, - end: windowEnd, - maxStr, - }) - - while (windowUniqueCount !== windowEnd - windowStart + 1) { - const startChar = str[windowStart] - seen[startChar]-- - if (seen[startChar] === 0) { - windowUniqueCount-- - } - windowStart++ - - record({ - start: windowStart, - end: windowEnd, - maxStr, - }) - } - - const [head, tail] = maxStr - if (tail - head < windowEnd - windowStart) { - maxStr = [windowStart, windowEnd] - } - windowEnd++ - } - - record({ - done: true, - maxStr, - }) - - return maxStr -} diff --git a/pages/patterns/sliding-window/non-repeat-substring.tsx b/pages/patterns/sliding-window/non-repeat-substring.tsx new file mode 100644 index 0000000..bf26e2e --- /dev/null +++ b/pages/patterns/sliding-window/non-repeat-substring.tsx @@ -0,0 +1,94 @@ +import React from 'react' + +import { Iterable, IterableItem } from '~components/Iterable' +import { Window } from '~components/Window' +import defineAlgorithm from '~lib/defineAlgorithm' +import snapshot from '../../../lib/snapshot.macro' + +export default defineAlgorithm( + { + title: 'Non-Repeat Substring', + pattern: 'Sliding Window', + description: + 'Given a string, find the longest substring with no repeating characters.', + inputs: ['aabccbb'], + algorithm: snapshot((str: string) => { + const seen = {} + let windowStart = 0 + let windowEnd = 0 + let windowUniqueCount = 0 + + let maxStr = [0, 0] + + while (windowEnd < str.length) { + const char = str[windowEnd] + if (seen[char]) { + seen[char]++ + } else { + seen[char] = 1 + windowUniqueCount++ + } + debugger + + while (windowUniqueCount !== windowEnd - windowStart + 1) { + const startChar = str[windowStart] + seen[startChar]-- + if (seen[startChar] === 0) { + windowUniqueCount-- + } + windowStart++ + debugger + } + + const [head, tail] = maxStr + if (tail - head < windowEnd - windowStart) { + maxStr = [windowStart, windowEnd] + } + windowEnd++ + } + + debugger + return maxStr + }), + }, + NonRepeatSubstring +) + +function NonRepeatSubstring({ state, inputs }) { + const { __done: done, windowStart, windowEnd, maxStr } = state + const [str] = inputs + + const isActive = (index) => + done ? inResult(index) : index >= windowStart && index <= windowEnd + const inResult = (index) => { + if (done) { + const [head, tail] = maxStr + return index >= head && index <= tail + } + return false + } + + return ( + <> +
+ + {Array.from(str).map((item, index) => ( + + {item} + + ))} + + +
+
+ Max size: {maxStr[1] - maxStr[0] + 1} +
+ + ) +} diff --git a/pages/patterns/two-pointers/closest-triplet.js b/pages/patterns/two-pointers/closest-triplet.js deleted file mode 100644 index f3d559a..0000000 --- a/pages/patterns/two-pointers/closest-triplet.js +++ /dev/null @@ -1,110 +0,0 @@ -import React from 'react' - -import { Iterable, IterableItem } from '~components/Iterable' -import { makeAlgorithmPage } from '~lib/makeAlgorithmPage' -import { addIds } from '~utils/helpers' - -function ClosestTriples({ state, inputs }) { - const { done, active, curr, head, tail, triple, minDiff, currDiff } = state - - const isActive = (index) => - done ? triple.includes(index) : active || [curr, head, tail].includes(index) - const showPointer = (index) => !done && !active && isActive(index) - - return ( - <> -
- - {state.input.map((item, index) => ( - - {item.val} - - ))} - -
-
- Target: {inputs.target} - Min diff: {minDiff} - Current diff: {currDiff} -
- - ) -} - -export default makeAlgorithmPage( - { - title: 'Closest Triples', - pattern: 'Two Pointers', - description: - 'Given an array and a target number, find the triple whose sum is closest to the given target.', - algorithm: findClosestTriples, - inputs: { - arr: [1, -3, -1, 2], - target: 1, - }, - }, - ClosestTriples -) - -// -- - -export function findClosestTriples({ record }, { arr, target }) { - const nums = addIds(arr) - const recordWithNums = (data) => record({ input: [...nums], ...data }) - - recordWithNums({ active: true }) - nums.sort((a, b) => a.val - b.val) - recordWithNums({ active: true }) - - let minDiff = Number.POSITIVE_INFINITY - let minTriple = null - for (let i = 0; i < nums.length - 2; i++) { - const curr = nums[i].val - - recordWithNums({ curr: i, minDiff }) - - let head = i + 1 - let tail = nums.length - 1 - - while (head < tail) { - const diff = target - (curr + nums[head].val + nums[tail].val) - - recordWithNums({ curr: i, head, tail, minDiff, currDiff: diff }) - - if (diff === 0) { - recordWithNums({ - done: true, - triple: [i, head, tail], - minDiff: 0, - currDiff: diff, - }) - return target - diff - } - - if (Math.abs(minDiff) > Math.abs(diff)) { - minDiff = diff - minTriple = [i, head, tail] - } - - // If equal, prefer the positive diff because triple sum is smaller - if (Math.abs(minDiff) === Math.abs(diff) && Math.sign(diff) > 0) { - minDiff = diff - minTriple = [i, head, tail] - } - - if (diff > 0) { - head++ - } else { - tail-- - } - } - } - - recordWithNums({ done: true, triple: minTriple, minDiff, currDiff: minDiff }) - return target - minDiff -} diff --git a/pages/patterns/two-pointers/closest-triplet.tsx b/pages/patterns/two-pointers/closest-triplet.tsx new file mode 100644 index 0000000..178c78f --- /dev/null +++ b/pages/patterns/two-pointers/closest-triplet.tsx @@ -0,0 +1,101 @@ +import React from 'react' + +import { Iterable, IterableItem } from '~components/Iterable' +import defineAlgorithm from '~lib/defineAlgorithm' +import { addIds } from '~utils/helpers' +import snapshot from '../../../lib/snapshot.macro' + +export default defineAlgorithm( + { + title: 'Closest Triples', + pattern: 'Two Pointers', + description: + 'Given an array and a target number, find the triple whose sum is closest to the given target.', + algorithm: snapshot((arr: number[], target: number) => { + const nums = addIds(arr) + + debugger + nums.sort((a, b) => a.val - b.val) + debugger + + let minDiff = Number.POSITIVE_INFINITY + let minTriple = null + for (let i = 0; i < nums.length - 2; i++) { + const curr = nums[i].val + + debugger + + let head = i + 1 + let tail = nums.length - 1 + + while (head < tail) { + const diff = target - (curr + nums[head].val + nums[tail].val) + + debugger + + if (diff === 0) { + debugger + return target - diff + } + + if (Math.abs(minDiff) > Math.abs(diff)) { + minDiff = diff + minTriple = [i, head, tail] + } + + // If equal, prefer the positive diff because triple sum is smaller + if (Math.abs(minDiff) === Math.abs(diff) && Math.sign(diff) > 0) { + minDiff = diff + minTriple = [i, head, tail] + } + + if (diff > 0) { + head++ + } else { + tail-- + } + } + } + + debugger + return target - minDiff + }), + inputs: [[1, -3, -1, 2], 1], + }, + ClosestTriples +) + +function ClosestTriples({ state, inputs }) { + const { __done: done, i, head, tail, minTriple, minDiff, diff } = state + + const active = !done && i === undefined + const isActive = (index: number) => + done ? minTriple.includes(index) : active || [i, head, tail].includes(index) + const showPointer = (index: number) => !done && !active && isActive(index) + + return ( + <> +
+ + {state.nums.map( + (item: { id: string; val: number }, index: number) => ( + + {item.val} + + ) + )} + +
+
+ Target: {inputs.target} + Min diff: {minDiff} + Current diff: {diff} +
+ + ) +} diff --git a/pages/patterns/two-pointers/dutch-flag-sort.js b/pages/patterns/two-pointers/dutch-flag-sort.js deleted file mode 100644 index 8320eec..0000000 --- a/pages/patterns/two-pointers/dutch-flag-sort.js +++ /dev/null @@ -1,86 +0,0 @@ -import React from 'react' - -import { Iterable, IterableItem } from '~components/Iterable' -import { makeAlgorithmPage } from '~lib/makeAlgorithmPage' -import { addIds } from '~utils/helpers' - -function DutchFlagSort({ state }) { - const { done, input, curr, high, low } = state - const isActive = (index) => done || [curr, high, low].includes(index) - return ( - <> -
- - {input.map((item, index) => ( - - {item.val} - - ))} - -
- {!done && ( -
- curr: {curr} - high: {high} - low: {low} -
- )} - - ) -} - -export default makeAlgorithmPage( - { - title: 'Dutch Flag Sort', - pattern: 'Two Pointers', - description: - 'Given an array of only 0s, 1s, and 2s, sort the array in linear time.', - algorithm: dutchFlagSort, - inputs: { arr: [2, 2, 0, 1, 2, 0] }, - }, - DutchFlagSort -) - -// -- - -function dutchFlagSort({ record }, { arr }) { - const nums = addIds(arr) - const recordWithNums = (data) => record({ input: [...nums], ...data }) - - let low = 0 - let high = arr.length - 1 - - let curr = 0 - while (curr <= high) { - const num = nums[curr].val - recordWithNums({ curr, high, low }) - if (num === 0) { - if (curr !== low) { - swap(nums, curr, low) - recordWithNums({ curr, high, low }) - } - curr++ - low++ - } else if (num === 1) { - curr++ - } else if (num === 2) { - swap(nums, curr, high) - recordWithNums({ curr, high, low }) - high-- - } - } - - recordWithNums({ done: true }) - return nums -} - -const swap = (arr, i, j) => { - const temp = arr[i] - arr[i] = arr[j] - arr[j] = temp -} diff --git a/pages/patterns/two-pointers/dutch-flag-sort.tsx b/pages/patterns/two-pointers/dutch-flag-sort.tsx new file mode 100644 index 0000000..94efba2 --- /dev/null +++ b/pages/patterns/two-pointers/dutch-flag-sort.tsx @@ -0,0 +1,82 @@ +import React from 'react' + +import { Iterable, IterableItem } from '~components/Iterable' +import defineAlgorithm from '~lib/defineAlgorithm' +import { addIds } from '~utils/helpers' +import snapshot from '../../../lib/snapshot.macro' + +export default defineAlgorithm( + { + title: 'Dutch Flag Sort', + pattern: 'Two Pointers', + description: + 'Given an array of only 0s, 1s, and 2s, sort the array in linear time.', + algorithm: snapshot((arr: number[]) => { + function swap(arr: any[], i: number, j: number) { + const temp = arr[i] + arr[i] = arr[j] + arr[j] = temp + } + + const nums = addIds(arr) + + let low = 0 + let high = arr.length - 1 + + let curr = 0 + while (curr <= high) { + const num = nums[curr].val + debugger + if (num === 0) { + if (curr !== low) { + swap(nums, curr, low) + debugger + } + curr++ + low++ + } else if (num === 1) { + curr++ + } else if (num === 2) { + swap(nums, curr, high) + debugger + high-- + } + } + + debugger + return nums + }), + inputs: [[2, 2, 0, 1, 2, 0]], + }, + DutchFlagSort +) + +function DutchFlagSort({ state }) { + const { __done: done, curr, high, low, nums } = state + const isActive = (index: number) => done || [curr, high, low].includes(index) + return ( + <> +
+ + {nums.map((item, index) => ( + + {item.val} + + ))} + +
+ {!done && ( +
+ curr: {curr} + high: {high} + low: {low} +
+ )} + + ) +} diff --git a/pages/patterns/two-pointers/pair-sum.js b/pages/patterns/two-pointers/pair-sum.tsx similarity index 55% rename from pages/patterns/two-pointers/pair-sum.js rename to pages/patterns/two-pointers/pair-sum.tsx index 9a57185..0cfea9f 100644 --- a/pages/patterns/two-pointers/pair-sum.js +++ b/pages/patterns/two-pointers/pair-sum.tsx @@ -1,14 +1,50 @@ import React from 'react' import { Iterable, IterableItem } from '~components/Iterable' -import { makeAlgorithmPage } from '~lib/makeAlgorithmPage' +import defineAlgorithm from '~lib/defineAlgorithm' +import snapshot from '../../../lib/snapshot.macro' + +export default defineAlgorithm( + { + title: 'Pair Sum', + pattern: 'Two Pointers', + description: + 'Given a sorted array and a target, find a pair of numbers that add up to the target.', + algorithm: snapshot((nums: number[], target: number) => { + let head = 0 + let tail = nums.length - 1 + + while (head < tail) { + const headNum = nums[head] + const tailNum = nums[tail] + debugger + + if (headNum + tailNum === target) { + debugger + return [head, tail] + } + + if (headNum + tailNum > target) { + tail-- + } else { + head++ + } + } + + debugger + return null + }), + inputs: [[1, 2, 3, 4, 6], 6], + }, + PairSum +) function PairSum({ state, inputs }) { - const { done, head, tail, result } = state - const { nums, target } = inputs - const isActive = (index) => + const { __done: done, head, tail, __returnValue: result } = state + const [nums, target] = inputs + const isActive = (index: number) => (done && result === null) || index === head || index === tail - const showPointer = (index) => (done ? false : isActive(index)) + const showPointer = (index: number) => (done ? false : isActive(index)) return ( <> @@ -40,59 +76,3 @@ function PairSum({ state, inputs }) { ) } - -export default makeAlgorithmPage( - { - title: 'Pair Sum', - pattern: 'Two Pointers', - description: - 'Given a sorted array and a target, find a pair of numbers that add up to the target.', - algorithm: findPairWithSum, - inputs: { - nums: [1, 2, 3, 4, 6], - target: 6, - }, - }, - PairSum -) - -// -- - -function findPairWithSum({ record }, { nums, target }) { - let head = 0 - let tail = nums.length - 1 - - while (head < tail) { - const headNum = nums[head] - const tailNum = nums[tail] - - record({ - head, - tail, - done: false, - }) - - if (headNum + tailNum === target) { - record({ - head, - tail, - done: true, - result: [head, tail], - }) - return [head, tail] - } - - if (headNum + tailNum > target) { - tail-- - } else { - head++ - } - } - - record({ - done: true, - result: null, - }) - - return null -} diff --git a/pages/patterns/two-pointers/remove-duplicates.js b/pages/patterns/two-pointers/remove-duplicates.tsx similarity index 60% rename from pages/patterns/two-pointers/remove-duplicates.js rename to pages/patterns/two-pointers/remove-duplicates.tsx index 105ca4e..af64646 100644 --- a/pages/patterns/two-pointers/remove-duplicates.js +++ b/pages/patterns/two-pointers/remove-duplicates.tsx @@ -2,18 +2,41 @@ import React from 'react' import { AnimatePresence } from 'framer-motion' import { Iterable, IterableItem } from '~components/Iterable' -import { makeAlgorithmPage } from '~lib/makeAlgorithmPage' +import defineAlgorithm from '~lib/defineAlgorithm' +import snapshot from '../../../lib/snapshot.macro' -function RemoveDuplicates({ state, inputs }) { - const { done, curr, result } = state - const { arr } = inputs - const isActive = (index) => index === curr +export default defineAlgorithm( + { + title: 'Remove Duplicates', + pattern: 'Two Pointers', + description: 'Given a sorted array, remove its duplicates.', + algorithm: snapshot((arr: number[]) => { + const result = [] + + for (let i = 0; i < arr.length; i++) { + if (arr[i] !== arr[i - 1]) { + result.push(arr[i]) + } + debugger + } + debugger + return result + }), + inputs: [[2, 3, 3, 3, 6, 9, 9]], + }, + RemoveDuplicates +) + +function RemoveDuplicates({ state, inputs }) { + const { __done: done, i: curr, result } = state + const [arr] = inputs + const isActive = (index: number) => index === curr return ( <>
- {arr.map((item, index) => ( + {arr.map((item: number, index: number) => ( - {result.map((item, index) => ( + {result.map((item: number, index: number) => ( ) } - -export default makeAlgorithmPage( - { - title: 'Remove Duplicates', - pattern: 'Two Pointers', - description: 'Given a sorted array, remove its duplicates.', - algorithm: removeDuplicates, - inputs: { - arr: [2, 3, 3, 3, 6, 9, 9], - }, - }, - RemoveDuplicates -) - -// -- - -function removeDuplicates({ record }, { arr }) { - const result = [] - - for (let i = 0; i < arr.length; i++) { - if (arr[i] !== arr[i - 1]) { - result.push(arr[i]) - } - record({ - done: false, - curr: i, - result: [...result], - }) - } - - record({ - done: true, - result: [...result], - }) - - return result -} diff --git a/pages/patterns/two-pointers/sorted-squares.js b/pages/patterns/two-pointers/sorted-squares.tsx similarity index 64% rename from pages/patterns/two-pointers/sorted-squares.js rename to pages/patterns/two-pointers/sorted-squares.tsx index 97879a4..f5c05ee 100644 --- a/pages/patterns/two-pointers/sorted-squares.js +++ b/pages/patterns/two-pointers/sorted-squares.tsx @@ -2,12 +2,55 @@ import React from 'react' import { AnimatePresence } from 'framer-motion' import { Iterable, IterableItem } from '~components/Iterable' -import { makeAlgorithmPage } from '~lib/makeAlgorithmPage' +import defineAlgorithm from '~lib/defineAlgorithm' import { addIds } from '~utils/helpers' +import snapshot from '../../../lib/snapshot.macro' + +export default defineAlgorithm( + { + title: 'Sorted Squares', + pattern: 'Two Pointers', + description: + 'Given a sorted array, return a sorted array of each element squared.', + algorithm: snapshot((arr: number[]) => { + const withIds = addIds(arr) + let result = [] + + let head = 0 + let tail = arr.length - 1 + + while (head <= tail) { + const currHead = withIds[head] + const currTail = withIds[tail] + + const headSquare = currHead.val * currHead.val + const tailSquare = currTail.val * currTail.val + + debugger + + if (headSquare < tailSquare) { + result.push({ val: tailSquare, id: currTail.id }) + tail-- + } else { + result.push({ val: headSquare, id: currHead.id }) + head++ + } + } + + debugger + result = result.reverse() + debugger + + return result + }), + inputs: [[-2, -1, 0, 2, 3]], + }, + SortedSquares +) function SortedSquares({ state, inputs }) { - const { done, head, tail, result } = state - const { arr } = inputs + const { __done: done, head, tail, result } = state + const [arr] = inputs const isActive = (index) => index === head || index === tail return ( @@ -48,20 +91,6 @@ function SortedSquares({ state, inputs }) { ) } -export default makeAlgorithmPage( - { - title: 'Sorted Squares', - pattern: 'Two Pointers', - description: - 'Given a sorted array, return a sorted array of each element squared.', - algorithm: sortedSquare, - inputs: { - arr: [-2, -1, 0, 2, 3], - }, - }, - SortedSquares -) - // -- // Uncomment this to use the non-optimal O(nlogn) version @@ -84,46 +113,3 @@ export default makeAlgorithmPage( return squared } */ - -function sortedSquare({ record }, { arr }) { - const withIds = addIds(arr) - const squares = [] - - let head = 0 - let tail = arr.length - 1 - - while (head <= tail) { - const currHead = withIds[head] - const currTail = withIds[tail] - - const headSquare = currHead.val * currHead.val - const tailSquare = currTail.val * currTail.val - - record({ - head, - tail, - result: [...squares], - done: false, - }) - - if (headSquare < tailSquare) { - squares.push({ val: tailSquare, id: currTail.id }) - tail-- - } else { - squares.push({ val: headSquare, id: currHead.id }) - head++ - } - } - - record({ - result: [...squares], - done: false, - }) - const reversed = squares.reverse() - record({ - result: [...squares], - done: true, - }) - - return reversed -} diff --git a/pages/patterns/two-pointers/triplet-sum-to-zero.js b/pages/patterns/two-pointers/triplet-sum-to-zero.js deleted file mode 100644 index e537475..0000000 --- a/pages/patterns/two-pointers/triplet-sum-to-zero.js +++ /dev/null @@ -1,132 +0,0 @@ -import React from 'react' - -import { Iterable, IterableItem } from '~components/Iterable' -import { makeAlgorithmPage } from '~lib/makeAlgorithmPage' -import { addIds } from '~utils/helpers' - -function TripleSumToZero({ state }) { - const { done, active, curr, head, tail, result } = state - - const isActive = (index) => - done || active || [curr, head, tail].includes(index) - const showPointer = (index) => !done && !active && isActive(index) - - return ( - <> -
- - {state.input.map((item, index) => ( - - {item.val} - - ))} - -
- {result && result.length > 0 && ( -
- {JSON.stringify(result, null, 1)} -
- )} - - ) -} - -export default makeAlgorithmPage( - { - title: 'Triplet Sum to Zero', - pattern: 'Two Pointers', - description: 'Given an array, find all triplets that add up to zero.', - algorithm: findTriples, - inputs: { - arr: [-3, 0, 1, 2, -1, 1, -2], - }, - }, - TripleSumToZero -) - -// -- - -export function findTriples({ record }, { arr }) { - const nums = addIds(arr) - - record({ input: [...nums], active: true }) - nums.sort((a, b) => a.val - b.val) - record({ input: nums, active: true }) - - const result = [] - for (let i = 0; i < nums.length - 2; i++) { - const target = nums[i].val - record({ - input: nums, - curr: i, - result: [...result], - }) - if (i > 0 && target === nums[i - 1]) { - continue - } - const pairs = findAllPairsWithSum( - { - record: (data) => - record({ ...data, input: nums, curr: i, result: [...result] }), - }, - { nums, target: -target, head: i + 1 } - ) - if (pairs) { - for (const [head, tail] of pairs) { - result.push([target, nums[head].val, nums[tail].val]) - } - } - } - record({ done: true, input: nums, result: [...result] }) -} - -function findAllPairsWithSum({ record }, { nums, target, head }) { - let tail = nums.length - 1 - const result = [] - - while (head < tail) { - const headNum = nums[head].val - const tailNum = nums[tail].val - - record({ - head, - tail, - done: false, - }) - - if (headNum + tailNum === target) { - result.push([head, tail]) - head++ - tail-- - - while (head < tail && nums[head].val === nums[head - 1].val) { - record({ - head, - tail, - done: false, - }) - head++ - } - - while (head < tail && nums[tail].val === nums[tail + 1].val) { - record({ - head, - tail, - done: false, - }) - tail-- - } - } else if (headNum + tailNum > target) { - tail-- - } else { - head++ - } - } - - return result -} diff --git a/pages/patterns/two-pointers/triplet-sum-to-zero.tsx b/pages/patterns/two-pointers/triplet-sum-to-zero.tsx new file mode 100644 index 0000000..d0419dc --- /dev/null +++ b/pages/patterns/two-pointers/triplet-sum-to-zero.tsx @@ -0,0 +1,104 @@ +import React from 'react' + +import { Iterable, IterableItem } from '~components/Iterable' +import defineAlgorithm from '~lib/defineAlgorithm' +import { addIds } from '~utils/helpers' +import snapshot from '../../../lib/snapshot.macro' + +export default defineAlgorithm( + { + title: 'Triplet Sum to Zero', + pattern: 'Two Pointers', + description: 'Given an array, find all triplets that add up to zero.', + algorithm: snapshot((arr: number[]) => { + const nums = addIds(arr) + + debugger + nums.sort((a, b) => a.val - b.val) + debugger + + const result = [] + for (let i = 0; i < nums.length - 2; i++) { + const target = nums[i].val + debugger + + if (i > 0 && target === nums[i - 1].val) { + continue + } + + const pairs = [] + const currTarget = -target + let head = i + 1 + let tail = nums.length - 1 + + while (head < tail) { + const headNum = nums[head].val + const tailNum = nums[tail].val + + debugger + if (headNum + tailNum === currTarget) { + pairs.push([head, tail]) + head++ + tail-- + + while (head < tail && nums[head].val === nums[head - 1].val) { + debugger + head++ + } + + while (head < tail && nums[tail].val === nums[tail + 1].val) { + debugger + tail-- + } + } else if (headNum + tailNum > currTarget) { + tail-- + } else { + head++ + } + } + + for (const [head, tail] of pairs) { + result.push([target, nums[head].val, nums[tail].val]) + debugger + } + } + debugger + return result + }), + inputs: [[-3, 0, 1, 2, -1, 1, -2]], + }, + TripleSumToZero +) + +function TripleSumToZero({ state }) { + const { __done: done, i: curr, head, tail, result, nums } = state + + const active = !done && curr === undefined + const isActive = (index: number) => + done || active || [curr, head, tail].includes(index) + const showPointer = (index: number) => !done && !active && isActive(index) + + return ( + <> +
+ + {nums.map((item, index) => ( + + {item.val} + + ))} + +
+ {result && result.length > 0 && ( +
+ {JSON.stringify(result, null, 1)} +
+ )} + + ) +} diff --git a/utils/helpers.js b/utils/helpers.js deleted file mode 100644 index 24b75ff..0000000 --- a/utils/helpers.js +++ /dev/null @@ -1,9 +0,0 @@ -import { v4 } from 'uuid' - -export function addIds(arr) { - return arr.map((val) => ({ id: v4(), val })) -} - -export function identity(item) { - return item -} diff --git a/utils/helpers.ts b/utils/helpers.ts new file mode 100644 index 0000000..efecedc --- /dev/null +++ b/utils/helpers.ts @@ -0,0 +1,14 @@ +import { v4 } from 'uuid' + +type IdWrapper = { + id: string + val: T +} + +export function addIds(arr: T[]): IdWrapper[] { + return arr.map((val) => ({ id: v4(), val })) +} + +export function identity(item: T): T { + return item +} diff --git a/yarn.lock b/yarn.lock index df29a67..a1e088c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1155,6 +1155,11 @@ resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e" integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ== +"@types/uuid@^8.3.0": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.0.tgz#215c231dff736d5ba92410e6d602050cce7e273f" + integrity sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ== + "@typescript-eslint/eslint-plugin@^4.9.0": version "4.9.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.9.0.tgz#8fde15743413661fdc086c9f1f5d74a80b856113" @@ -6803,10 +6808,10 @@ util@^0.11.0: dependencies: inherits "2.0.3" -uuid@^8.3.0: - version "8.3.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.0.tgz#ab738085ca22dc9a8c92725e459b1d507df5d6ea" - integrity sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ== +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== v8-compile-cache@^2.0.3: version "2.1.1" From 3f436c993631bad45b250a17a3eda2b1049c9f48 Mon Sep 17 00:00:00 2001 From: Narendra Syahrasyad Date: Thu, 24 Dec 2020 18:00:35 -0800 Subject: [PATCH 04/15] feat: improve triplet-sum-to-zero animation --- pages/patterns/two-pointers/triplet-sum-to-zero.tsx | 10 +++++++--- tailwind.config.js | 1 + utils/helpers.ts | 4 ++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/pages/patterns/two-pointers/triplet-sum-to-zero.tsx b/pages/patterns/two-pointers/triplet-sum-to-zero.tsx index d0419dc..e70cb8a 100644 --- a/pages/patterns/two-pointers/triplet-sum-to-zero.tsx +++ b/pages/patterns/two-pointers/triplet-sum-to-zero.tsx @@ -2,7 +2,7 @@ import React from 'react' import { Iterable, IterableItem } from '~components/Iterable' import defineAlgorithm from '~lib/defineAlgorithm' -import { addIds } from '~utils/helpers' +import { addIds, sum } from '~utils/helpers' import snapshot from '../../../lib/snapshot.macro' export default defineAlgorithm( @@ -59,7 +59,6 @@ export default defineAlgorithm( for (const [head, tail] of pairs) { result.push([target, nums[head].val, nums[tail].val]) - debugger } } debugger @@ -78,6 +77,11 @@ function TripleSumToZero({ state }) { done || active || [curr, head, tail].includes(index) const showPointer = (index: number) => !done && !active && isActive(index) + const activeItems = nums + .filter((_, index) => isActive(index)) + .map((item) => item.val) + const match = activeItems.length === 3 && sum(activeItems) === 0 + return ( <>
@@ -86,7 +90,7 @@ function TripleSumToZero({ state }) { {item.val} diff --git a/tailwind.config.js b/tailwind.config.js index bb4dd10..607d3d2 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -14,6 +14,7 @@ const theme2 = { highlight: '#8bd3dd', secondary: '#fef6e4', tertiary: '#e53170', + ok: '#60D394', } module.exports = { diff --git a/utils/helpers.ts b/utils/helpers.ts index efecedc..d2c8cf7 100644 --- a/utils/helpers.ts +++ b/utils/helpers.ts @@ -12,3 +12,7 @@ export function addIds(arr: T[]): IdWrapper[] { export function identity(item: T): T { return item } + +export function sum(nums: number[]): number { + return nums.reduce((currSum, num) => currSum + num, 0) +} From a4c365feb5a8a1f8ade29a57b61b9457e353003f Mon Sep 17 00:00:00 2001 From: Narendra Syahrasyad Date: Fri, 25 Dec 2020 00:38:55 -0800 Subject: [PATCH 05/15] feat: migrate to new style --- components/Algorithm.tsx | 192 +++++++++++------- components/AlgorithmPage.js | 18 -- components/Button.js | 4 +- components/Iterable.tsx | 2 +- components/Window.js | 2 +- lib/defineAlgorithm.tsx | 15 +- lib/snapshot.macro.js | 8 +- pages/_app.js | 8 +- pages/index.js | 26 --- .../sliding-window/find-all-averages.tsx | 3 + .../sliding-window/non-repeat-substring.tsx | 7 +- .../patterns/two-pointers/closest-triplet.tsx | 7 +- .../patterns/two-pointers/dutch-flag-sort.tsx | 5 +- .../two-pointers/remove-duplicates.tsx | 1 + .../patterns/two-pointers/sorted-squares.tsx | 1 - .../two-pointers/triplet-sum-to-zero.tsx | 6 +- styles/global.css | 10 +- tailwind.config.js | 9 +- 18 files changed, 155 insertions(+), 169 deletions(-) delete mode 100644 components/AlgorithmPage.js delete mode 100644 pages/index.js diff --git a/components/Algorithm.tsx b/components/Algorithm.tsx index c220ece..f57867a 100644 --- a/components/Algorithm.tsx +++ b/components/Algorithm.tsx @@ -1,6 +1,6 @@ import React from 'react' import clsx, { ClassValue } from 'clsx' -import { motion, AnimateSharedLayout } from 'framer-motion' +import { motion, AnimateSharedLayout, AnimatePresence } from 'framer-motion' import { BsFillPlayFill, BsPauseFill } from 'react-icons/bs' import { FaUndoAlt } from 'react-icons/fa' import { BiLeftArrowAlt, BiRightArrowAlt } from 'react-icons/bi' @@ -11,15 +11,100 @@ import { AlgorithmContext } from '~lib/types' import styles from './styles/Algorithm.module.scss' -export function Algorithm({ children, algorithm, inputs, ...opts }) { - const context = useAlgorithm(algorithm, inputs) +export function Algorithm({ + children, + algorithm, + inputs, + title, + pattern, + description, +}) { + const [showCode, toggleCode] = React.useReducer((show) => !show, false) + + const { entryPoint, params, code } = algorithm + const context = useAlgorithm(entryPoint, inputs) return ( - - - - {React.cloneElement(React.Children.only(children), context.models)} - - +
+
+
+

{pattern}

+

+ {title} +

+

{description}

+
+ {JSON.parse(params).map((paramName, index) => ( + + ))} + +
+
+ + + {showCode && ( + + +
+                
+                {code}
+              
+
+ )} +
+
+
+ + + {React.cloneElement(React.Children.only(children), context.models)} + + + +
+
) } @@ -28,81 +113,32 @@ export function Algorithm({ children, algorithm, inputs, ...opts }) { type ControlsProps = { context: AlgorithmContext className?: ClassValue + style: any } function Controls({ context: { actions, models }, className = '', + style, }: ControlsProps) { return ( - - - - -
- -

- {models.steps.indexOf(models.state) + 1} / {models.steps.length} -

- -
-
-
+
+ + + + +
) } - -// --- - -function Display({ children, className = 'mt-4' }) { - return ( - - {children} - - ) -} - -// --- - -Algorithm.Controls = Controls -Algorithm.Display = Display diff --git a/components/AlgorithmPage.js b/components/AlgorithmPage.js deleted file mode 100644 index 2933763..0000000 --- a/components/AlgorithmPage.js +++ /dev/null @@ -1,18 +0,0 @@ -import Head from 'next/head' -import React from 'react' - -export function AlgorithmPage({ title, pattern, description, children }) { - return ( - <> - - {title} - -
-

{pattern}

-

{title}

-

{description}

-
- {children} - - ) -} diff --git a/components/Button.js b/components/Button.js index ac9cb4a..a8057cc 100644 --- a/components/Button.js +++ b/components/Button.js @@ -2,11 +2,11 @@ import React from 'react' import { motion } from 'framer-motion' import clsx from 'clsx' -export function Button({ onClick, children, className, ...props }) { +export function Button({ onClick, children, className = '', ...props }) { return ( )} diff --git a/lib/defineAlgorithm.tsx b/lib/defineAlgorithm.tsx index 6d3d980..48ce0c3 100644 --- a/lib/defineAlgorithm.tsx +++ b/lib/defineAlgorithm.tsx @@ -1,7 +1,6 @@ import React from 'react' import { Algorithm } from '~components/Algorithm' -import { AlgorithmPage } from '~components/AlgorithmPage' export default function defineAlgorithm( options: any, @@ -10,11 +9,15 @@ export default function defineAlgorithm( const { title, pattern, description, algorithm, inputs } = options const Page = function () { return ( - - - - - + + + ) } Page.displayName = `${Component.name}Page` diff --git a/lib/snapshot.macro.js b/lib/snapshot.macro.js index 1d8f2e9..d16c3e1 100644 --- a/lib/snapshot.macro.js +++ b/lib/snapshot.macro.js @@ -1,4 +1,5 @@ const { createMacro } = require('babel-plugin-macros') +const { default: generate } = require('@babel/generator') module.exports = createMacro(snapshot) @@ -7,7 +8,11 @@ function snapshot({ references, babel: { types: t } }) { return } references.default.forEach((path) => { + const { loc } = path.parentPath.node + const func = path.parentPath.node.arguments[0] + const { start, end } = func + const code = path.hub.file.code.slice(start, end) path.parentPath.traverse( { FunctionDeclaration(path) { @@ -32,7 +37,7 @@ function snapshot({ references, babel: { types: t } }) { path.replaceWith( createSnapshot(t, [ ...scope, - { line: String(path.node.loc?.start.line) }, + { line: String(path.node.loc?.start.line - loc.start.line) }, ]) ) }, @@ -54,6 +59,7 @@ function snapshot({ references, babel: { types: t } }) { t.identifier('params'), t.stringLiteral(JSON.stringify(params)) ), + t.objectProperty(t.identifier('code'), t.stringLiteral(code)), ]) ) }) diff --git a/pages/_app.js b/pages/_app.js index eb29358..7e66efc 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -1,11 +1,5 @@ import '../styles/global.css' -import { Layout } from '../components/Layout' - export default function App({ Component, pageProps }) { - return ( - - - - ) + return } diff --git a/pages/index.js b/pages/index.js deleted file mode 100644 index 354b2d2..0000000 --- a/pages/index.js +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react' -import Link from 'next/link' - -import { Layout } from '~components/Layout' - -function Home() { - return ( - -

Algorithm Patterns

- -
- ) -} - -export default Home diff --git a/pages/patterns/sliding-window/find-all-averages.tsx b/pages/patterns/sliding-window/find-all-averages.tsx index 29b7307..c9b2f0f 100644 --- a/pages/patterns/sliding-window/find-all-averages.tsx +++ b/pages/patterns/sliding-window/find-all-averages.tsx @@ -16,15 +16,18 @@ export default defineAlgorithm( const result = [] let windowStart = 0 let windowSum = 0 + for (let windowEnd = 0; windowEnd < arr.length; windowEnd++) { windowSum += arr[windowEnd] debugger + if (windowEnd >= k - 1) { result.push((windowSum / k).toFixed(2)) windowSum -= arr[windowStart] windowStart++ } } + debugger return result }), diff --git a/pages/patterns/sliding-window/non-repeat-substring.tsx b/pages/patterns/sliding-window/non-repeat-substring.tsx index bf26e2e..29ef0b6 100644 --- a/pages/patterns/sliding-window/non-repeat-substring.tsx +++ b/pages/patterns/sliding-window/non-repeat-substring.tsx @@ -17,33 +17,38 @@ export default defineAlgorithm( let windowStart = 0 let windowEnd = 0 let windowUniqueCount = 0 - let maxStr = [0, 0] while (windowEnd < str.length) { const char = str[windowEnd] + if (seen[char]) { seen[char]++ } else { seen[char] = 1 windowUniqueCount++ } + debugger while (windowUniqueCount !== windowEnd - windowStart + 1) { const startChar = str[windowStart] seen[startChar]-- + if (seen[startChar] === 0) { windowUniqueCount-- } + windowStart++ debugger } const [head, tail] = maxStr + if (tail - head < windowEnd - windowStart) { maxStr = [windowStart, windowEnd] } + windowEnd++ } diff --git a/pages/patterns/two-pointers/closest-triplet.tsx b/pages/patterns/two-pointers/closest-triplet.tsx index 178c78f..ee35576 100644 --- a/pages/patterns/two-pointers/closest-triplet.tsx +++ b/pages/patterns/two-pointers/closest-triplet.tsx @@ -13,24 +13,20 @@ export default defineAlgorithm( 'Given an array and a target number, find the triple whose sum is closest to the given target.', algorithm: snapshot((arr: number[], target: number) => { const nums = addIds(arr) - debugger nums.sort((a, b) => a.val - b.val) debugger - let minDiff = Number.POSITIVE_INFINITY let minTriple = null + for (let i = 0; i < nums.length - 2; i++) { const curr = nums[i].val - debugger - let head = i + 1 let tail = nums.length - 1 while (head < tail) { const diff = target - (curr + nums[head].val + nums[tail].val) - debugger if (diff === 0) { @@ -43,7 +39,6 @@ export default defineAlgorithm( minTriple = [i, head, tail] } - // If equal, prefer the positive diff because triple sum is smaller if (Math.abs(minDiff) === Math.abs(diff) && Math.sign(diff) > 0) { minDiff = diff minTriple = [i, head, tail] diff --git a/pages/patterns/two-pointers/dutch-flag-sort.tsx b/pages/patterns/two-pointers/dutch-flag-sort.tsx index 94efba2..21066f3 100644 --- a/pages/patterns/two-pointers/dutch-flag-sort.tsx +++ b/pages/patterns/two-pointers/dutch-flag-sort.tsx @@ -19,19 +19,20 @@ export default defineAlgorithm( } const nums = addIds(arr) - let low = 0 let high = arr.length - 1 - let curr = 0 + while (curr <= high) { const num = nums[curr].val debugger + if (num === 0) { if (curr !== low) { swap(nums, curr, low) debugger } + curr++ low++ } else if (num === 1) { diff --git a/pages/patterns/two-pointers/remove-duplicates.tsx b/pages/patterns/two-pointers/remove-duplicates.tsx index af64646..b636a48 100644 --- a/pages/patterns/two-pointers/remove-duplicates.tsx +++ b/pages/patterns/two-pointers/remove-duplicates.tsx @@ -17,6 +17,7 @@ export default defineAlgorithm( if (arr[i] !== arr[i - 1]) { result.push(arr[i]) } + debugger } diff --git a/pages/patterns/two-pointers/sorted-squares.tsx b/pages/patterns/two-pointers/sorted-squares.tsx index f5c05ee..9f08124 100644 --- a/pages/patterns/two-pointers/sorted-squares.tsx +++ b/pages/patterns/two-pointers/sorted-squares.tsx @@ -15,7 +15,6 @@ export default defineAlgorithm( algorithm: snapshot((arr: number[]) => { const withIds = addIds(arr) let result = [] - let head = 0 let tail = arr.length - 1 diff --git a/pages/patterns/two-pointers/triplet-sum-to-zero.tsx b/pages/patterns/two-pointers/triplet-sum-to-zero.tsx index e70cb8a..c21f471 100644 --- a/pages/patterns/two-pointers/triplet-sum-to-zero.tsx +++ b/pages/patterns/two-pointers/triplet-sum-to-zero.tsx @@ -12,12 +12,11 @@ export default defineAlgorithm( description: 'Given an array, find all triplets that add up to zero.', algorithm: snapshot((arr: number[]) => { const nums = addIds(arr) - debugger nums.sort((a, b) => a.val - b.val) debugger - const result = [] + for (let i = 0; i < nums.length - 2; i++) { const target = nums[i].val debugger @@ -34,8 +33,8 @@ export default defineAlgorithm( while (head < tail) { const headNum = nums[head].val const tailNum = nums[tail].val - debugger + if (headNum + tailNum === currTarget) { pairs.push([head, tail]) head++ @@ -61,6 +60,7 @@ export default defineAlgorithm( result.push([target, nums[head].val, nums[tail].val]) } } + debugger return result }), diff --git a/styles/global.css b/styles/global.css index 149988f..868fd42 100644 --- a/styles/global.css +++ b/styles/global.css @@ -4,13 +4,11 @@ html, body { - @apply text-paragraph; padding: 0; margin: 0; font-family: Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; line-height: 1.6; - font-size: 18px; } * { @@ -27,11 +25,11 @@ h1 { } .result { - @apply bg-highlight; + @apply bg-ok; } .not-found { - @apply bg-tertiary; + @apply bg-red-600; } .arrow-down { @@ -45,7 +43,3 @@ h1 { .svg { min-height: 200px; } - -.debug { - @apply border-4 border-stroke; -} diff --git a/tailwind.config.js b/tailwind.config.js index 607d3d2..dbe25c8 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,11 +1,3 @@ -const theme1 = { - stroke: '#001858', - main: '#f3d2c1', - highlight: '#fef6e4', - secondary: '#8bd3dd', - tertiary: '#f582ae', -} - const theme2 = { stroke: '#001858', main: '#fffffe', @@ -21,6 +13,7 @@ module.exports = { purge: [], theme: { fontFamily: { + serif: ['PT Serif', 'serif'], mono: ['SF Mono', 'Menlo', 'monospace'], }, extend: { From 9ff5d9961d7d5185ec0e81464a79a3652cf6847e Mon Sep 17 00:00:00 2001 From: Narendra Syahrasyad Date: Sat, 26 Dec 2020 16:59:51 -0800 Subject: [PATCH 06/15] feat: add prismjs and fix ts errors --- components/Algorithm.tsx | 28 ++-- components/styles/Algorithm.module.scss | 35 +---- lib/types.ts | 10 +- lib/useAlgorithm.ts | 4 +- package.json | 2 + pages/_app.js | 1 + styles/prism.css | 162 ++++++++++++++++++++++++ yarn.lock | 43 +++++++ 8 files changed, 239 insertions(+), 46 deletions(-) create mode 100644 styles/prism.css diff --git a/components/Algorithm.tsx b/components/Algorithm.tsx index f57867a..8b929ee 100644 --- a/components/Algorithm.tsx +++ b/components/Algorithm.tsx @@ -23,11 +23,10 @@ export function Algorithm({ const { entryPoint, params, code } = algorithm const context = useAlgorithm(entryPoint, inputs) + + const paramList = JSON.parse(params) as string[] return ( -
+

{pattern}

@@ -39,13 +38,13 @@ export function Algorithm({

{description}

- {JSON.parse(params).map((paramName, index) => ( + {paramList.map((paramName, index) => ( ))} @@ -54,7 +53,12 @@ export function Algorithm({
- + {showCode && (
+

+ {context.models.steps.indexOf(context.models.state) + 1} /{' '} + {context.models.steps.length} +

= { settings: Settings } -export type AlgorithmContext = { +type BaseState = { + line: number + [variable: string]: unknown +} + +export type AlgorithmContext< + Parameters = unknown[], + State extends BaseState = BaseState +> = { models: Snapshot actions: { /** diff --git a/lib/useAlgorithm.ts b/lib/useAlgorithm.ts index 76aeb70..5fbaf12 100644 --- a/lib/useAlgorithm.ts +++ b/lib/useAlgorithm.ts @@ -11,10 +11,10 @@ const clone = rfdc() * Given an algorithm and arguments, this hook runs the algorithm with the given * arguments and returns a series of algorithm "states" and animation controls. */ -export function useAlgorithm( +export function useAlgorithm( algorithm: any, initialArguments: any[] -): AlgorithmContext { +): AlgorithmContext { const [activeStepIndex, setActiveStepIndex] = React.useState(0) const [isPlaying, setIsPlaying] = React.useState(false) const [inputs, setInputs] = React.useState(clone(initialArguments)) diff --git a/package.json b/package.json index b45b58f..e79adbf 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "framer-motion": "^2.6.6", "gray-matter": "^4.0.2", "next": "^9.5.3", + "prismjs": "^1.22.0", "react": "16.13.1", "react-dom": "16.13.1", "react-icons": "^3.11.0", @@ -26,6 +27,7 @@ }, "devDependencies": { "@types/node": "^14.14.10", + "@types/prismjs": "^1.16.2", "@types/react": "^17.0.0", "@types/rfdc": "^1.1.0", "@types/uuid": "^8.3.0", diff --git a/pages/_app.js b/pages/_app.js index 7e66efc..8ab9202 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -1,4 +1,5 @@ import '../styles/global.css' +import '../styles/prism.css' export default function App({ Component, pageProps }) { return diff --git a/styles/prism.css b/styles/prism.css new file mode 100644 index 0000000..0871467 --- /dev/null +++ b/styles/prism.css @@ -0,0 +1,162 @@ +/** + * Custom prismJS theme adapted from + * VS theme by Andrew Lock (https://andrewlock.net) + */ + +code[class*='language-'], +pre[class*='language-'] { + color: #292a2c; + direction: ltr; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +pre[class*='language-']::-moz-selection, +pre[class*='language-'] ::-moz-selection, +code[class*='language-']::-moz-selection, +code[class*='language-'] ::-moz-selection { + background: #c1def1; +} + +pre[class*='language-']::selection, +pre[class*='language-'] ::selection, +code[class*='language-']::selection, +code[class*='language-'] ::selection { + background: #c1def1; +} + +/* Code blocks */ +pre[class*='language-'] { +} + +/* Inline code */ +:not(pre) > code[class*='language-'] { + color: #cc00ff; + padding: 0.2em 0.4em; + background: #e6fffb; + border-radius: 4px; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: #777; + font-style: italic; +} + +.token.namespace { + opacity: 0.7; +} + +.token.string { + color: #c0f; +} + +.token.punctuation, +.token.operator { + color: #393a34; /* no highlight */ +} + +.token.url, +.token.symbol, +.token.number, +.token.boolean, +.token.variable, +.token.constant, +.token.inserted { + color: #00a; +} + +.token.atrule, +.token.keyword, +.token.attr-value, +.language-autohotkey .token.selector, +.language-json .token.boolean, +.language-json .token.number, +code[class*='language-css'] { + color: #0000ff; +} + +.token.function { + color: #00f; +} +.token.deleted, +.language-autohotkey .token.tag { + color: #9a050f; +} + +.token.selector, +.language-autohotkey .token.keyword { + color: #00009f; +} + +.token.important, +.token.bold { + font-weight: bold; +} + +.token.italic { + font-style: italic; +} + +.token.class-name, +.language-json .token.property { + color: #00f; +} + +.token.tag, +.token.selector { + color: #800000; +} + +.token.attr-name, +.token.property, +.token.regex, +.token.entity { + color: #c0f; +} + +.token.directive.tag .tag { + background: #ffff00; + color: #393a34; +} + +/* overrides color-values for the Line Numbers plugin + * http://prismjs.com/plugins/line-numbers/ + */ +.line-numbers .line-numbers-rows { + border-right-color: #a5a5a5; +} + +.line-numbers-rows > span:before { + color: #2b91af; +} + +/* overrides color-values for the Line Highlight plugin + * http://prismjs.com/plugins/line-highlight/ + */ +.line-highlight { + background: rgba(193, 222, 241, 0.2); + background: -webkit-linear-gradient( + left, + rgba(193, 222, 241, 0.2) 70%, + rgba(221, 222, 241, 0) + ); + background: linear-gradient( + to right, + rgba(193, 222, 241, 0.2) 70%, + rgba(221, 222, 241, 0) + ); +} diff --git a/yarn.lock b/yarn.lock index a1e088c..d690aab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1132,6 +1132,11 @@ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== +"@types/prismjs@^1.16.2": + version "1.16.2" + resolved "https://registry.yarnpkg.com/@types/prismjs/-/prismjs-1.16.2.tgz#c130c977191c988cb35e97585da5d580948cc2d2" + integrity sha512-1M/j21xgTde7RPtpJVQebW5rzrquj7S+wnqt4x9uWrIPpr0Ya/uXypcqC2aUQL5gtLXFCKSH7GnjfAijMdfbuA== + "@types/prop-types@*": version "15.7.3" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" @@ -2172,6 +2177,15 @@ clean-stack@^2.0.0: resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== +clipboard@^2.0.0: + version "2.0.6" + resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.6.tgz#52921296eec0fdf77ead1749421b21c968647376" + integrity sha512-g5zbiixBRk/wyKakSwCKd7vQXDjFnAMGHoEyBogG/bw9kTD9GvdAvaoRR1ALcEzt3pVKxZR0pViekPMIS0QyGg== + dependencies: + good-listener "^1.2.2" + select "^1.1.2" + tiny-emitter "^2.0.0" + clone-deep@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" @@ -2623,6 +2637,11 @@ defined@^1.0.0: resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= +delegate@^3.1.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166" + integrity sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw== + des.js@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" @@ -3444,6 +3463,13 @@ globby@^11.0.1: merge2 "^1.3.0" slash "^3.0.0" +good-listener@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50" + integrity sha1-1TswzfkxPf+33JoNR3CWqm0UXFA= + dependencies: + delegate "^3.1.2" + graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2: version "4.2.4" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" @@ -5399,6 +5425,13 @@ pretty-hrtime@^1.0.3: resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE= +prismjs@^1.22.0: + version "1.22.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.22.0.tgz#73c3400afc58a823dd7eed023f8e1ce9fd8977fa" + integrity sha512-lLJ/Wt9yy0AiSYBf212kK3mM5L8ycwlyTlSxHBAneXLR0nzFMlZ5y7riFPF3E33zXOF2IH95xdY5jIyZbM9z/w== + optionalDependencies: + clipboard "^2.0.0" + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -5956,6 +5989,11 @@ section-matter@^1.0.0: extend-shallow "^2.0.1" kind-of "^6.0.0" +select@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d" + integrity sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0= + semver@7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" @@ -6478,6 +6516,11 @@ timers-browserify@^2.0.4: dependencies: setimmediate "^1.0.4" +tiny-emitter@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" + integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== + to-arraybuffer@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" From 3fcd5ac5c461bfbc824a3c605365a441f30ef7da Mon Sep 17 00:00:00 2001 From: Narendra Syahrasyad Date: Sat, 26 Dec 2020 19:12:15 -0800 Subject: [PATCH 07/15] feat: add custom inputs --- components/Algorithm/Algorithm.module.scss | 8 ++ components/Algorithm/ArgumentForm.tsx | 72 +++++++++++++++++ components/Algorithm/Controls.tsx | 39 +++++++++ .../{Algorithm.tsx => Algorithm/index.tsx} | 81 +++++-------------- components/styles/Algorithm.module.scss | 3 - lib/defineAlgorithm.tsx | 2 +- .../sliding-window/find-all-averages.tsx | 40 ++++----- 7 files changed, 159 insertions(+), 86 deletions(-) create mode 100644 components/Algorithm/Algorithm.module.scss create mode 100644 components/Algorithm/ArgumentForm.tsx create mode 100644 components/Algorithm/Controls.tsx rename components/{Algorithm.tsx => Algorithm/index.tsx} (58%) delete mode 100644 components/styles/Algorithm.module.scss diff --git a/components/Algorithm/Algorithm.module.scss b/components/Algorithm/Algorithm.module.scss new file mode 100644 index 0000000..103e680 --- /dev/null +++ b/components/Algorithm/Algorithm.module.scss @@ -0,0 +1,8 @@ +.main { + grid-template-columns: minmax(0, 3fr) 4fr; +} + +.controls { + top: 50%; + transform: translateY(-50%); +} diff --git a/components/Algorithm/ArgumentForm.tsx b/components/Algorithm/ArgumentForm.tsx new file mode 100644 index 0000000..fa90316 --- /dev/null +++ b/components/Algorithm/ArgumentForm.tsx @@ -0,0 +1,72 @@ +import React, { FocusEventHandler, FormEventHandler } from 'react' + +type Args = (readonly [string, unknown])[] + +type ArgumentFormProps = { + args: Args + onSubmit: (newArgs: Args) => void +} + +export default function ArgumentForm({ args, onSubmit }: ArgumentFormProps) { + const [errors, setErrors] = React.useState({}) + + const validate = (name: string, value: string) => { + if (isSerializable(value)) { + setErrors({ [name]: null }) + return true + } + setErrors({ [name]: 'Please enter a serializable input.' }) + return false + } + + const handleBlur: FocusEventHandler = (evt) => { + const { name, value } = evt.target + validate(name, value) + } + + const handleSubmit: FormEventHandler = (evt) => { + evt.preventDefault() + const data = [...new FormData(evt.target as HTMLFormElement).entries()] as [ + string, + string + ][] + + if (!data.every(([name, value]) => validate(name, value))) { + return + } + + onSubmit(data.map(([name, value]) => [name, JSON.parse(value)])) + } + + return ( +
+ {args.map(([name, value]) => ( + + ))} + +
+ ) +} + +function isSerializable(value: string) { + try { + JSON.parse(value) + return true + } catch { + return false + } +} diff --git a/components/Algorithm/Controls.tsx b/components/Algorithm/Controls.tsx new file mode 100644 index 0000000..cde7012 --- /dev/null +++ b/components/Algorithm/Controls.tsx @@ -0,0 +1,39 @@ +import React from 'react' +import clsx, { ClassValue } from 'clsx' +import { BsFillPlayFill, BsPauseFill } from 'react-icons/bs' +import { FaUndoAlt } from 'react-icons/fa' +import { BiLeftArrowAlt, BiRightArrowAlt } from 'react-icons/bi' + +import { Button } from '~components/Button' +import { AlgorithmContext } from '~lib/types' + +type ControlsProps = { + context: AlgorithmContext + className?: ClassValue +} + +export default function Controls({ + context: { actions, models }, + className = '', +}: ControlsProps) { + return ( +
+ + + + +
+ ) +} diff --git a/components/Algorithm.tsx b/components/Algorithm/index.tsx similarity index 58% rename from components/Algorithm.tsx rename to components/Algorithm/index.tsx index 8b929ee..4f02000 100644 --- a/components/Algorithm.tsx +++ b/components/Algorithm/index.tsx @@ -1,17 +1,15 @@ import React from 'react' -import clsx, { ClassValue } from 'clsx' +import clsx from 'clsx' import { motion, AnimateSharedLayout, AnimatePresence } from 'framer-motion' -import { BsFillPlayFill, BsPauseFill } from 'react-icons/bs' -import { FaUndoAlt } from 'react-icons/fa' -import { BiLeftArrowAlt, BiRightArrowAlt } from 'react-icons/bi' +import { BiLeftArrowAlt } from 'react-icons/bi' -import { Button } from '~components/Button' import { useAlgorithm } from '~lib/useAlgorithm' -import { AlgorithmContext } from '~lib/types' -import styles from './styles/Algorithm.module.scss' +import styles from './Algorithm.module.scss' +import ArgumentForm from './ArgumentForm' +import Controls from './Controls' -export function Algorithm({ +export default function Algorithm({ children, algorithm, inputs, @@ -20,14 +18,16 @@ export function Algorithm({ description, }) { const [showCode, toggleCode] = React.useReducer((show) => !show, false) - const { entryPoint, params, code } = algorithm const context = useAlgorithm(entryPoint, inputs) - const paramList = JSON.parse(params) as string[] + const args = (JSON.parse(params) as string[]).map( + (name, index) => [name, inputs[index]] as const + ) + return (
-
+

{pattern}

{description}

-
- {paramList.map((paramName, index) => ( - - ))} - -
+ + context.actions.setInputs(args.map(([, value]) => value)) + } + />

) } - -// --- - -type ControlsProps = { - context: AlgorithmContext - className?: ClassValue - style: any -} - -function Controls({ - context: { actions, models }, - className = '', - style, -}: ControlsProps) { - return ( -
- - - - -
- ) -} diff --git a/components/styles/Algorithm.module.scss b/components/styles/Algorithm.module.scss deleted file mode 100644 index d00c5b2..0000000 --- a/components/styles/Algorithm.module.scss +++ /dev/null @@ -1,3 +0,0 @@ -.main { - grid-template-columns: minmax(0, 3fr) 4fr; -} diff --git a/lib/defineAlgorithm.tsx b/lib/defineAlgorithm.tsx index 48ce0c3..020b65a 100644 --- a/lib/defineAlgorithm.tsx +++ b/lib/defineAlgorithm.tsx @@ -1,6 +1,6 @@ import React from 'react' -import { Algorithm } from '~components/Algorithm' +import Algorithm from '~components/Algorithm' export default function defineAlgorithm( options: any, diff --git a/pages/patterns/sliding-window/find-all-averages.tsx b/pages/patterns/sliding-window/find-all-averages.tsx index c9b2f0f..39d6c1e 100644 --- a/pages/patterns/sliding-window/find-all-averages.tsx +++ b/pages/patterns/sliding-window/find-all-averages.tsx @@ -5,6 +5,26 @@ import { Window } from '~components/Window' import defineAlgorithm from '~lib/defineAlgorithm' import snapshot from '../../../lib/snapshot.macro' +const findAllAverages = snapshot((arr: number[], k: number) => { + const result = [] + let windowStart = 0 + let windowSum = 0 + + for (let windowEnd = 0; windowEnd < arr.length; windowEnd++) { + windowSum += arr[windowEnd] + debugger + + if (windowEnd >= k - 1) { + result.push((windowSum / k).toFixed(2)) + windowSum -= arr[windowStart] + windowStart++ + } + } + + debugger + return result +}) + export default defineAlgorithm( { title: 'Find All Averages', @@ -12,25 +32,7 @@ export default defineAlgorithm( description: 'Given an array, find the averages of all subarrays of size k.', inputs: [[1, 3, 2, 6, -1, 4, 1, 8, 2], 3], - algorithm: snapshot((arr: number[], k: number) => { - const result = [] - let windowStart = 0 - let windowSum = 0 - - for (let windowEnd = 0; windowEnd < arr.length; windowEnd++) { - windowSum += arr[windowEnd] - debugger - - if (windowEnd >= k - 1) { - result.push((windowSum / k).toFixed(2)) - windowSum -= arr[windowStart] - windowStart++ - } - } - - debugger - return result - }), + algorithm: findAllAverages, }, function FindAllAverages({ state, inputs }) { const { __done: done, windowStart, windowEnd, result } = state From fea492045bee7940ad0570516be8b8bbb66eb24d Mon Sep 17 00:00:00 2001 From: Narendra Syahrasyad Date: Mon, 28 Dec 2020 13:00:36 -0800 Subject: [PATCH 08/15] refactor: improve types --- components/Algorithm/index.tsx | 7 ++++++- lib/defineAlgorithm.tsx | 20 +++++++++++--------- lib/snapshot.macro.d.ts | 13 ++++++++++++- lib/snapshot.macro.js | 1 - lib/snapshot.ts | 8 ++------ 5 files changed, 31 insertions(+), 18 deletions(-) diff --git a/components/Algorithm/index.tsx b/components/Algorithm/index.tsx index 4f02000..f7ed00a 100644 --- a/components/Algorithm/index.tsx +++ b/components/Algorithm/index.tsx @@ -4,11 +4,16 @@ import { motion, AnimateSharedLayout, AnimatePresence } from 'framer-motion' import { BiLeftArrowAlt } from 'react-icons/bi' import { useAlgorithm } from '~lib/useAlgorithm' +import type { AlgorithmOptions } from '~lib/defineAlgorithm' import styles from './Algorithm.module.scss' import ArgumentForm from './ArgumentForm' import Controls from './Controls' +export type AlgorithmProps = AlgorithmOptions & { + children: React.ReactElement +} + export default function Algorithm({ children, algorithm, @@ -16,7 +21,7 @@ export default function Algorithm({ title, pattern, description, -}) { +}: AlgorithmProps) { const [showCode, toggleCode] = React.useReducer((show) => !show, false) const { entryPoint, params, code } = algorithm const context = useAlgorithm(entryPoint, inputs) diff --git a/lib/defineAlgorithm.tsx b/lib/defineAlgorithm.tsx index 020b65a..8d56a9c 100644 --- a/lib/defineAlgorithm.tsx +++ b/lib/defineAlgorithm.tsx @@ -1,21 +1,23 @@ import React from 'react' import Algorithm from '~components/Algorithm' +import type { Recordable } from './snapshot.macro' + +export type AlgorithmOptions = { + title: string + pattern: string + description: string + algorithm: Recordable + inputs: unknown[] +} export default function defineAlgorithm( - options: any, + options: AlgorithmOptions, Component: (props: unknown) => JSX.Element ) { - const { title, pattern, description, algorithm, inputs } = options const Page = function () { return ( - + ) diff --git a/lib/snapshot.macro.d.ts b/lib/snapshot.macro.d.ts index c055002..5fb5702 100644 --- a/lib/snapshot.macro.d.ts +++ b/lib/snapshot.macro.d.ts @@ -1,3 +1,14 @@ +export type Recordable = { + entryPoint: (snapshot: Snapshot) => (...args: Parameters) => ReturnType + params: string + code: string +} + +export type Snapshot = { + data: Partial[] + push(val: Partial): void +} + export default function snapshot any>( algo: T -): (snapshots) => (...args: Parameters) => any +): Recordable diff --git a/lib/snapshot.macro.js b/lib/snapshot.macro.js index d16c3e1..a1a2cd8 100644 --- a/lib/snapshot.macro.js +++ b/lib/snapshot.macro.js @@ -1,5 +1,4 @@ const { createMacro } = require('babel-plugin-macros') -const { default: generate } = require('@babel/generator') module.exports = createMacro(snapshot) diff --git a/lib/snapshot.ts b/lib/snapshot.ts index b008c66..502ef57 100644 --- a/lib/snapshot.ts +++ b/lib/snapshot.ts @@ -1,14 +1,10 @@ import rfdc from 'rfdc' - -export type Snapshot = { - data: Partial[] - push(val: Partial): void -} +import type { Snapshot } from './snapshot.macro' const clone = rfdc() const snapshot = { - createSnapshot() { + createSnapshot(): Snapshot { const data = [] return { data, From 481f47ad31bac7a10e109f7e4951dee8ddf102f3 Mon Sep 17 00:00:00 2001 From: Narendra Syahrasyad Date: Mon, 28 Dec 2020 13:18:00 -0800 Subject: [PATCH 09/15] refactor: improve use algorithm type checks --- lib/defineAlgorithm.tsx | 2 +- lib/snapshot.macro.d.ts | 11 ----------- lib/snapshot.ts | 4 ++-- lib/types.ts | 42 ++++++++++++++++++++++++++++++----------- lib/useAlgorithm.ts | 15 ++++++++++----- 5 files changed, 44 insertions(+), 30 deletions(-) diff --git a/lib/defineAlgorithm.tsx b/lib/defineAlgorithm.tsx index 8d56a9c..db05c52 100644 --- a/lib/defineAlgorithm.tsx +++ b/lib/defineAlgorithm.tsx @@ -1,7 +1,7 @@ import React from 'react' import Algorithm from '~components/Algorithm' -import type { Recordable } from './snapshot.macro' +import type { Recordable } from './types' export type AlgorithmOptions = { title: string diff --git a/lib/snapshot.macro.d.ts b/lib/snapshot.macro.d.ts index 5fb5702..c00fd3f 100644 --- a/lib/snapshot.macro.d.ts +++ b/lib/snapshot.macro.d.ts @@ -1,14 +1,3 @@ -export type Recordable = { - entryPoint: (snapshot: Snapshot) => (...args: Parameters) => ReturnType - params: string - code: string -} - -export type Snapshot = { - data: Partial[] - push(val: Partial): void -} - export default function snapshot any>( algo: T ): Recordable diff --git a/lib/snapshot.ts b/lib/snapshot.ts index 502ef57..4714aca 100644 --- a/lib/snapshot.ts +++ b/lib/snapshot.ts @@ -1,10 +1,10 @@ import rfdc from 'rfdc' -import type { Snapshot } from './snapshot.macro' +import type { Snapshotter } from './types' const clone = rfdc() const snapshot = { - createSnapshot(): Snapshot { + createSnapshot(): Snapshotter { const data = [] return { data, diff --git a/lib/types.ts b/lib/types.ts index d4081d2..f9b7e36 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -6,7 +6,35 @@ export type Settings = { delay: number } -export type Snapshot = { +export type EntryFunction = ( + snapshot: Snapshotter +) => (...args: unknown[]) => unknown + +export type EntryArguments = T extends ( + snapshot: Snapshotter +) => (...args: infer Params) => unknown + ? Params + : unknown[] + +export type Recordable = { + entryPoint: EntryFunction + params: string + code: string +} + +export type Snapshotter = { + data: State[] + push(val: Omit): void +} + +export type State = { + line: number + __done: boolean + __returnValue: unknown + [variable: string]: unknown +} + +export type Snapshot = { /** * A snapshot of the current state of the algorithm. Typically represents the * value of specific variables at a particular point in time. @@ -30,16 +58,8 @@ export type Snapshot = { settings: Settings } -type BaseState = { - line: number - [variable: string]: unknown -} - -export type AlgorithmContext< - Parameters = unknown[], - State extends BaseState = BaseState -> = { - models: Snapshot +export type AlgorithmContext = { + models: Snapshot actions: { /** * Set arguments for the algorithm. Triggers a re-run of the algorithm, in turn diff --git a/lib/useAlgorithm.ts b/lib/useAlgorithm.ts index 5fbaf12..6928bf8 100644 --- a/lib/useAlgorithm.ts +++ b/lib/useAlgorithm.ts @@ -2,7 +2,12 @@ import React from 'react' import useInterval from '@use-it/interval' import rfdc from 'rfdc' -import { AlgorithmContext, Settings } from './types' +import type { + EntryFunction, + EntryArguments, + AlgorithmContext, + Settings, +} from './types' import snapshot from './snapshot' const clone = rfdc() @@ -11,10 +16,10 @@ const clone = rfdc() * Given an algorithm and arguments, this hook runs the algorithm with the given * arguments and returns a series of algorithm "states" and animation controls. */ -export function useAlgorithm( - algorithm: any, - initialArguments: any[] -): AlgorithmContext { +export function useAlgorithm( + algorithm: T, + initialArguments: EntryArguments +): AlgorithmContext> { const [activeStepIndex, setActiveStepIndex] = React.useState(0) const [isPlaying, setIsPlaying] = React.useState(false) const [inputs, setInputs] = React.useState(clone(initialArguments)) From 51041df08090ca6b64478df5b019539a6a1ac51c Mon Sep 17 00:00:00 2001 From: Narendra Syahrasyad Date: Mon, 28 Dec 2020 21:04:30 -0800 Subject: [PATCH 10/15] feat: add highlighting to code blocks --- components/Algorithm/CodeBlock.tsx | 41 ++++++++++++++++++++++++++++++ components/Algorithm/index.tsx | 22 +++++----------- lib/snapshot.macro.d.ts | 19 +++++++++++--- lib/types.ts | 17 ++----------- lib/useAlgorithm.ts | 8 ++---- package.json | 1 + styles/prism.css | 4 +++ yarn.lock | 5 ++++ 8 files changed, 78 insertions(+), 39 deletions(-) create mode 100644 components/Algorithm/CodeBlock.tsx diff --git a/components/Algorithm/CodeBlock.tsx b/components/Algorithm/CodeBlock.tsx new file mode 100644 index 0000000..552fa43 --- /dev/null +++ b/components/Algorithm/CodeBlock.tsx @@ -0,0 +1,41 @@ +import React from 'react' +import Highlight, { defaultProps } from 'prism-react-renderer' +import { motion } from 'framer-motion' +import clsx from 'clsx' + +export default function CodeBlock({ code, highlightLine }) { + return ( + + {({ className, style, tokens, getLineProps, getTokenProps }) => ( +
+          
+          {tokens.map((line, i) => (
+            
+ {line.map((token, key) => ( + + ))} +
+ ))} +
+ )} +
+ ) +} diff --git a/components/Algorithm/index.tsx b/components/Algorithm/index.tsx index f7ed00a..51f8d81 100644 --- a/components/Algorithm/index.tsx +++ b/components/Algorithm/index.tsx @@ -2,6 +2,7 @@ import React from 'react' import clsx from 'clsx' import { motion, AnimateSharedLayout, AnimatePresence } from 'framer-motion' import { BiLeftArrowAlt } from 'react-icons/bi' +import Highlight, { defaultProps } from 'prism-react-renderer' import { useAlgorithm } from '~lib/useAlgorithm' import type { AlgorithmOptions } from '~lib/defineAlgorithm' @@ -9,6 +10,7 @@ import type { AlgorithmOptions } from '~lib/defineAlgorithm' import styles from './Algorithm.module.scss' import ArgumentForm from './ArgumentForm' import Controls from './Controls' +import CodeBlock from './CodeBlock' export type AlgorithmProps = AlgorithmOptions & { children: React.ReactElement @@ -62,7 +64,7 @@ export default function Algorithm({ animate={{ x: 0 }} exit={{ x: '-100%' }} transition={{ duration: 0.2 }} - className="absolute w-full h-full left-0 bg-white px-16 py-20 flex items-center justify-center" + className="absolute w-full h-full left-0 bg-white px-4 py-20 flex items-center justify-center" > -
-                
-                {code}
-              
+
)} diff --git a/lib/snapshot.macro.d.ts b/lib/snapshot.macro.d.ts index c00fd3f..9710bf1 100644 --- a/lib/snapshot.macro.d.ts +++ b/lib/snapshot.macro.d.ts @@ -1,3 +1,16 @@ -export default function snapshot any>( - algo: T -): Recordable +export type Snapshotter = { + data: State[] + push(val: Omit): void +} + +export type Recordable = { + entryPoint: EntryFunction + params: string + code: string +} + +export type EntryFunction = ( + snapshot: Snapshotter +) => (...args: unknown[]) => unknown + +export default function snapshot(algo): Recordable diff --git a/lib/types.ts b/lib/types.ts index f9b7e36..0ac7fd0 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -1,3 +1,5 @@ +import type { Snapshotter } from './snapshot.macro' + export type Settings = { /** * Determines the running speed of the animation. This number is passed directly @@ -6,27 +8,12 @@ export type Settings = { delay: number } -export type EntryFunction = ( - snapshot: Snapshotter -) => (...args: unknown[]) => unknown - export type EntryArguments = T extends ( snapshot: Snapshotter ) => (...args: infer Params) => unknown ? Params : unknown[] -export type Recordable = { - entryPoint: EntryFunction - params: string - code: string -} - -export type Snapshotter = { - data: State[] - push(val: Omit): void -} - export type State = { line: number __done: boolean diff --git a/lib/useAlgorithm.ts b/lib/useAlgorithm.ts index 6928bf8..412f090 100644 --- a/lib/useAlgorithm.ts +++ b/lib/useAlgorithm.ts @@ -2,12 +2,8 @@ import React from 'react' import useInterval from '@use-it/interval' import rfdc from 'rfdc' -import type { - EntryFunction, - EntryArguments, - AlgorithmContext, - Settings, -} from './types' +import type { EntryArguments, AlgorithmContext, Settings } from './types' +import { EntryFunction } from './snapshot.macro' import snapshot from './snapshot' const clone = rfdc() diff --git a/package.json b/package.json index e79adbf..39ee7fc 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "framer-motion": "^2.6.6", "gray-matter": "^4.0.2", "next": "^9.5.3", + "prism-react-renderer": "^1.1.1", "prismjs": "^1.22.0", "react": "16.13.1", "react-dom": "16.13.1", diff --git a/styles/prism.css b/styles/prism.css index 0871467..054c316 100644 --- a/styles/prism.css +++ b/styles/prism.css @@ -160,3 +160,7 @@ code[class*='language-css'] { rgba(221, 222, 241, 0) ); } + +.token-line:not(:last-child) { + min-height: 22.4px; +} diff --git a/yarn.lock b/yarn.lock index d690aab..3106f17 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5425,6 +5425,11 @@ pretty-hrtime@^1.0.3: resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE= +prism-react-renderer@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.1.1.tgz#1c1be61b1eb9446a146ca7a50b7bcf36f2a70a44" + integrity sha512-MgMhSdHuHymNRqD6KM3eGS0PNqgK9q4QF5P0yoQQvpB6jNjeSAi3jcSAz0Sua/t9fa4xDOMar9HJbLa08gl9ug== + prismjs@^1.22.0: version "1.22.0" resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.22.0.tgz#73c3400afc58a823dd7eed023f8e1ce9fd8977fa" From 6ebfd1ced21e3ee46e1b5be50af8f6d0aa69ee75 Mon Sep 17 00:00:00 2001 From: Narendra Syahrasyad Date: Tue, 29 Dec 2020 18:47:56 -0800 Subject: [PATCH 11/15] feat: implement search bar --- components/Algorithm/index.tsx | 136 ++++++++++++----------- components/Layout.js | 187 -------------------------------- components/Layout/SearchBar.tsx | 138 +++++++++++++++++++++++ components/Layout/index.tsx | 20 ++++ components/Window.js | 9 +- lib/defineAlgorithm.tsx | 2 +- package.json | 2 + pages/_app.js | 6 - pages/_app.tsx | 14 +++ yarn.lock | 33 ++++++ 10 files changed, 283 insertions(+), 264 deletions(-) delete mode 100644 components/Layout.js create mode 100644 components/Layout/SearchBar.tsx create mode 100644 components/Layout/index.tsx delete mode 100644 pages/_app.js create mode 100644 pages/_app.tsx diff --git a/components/Algorithm/index.tsx b/components/Algorithm/index.tsx index 51f8d81..381fb4e 100644 --- a/components/Algorithm/index.tsx +++ b/components/Algorithm/index.tsx @@ -1,8 +1,8 @@ import React from 'react' import clsx from 'clsx' +import Head from 'next/head' import { motion, AnimateSharedLayout, AnimatePresence } from 'framer-motion' import { BiLeftArrowAlt } from 'react-icons/bi' -import Highlight, { defaultProps } from 'prism-react-renderer' import { useAlgorithm } from '~lib/useAlgorithm' import type { AlgorithmOptions } from '~lib/defineAlgorithm' @@ -33,72 +33,80 @@ export default function Algorithm({ ) return ( -
-
-
-

{pattern}

-

+ + {title} + +
+
+
+

{pattern}

+

+ {title} +

+

{description}

+ + context.actions.setInputs(args.map(([, value]) => value)) + } + /> +
+

-

{description}

- - context.actions.setInputs(args.map(([, value]) => value)) - } - /> -
- - - {showCode && ( + Show code + + + {showCode && ( + + + + + )} + +
+
+

+ {context.models.steps.indexOf(context.models.state) + 1} /{' '} + {context.models.steps.length} +

+ - - + {React.cloneElement( + React.Children.only(children), + context.models + )} - )} - -
-
-

- {context.models.steps.indexOf(context.models.state) + 1} /{' '} - {context.models.steps.length} -

- - - {React.cloneElement(React.Children.only(children), context.models)} - - - -
-
+
+ +
+
+ ) } diff --git a/components/Layout.js b/components/Layout.js deleted file mode 100644 index cbcdec4..0000000 --- a/components/Layout.js +++ /dev/null @@ -1,187 +0,0 @@ -import Head from 'next/head' -import React, { useState, useEffect } from 'react' -import Link from 'next/link' -import { useRouter } from 'next/router' -import clsx from 'clsx' -import { useMediaQuery } from 'beautiful-react-hooks' -import { motion } from 'framer-motion' -import { FaBars, FaTimes } from 'react-icons/fa' - -import styles from './styles/Layout.module.scss' - -export function Layout({ children }) { - return ( - <> - - - -
-
- - ) -} - -const routes = { - 'sliding-window': ['find-all-averages', 'non-repeat-substring'], - 'two-pointers': [ - 'pair-sum', - 'remove-duplicates', - 'sorted-squares', - 'triplet-sum-to-zero', - 'closest-triplet', - 'dutch-flag-sort', - ], -} - -const navVariants = { - hidden: { - scaleX: 0, - scaleY: 0, - }, - show: { - scaleX: 1, - scaleY: 1, - transition: { - when: 'beforeChildren', - staggerChildren: 0.3, - }, - }, -} - -const itemVariants = { - hidden: { - opacity: 0, - y: 10, - }, - show: (i) => ({ - opacity: 1, - y: 0, - transition: { - delay: i * 0.05, - }, - }), -} - -function Nav() { - const showNavButton = useMediaQuery('(max-width: 80rem)') - const isMed = useMediaQuery('(min-width: 48rem)') - const [isNavOpen, setIsNavOpen] = useState(!showNavButton) - const router = useRouter() - - useEffect(() => { - if (!showNavButton) { - setIsNavOpen(true) - } - }, [showNavButton]) - - useEffect(() => { - const close = (evt) => { - if (evt.key === 'Escape') { - setIsNavOpen(false) - } - } - if (showNavButton) { - document.addEventListener('keydown', close) - } - return () => document.removeEventListener('keydown', close) - }, [showNavButton]) - - return ( - <> - {showNavButton && ( - - )} - {isNavOpen && showNavButton && ( -
setIsNavOpen(false)} - >
- )} - - - - - ) -} diff --git a/components/Layout/SearchBar.tsx b/components/Layout/SearchBar.tsx new file mode 100644 index 0000000..071e2b1 --- /dev/null +++ b/components/Layout/SearchBar.tsx @@ -0,0 +1,138 @@ +/* eslint-disable react/jsx-key */ +/* eslint-disable jsx-a11y/label-has-associated-control */ +import React from 'react' +import Downshift from 'downshift' +import Link from 'next/link' +import { useRouter } from 'next/router' +import { matchSorter } from 'match-sorter' + +type Algorithm = { + name: string + link: string + pattern: string +} + +const routes = [ + ['sliding-window', ['find-all-averages', 'non-repeat-substring']], + [ + 'two-pointers', + [ + 'pair-sum', + 'remove-duplicates', + 'sorted-squares', + 'triplet-sum-to-zero', + 'closest-triplet', + 'dutch-flag-sort', + ], + ], +] as [string, string[]][] + +const patterns = routes.map(([pattern, algorithms]) => ({ + name: format(pattern), + link: pattern, + algorithms: algorithms.map((link) => ({ + name: format(link), + link, + pattern, + })), +})) + +const allAlgorithms = patterns.flatMap((pattern) => pattern.algorithms) + +export default function SearchBar() { + const router = useRouter() + + const handleChange = (item) => { + if (!item) { + return + } + const { link, pattern } = item + router.push(`/patterns/${pattern}/${link}`) + } + + return ( + + onChange={handleChange} + itemToString={(item) => (item ? item.name : '')} + > + {({ + getInputProps, + getItemProps, + getLabelProps, + getMenuProps, + isOpen, + inputValue, + highlightedIndex, + selectedItem, + setState, + }) => { + const visiblePatterns = getVisibleItems(inputValue) + return ( +
+ + setState({ inputValue: '', isOpen: true }), + })} + /> +
    + {isOpen + ? visiblePatterns.map(({ name, link, algorithms }) => ( +
      + {name} + {algorithms.map((algo) => { + const index = allAlgorithms.indexOf(algo) + return ( +
    • + + {algo.name} + +
    • + ) + })} +
    + )) + : null} +
+
+ ) + }} + + ) +} + +// -- + +function format(str: string) { + return str.split('-').join(' ') +} + +function getVisibleItems(inputValue: string) { + const matches = matchSorter(allAlgorithms, inputValue, { + keys: ['name', 'pattern'], + }) + return patterns + .filter((pattern) => + matches.find((algorithm) => algorithm.pattern === pattern.link) + ) + .map((pattern) => ({ + ...pattern, + algorithms: pattern.algorithms.filter((algorithm) => + matches.includes(algorithm) + ), + })) +} diff --git a/components/Layout/index.tsx b/components/Layout/index.tsx new file mode 100644 index 0000000..5862eea --- /dev/null +++ b/components/Layout/index.tsx @@ -0,0 +1,20 @@ +import React from 'react' +import Head from 'next/head' + +import SearchBar from './SearchBar' + +export default function Layout({ children }) { + return ( + <> + + + +
+
+ +
+ {children} +
+ + ) +} diff --git a/components/Window.js b/components/Window.js index a306796..ee8c56e 100644 --- a/components/Window.js +++ b/components/Window.js @@ -1,23 +1,20 @@ import React from 'react' import { motion, AnimatePresence } from 'framer-motion' -import { useMediaQuery } from 'beautiful-react-hooks' const ItemMargin = 0.5 +const ItemWidth = 4 export function Window({ show, start, end }) { - const isLarge = useMediaQuery('(min-width: 768px)') const windowSize = end - start + 1 - - const itemWidth = isLarge ? 4 : 3 return ( {show && ( -} diff --git a/pages/_app.tsx b/pages/_app.tsx new file mode 100644 index 0000000..271e25b --- /dev/null +++ b/pages/_app.tsx @@ -0,0 +1,14 @@ +import React from 'react' + +import Layout from '~components/Layout' + +import '../styles/global.css' +import '../styles/prism.css' + +export default function App({ Component, pageProps }) { + return ( + + + + ) +} diff --git a/yarn.lock b/yarn.lock index 3106f17..8c5b05b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2290,6 +2290,11 @@ compose-function@3.0.3: dependencies: arity-n "^1.0.4" +compute-scroll-into-view@^1.0.16: + version "1.0.16" + resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.16.tgz#5b7bf4f7127ea2c19b750353d7ce6776a90ee088" + integrity sha512-a85LHKY81oQnikatZYA90pufpZ6sQx++BoCxOEMsjpZx+ZnaKGQnCyCehTRr/1p9GBIAHTjcU9k71kSYWloLiQ== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -2748,6 +2753,16 @@ domutils@^2.0.0: domelementtype "^2.0.1" domhandler "^3.0.0" +downshift@^6.0.10: + version "6.0.10" + resolved "https://registry.yarnpkg.com/downshift/-/downshift-6.0.10.tgz#c0fdc353b286c0b5f8d93af80a2bce57d90854d5" + integrity sha512-TuUh448snXiOXrstL1q6s13xev2kWEHAuNlwzEHXRMhG7NbPgvzFvjYelwkaOSZ1dFNJjzRnpK6cbvUO7oHlMQ== + dependencies: + "@babel/runtime" "^7.12.5" + compute-scroll-into-view "^1.0.16" + prop-types "^15.7.2" + react-is "^17.0.1" + duplexify@^3.4.2, duplexify@^3.6.0: version "3.7.1" resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" @@ -4283,6 +4298,14 @@ markdown-table@^2.0.0: dependencies: repeat-string "^1.0.0" +match-sorter@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/match-sorter/-/match-sorter-6.0.2.tgz#91bbab14c28a87f4a67755b7a194c0d11dedc080" + integrity sha512-SDRLNlWof9GnAUEyhKP0O5525MMGXUGt+ep4MrrqQ2StAh3zjvICVZseiwg7Zijn3GazpJDiwuRr/mFDHd92NQ== + dependencies: + "@babel/runtime" "^7.12.5" + remove-accents "0.4.2" + md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -5587,6 +5610,11 @@ react-is@16.13.1, react-is@^16.8.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== +react-is@^17.0.1: + version "17.0.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339" + integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA== + react-refresh@0.8.3: version "0.8.3" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f" @@ -5789,6 +5817,11 @@ remark@^12.0.0: remark-stringify "^8.0.0" unified "^9.0.0" +remove-accents@0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.4.2.tgz#0a43d3aaae1e80db919e07ae254b285d9e1c7bb5" + integrity sha1-CkPTqq4egNuRngeuJUsoXZ4ce7U= + remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" From 88665d63672b36e201be694a20ef2ea4747d15cd Mon Sep 17 00:00:00 2001 From: Narendra Syahrasyad Date: Tue, 29 Dec 2020 18:48:20 -0800 Subject: [PATCH 12/15] chore: better type check --- components/Layout/SearchBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/Layout/SearchBar.tsx b/components/Layout/SearchBar.tsx index 071e2b1..eb07e99 100644 --- a/components/Layout/SearchBar.tsx +++ b/components/Layout/SearchBar.tsx @@ -42,7 +42,7 @@ const allAlgorithms = patterns.flatMap((pattern) => pattern.algorithms) export default function SearchBar() { const router = useRouter() - const handleChange = (item) => { + const handleChange = (item: Algorithm) => { if (!item) { return } From 4988f367beb07834153cfe2f41ebf80ac9a03971 Mon Sep 17 00:00:00 2001 From: Narendra Syahrasyad Date: Tue, 5 Jan 2021 22:33:45 -0800 Subject: [PATCH 13/15] ci: fix build issues --- components/Layout/SearchBar.tsx | 59 ++++++++++----- components/Layout/index.tsx | 6 +- lib/snapshot.ts | 2 +- .../fast-slow-pointers/linked-list-cycle.js | 72 ------------------- .../patterns/two-pointers/closest-triplet.tsx | 15 +++- tailwind.config.js | 2 +- 6 files changed, 57 insertions(+), 99 deletions(-) delete mode 100644 pages/patterns/fast-slow-pointers/linked-list-cycle.js diff --git a/components/Layout/SearchBar.tsx b/components/Layout/SearchBar.tsx index eb07e99..41dce9b 100644 --- a/components/Layout/SearchBar.tsx +++ b/components/Layout/SearchBar.tsx @@ -5,6 +5,7 @@ import Downshift from 'downshift' import Link from 'next/link' import { useRouter } from 'next/router' import { matchSorter } from 'match-sorter' +import clsx from 'clsx' type Algorithm = { name: string @@ -39,7 +40,7 @@ const patterns = routes.map(([pattern, algorithms]) => ({ const allAlgorithms = patterns.flatMap((pattern) => pattern.algorithms) -export default function SearchBar() { +export default function SearchBar({ className = '' }) { const router = useRouter() const handleChange = (item: Algorithm) => { @@ -50,15 +51,16 @@ export default function SearchBar() { router.push(`/patterns/${pattern}/${link}`) } + const currentPath = format(getCurrentPath(router.pathname)) return ( onChange={handleChange} itemToString={(item) => (item ? item.name : '')} + initialInputValue={currentPath} > {({ getInputProps, getItemProps, - getLabelProps, getMenuProps, isOpen, inputValue, @@ -68,34 +70,42 @@ export default function SearchBar() { }) => { const visiblePatterns = getVisibleItems(inputValue) return ( -
- +
setState({ inputValue: '', isOpen: true }), })} /> -
    - {isOpen - ? visiblePatterns.map(({ name, link, algorithms }) => ( +
      + {isOpen ? ( + visiblePatterns.length ? ( + visiblePatterns.map(({ name, link, algorithms }) => (
        - {name} +

        {name}

        {algorithms.map((algo) => { const index = allAlgorithms.indexOf(algo) + const highlighted = highlightedIndex === index + const selected = + selectedItem === algo || algo.name === currentPath return (
      • @@ -106,7 +116,12 @@ export default function SearchBar() { })}
      )) - : null} + ) : ( +

      + No algorithms found :-( +

      + ) + ) : null}
) @@ -118,12 +133,18 @@ export default function SearchBar() { // -- function format(str: string) { - return str.split('-').join(' ') + const words = str.split('-') + return words.map((word) => word[0].toUpperCase() + word.slice(1)).join(' ') +} + +function getCurrentPath(pathName: string) { + const words = pathName.split('/') + return words[words.length - 1] } function getVisibleItems(inputValue: string) { const matches = matchSorter(allAlgorithms, inputValue, { - keys: ['name', 'pattern'], + keys: ['name', (item) => format(item.pattern)], }) return patterns .filter((pattern) => diff --git a/components/Layout/index.tsx b/components/Layout/index.tsx index 5862eea..854a752 100644 --- a/components/Layout/index.tsx +++ b/components/Layout/index.tsx @@ -9,9 +9,9 @@ export default function Layout({ children }) { -
-
- +
+
+
{children}
diff --git a/lib/snapshot.ts b/lib/snapshot.ts index 4714aca..6d31b84 100644 --- a/lib/snapshot.ts +++ b/lib/snapshot.ts @@ -1,5 +1,5 @@ import rfdc from 'rfdc' -import type { Snapshotter } from './types' +import type { Snapshotter } from './snapshot.macro' const clone = rfdc() diff --git a/pages/patterns/fast-slow-pointers/linked-list-cycle.js b/pages/patterns/fast-slow-pointers/linked-list-cycle.js deleted file mode 100644 index 7a1090d..0000000 --- a/pages/patterns/fast-slow-pointers/linked-list-cycle.js +++ /dev/null @@ -1,72 +0,0 @@ -import React from 'react' - -import { LinkedList, LinkedListItem } from '~components/LinkedList' -import * as List from '~lib/linked-list' -import { makeAlgorithmPage } from '~lib/makeAlgorithmPage' - -function LinkedListCycle({ state, inputs }) { - const { fast, slow, done } = state - return ( - - {({ item }) => ( - - {item === fast && fast} - {item === slow && slow} - - {item.value} - - - )} - - ) -} - -// -- - -function parseArgs({ list, cycleTo }) { - return { list: List.fromArray(list, { cycle: cycleTo }), cycleTo } -} - -function serialize(key, val) { - if (key === 'list') { - return [key, List.serialize(val)] - } - return [key, JSON.stringify(val)] -} - -export default makeAlgorithmPage( - { - title: 'Linked List Cycle', - pattern: `Fast & Slow Pointers`, - algorithm: hasCycle, - inputs: { - list: [1, 2, 3, 4, 5, 6], - cycleTo: 3, - }, - parseArgs, - serialize, - }, - LinkedListCycle -) - -// -- - -function hasCycle({ record }, { list }) { - let fast = list - let slow = list - - while (fast && fast.next) { - record({ fast, slow }) - fast = fast.next.next - slow = slow.next - - if (fast === slow) { - record({ fast, slow }) - record({ done: true, result: true, fast, slow }) - return true - } - } - - record({ done: true, result: false }) - return false -} diff --git a/pages/patterns/two-pointers/closest-triplet.tsx b/pages/patterns/two-pointers/closest-triplet.tsx index ee35576..b852b3b 100644 --- a/pages/patterns/two-pointers/closest-triplet.tsx +++ b/pages/patterns/two-pointers/closest-triplet.tsx @@ -60,8 +60,17 @@ export default defineAlgorithm( ClosestTriples ) -function ClosestTriples({ state, inputs }) { - const { __done: done, i, head, tail, minTriple, minDiff, diff } = state +function ClosestTriples({ state }) { + const { + __done: done, + i, + head, + tail, + minTriple, + minDiff, + diff, + target, + } = state const active = !done && i === undefined const isActive = (index: number) => @@ -87,7 +96,7 @@ function ClosestTriples({ state, inputs }) {
- Target: {inputs.target} + Target: {target} Min diff: {minDiff} Current diff: {diff}
diff --git a/tailwind.config.js b/tailwind.config.js index dbe25c8..84f2b21 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -10,7 +10,7 @@ const theme2 = { } module.exports = { - purge: [], + purge: ['./pages/**/*.tsx'], theme: { fontFamily: { serif: ['PT Serif', 'serif'], From 222d05a51dcf94a19c29d2c6d6d817c14aa33b51 Mon Sep 17 00:00:00 2001 From: Narendra Syahrasyad Date: Tue, 5 Jan 2021 22:35:44 -0800 Subject: [PATCH 14/15] fix: syntax too modern --- lib/snapshot.macro.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/snapshot.macro.js b/lib/snapshot.macro.js index a1a2cd8..da02293 100644 --- a/lib/snapshot.macro.js +++ b/lib/snapshot.macro.js @@ -36,7 +36,7 @@ function snapshot({ references, babel: { types: t } }) { path.replaceWith( createSnapshot(t, [ ...scope, - { line: String(path.node.loc?.start.line - loc.start.line) }, + { line: String(path.node.loc.start.line - loc.start.line) }, ]) ) }, From ceb87515e632b9d53125e95c2e51c2bb23d5a09a Mon Sep 17 00:00:00 2001 From: Narendra Syahrasyad Date: Tue, 5 Jan 2021 22:49:28 -0800 Subject: [PATCH 15/15] fix: purge too many styles --- tailwind.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tailwind.config.js b/tailwind.config.js index 84f2b21..3294d32 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -10,7 +10,7 @@ const theme2 = { } module.exports = { - purge: ['./pages/**/*.tsx'], + purge: ['./pages/**/*.tsx', './components/**/*'], theme: { fontFamily: { serif: ['PT Serif', 'serif'],