compute: add notebook block component (#32290)

This commit is contained in:
Rijnard van Tonder 2022-03-09 17:38:35 -08:00 committed by GitHub
parent 154ca95e7c
commit f0ca58171f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 880 additions and 14 deletions

1
.gitignore vendored
View File

@ -117,6 +117,7 @@ sourcegraph-webapp-*.tgz
graphql-operations.ts
*.module.scss.d.ts
dll-bundle
elm-stuff
# Extensions
/client/extension-api/dist

View File

@ -181,6 +181,18 @@ const config = {
type: 'asset/source',
})
config.module.rules.push({
test: /\.elm$/,
exclude: /elm-stuff/,
use: {
loader: 'elm-webpack-loader',
options: {
cwd: path.resolve(ROOT_PATH, 'client/web/src/notebooks/blocks/compute/component'),
report: 'json',
},
},
})
// Disable `CaseSensitivePathsPlugin` by default to speed up development build.
// Similar discussion: https://github.com/vercel/next.js/issues/6927#issuecomment-480579191
remove(config.plugins, plugin => plugin instanceof CaseSensitivePathsPlugin)

View File

@ -0,0 +1,40 @@
import { storiesOf } from '@storybook/react'
import { noop } from 'lodash'
import React from 'react'
import { WebStory } from '../../../components/WebStory'
import { NotebookComputeBlock } from './NotebookComputeBlock'
const { add } = storiesOf('web/search/notebooks/blocks/compute/NotebookComputeBlock', module).addDecorator(story => (
<div className="p-3 container">{story()}</div>
))
const noopBlockCallbacks = {
onRunBlock: noop,
onBlockInputChange: noop,
onSelectBlock: noop,
onMoveBlockSelection: noop,
onDeleteBlock: noop,
onMoveBlock: noop,
onDuplicateBlock: noop,
}
add('default', () => (
<WebStory>
{props => (
<NotebookComputeBlock
type="compute"
{...props}
{...noopBlockCallbacks}
input=""
output=""
id="compute-block-1"
isSelected={true}
isReadOnly={false}
isOtherBlockSelected={false}
isMacPlatform={true}
/>
)}
</WebStory>
))

View File

@ -1,5 +1,6 @@
import classNames from 'classnames'
import React, { useRef } from 'react'
import ElmComponent from 'react-elm-components'
import { ThemeProps } from '@sourcegraph/shared/src/theme'
@ -10,12 +11,68 @@ import blockStyles from '../NotebookBlock.module.scss'
import { useBlockSelection } from '../useBlockSelection'
import { useBlockShortcuts } from '../useBlockShortcuts'
import { Elm } from './component/src/Main.elm'
import styles from './NotebookComputeBlock.module.scss'
interface ComputeBlockProps extends BlockProps, ComputeBlock, ThemeProps {
isMacPlatform: boolean
}
interface ElmEvent {
data: string
eventType?: string
id?: string
}
function setupPorts(ports: {
receiveEvent: { send: (event: ElmEvent) => void }
openStream: { subscribe: (callback: (args: string[]) => void) => void }
}): void {
const sources: { [key: string]: EventSource } = {}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function sendEventToElm(event: any): void {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
const elmEvent = { data: event.data, eventType: event.type || null, id: event.id || null }
ports.receiveEvent.send(elmEvent)
}
function newEventSource(address: string): EventSource {
sources[address] = new EventSource(address)
return sources[address]
}
function deleteAllEventSources(): void {
for (const [key] of Object.entries(sources)) {
deleteEventSource(key)
}
}
function deleteEventSource(address: string): void {
sources[address].close()
delete sources[address]
}
ports.openStream.subscribe((args: string[]) => {
deleteAllEventSources() // Close any open streams if we receive a request to open a new stream before seeing 'done'.
console.log(`stream: ${args[0]}`)
const address = args[0]
const eventSource = newEventSource(address)
eventSource.addEventListener('error', () => {
console.log('EventSource failed')
})
eventSource.addEventListener('results', sendEventToElm)
eventSource.addEventListener('alert', sendEventToElm)
eventSource.addEventListener('error', sendEventToElm)
eventSource.addEventListener('done', () => {
deleteEventSource(address)
// Note: 'done:true' is sent in progress too. But we want a 'done' for the entire stream in case we don't see it.
sendEventToElm({ type: 'done', data: '' })
})
})
}
export const NotebookComputeBlock: React.FunctionComponent<ComputeBlockProps> = ({
id,
input,
@ -78,7 +135,10 @@ export const NotebookComputeBlock: React.FunctionComponent<ComputeBlockProps> =
aria-label="Notebook compute block"
ref={blockElement}
>
<div className="elm" />
<div className="elm">
{/* eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */}
<ElmComponent src={Elm.Main} ports={setupPorts} flags={null} />
</div>
</div>
{blockMenu}
</div>

View File

@ -0,0 +1,34 @@
{
"type": "application",
"source-directories": ["src"],
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"NoRedInk/elm-json-decode-pipeline": "1.0.0",
"elm/browser": "1.0.2",
"elm/core": "1.0.5",
"elm/html": "1.0.0",
"elm/json": "1.1.3",
"elm/svg": "1.0.1",
"elm/time": "1.0.0",
"elm/url": "1.0.0",
"mdgriffith/elm-ui": "1.1.8",
"terezka/elm-charts": "3.0.0"
},
"indirect": {
"danhandrea/elm-time-extra": "1.1.0",
"debois/elm-dom": "1.3.0",
"elm/parser": "1.1.0",
"elm/virtual-dom": "1.0.2",
"justinmimbs/date": "3.2.1",
"justinmimbs/time-extra": "1.1.0",
"myrho/elm-round": "1.0.4",
"ryannhg/date-format": "2.3.0",
"terezka/intervals": "2.0.1"
}
},
"test-dependencies": {
"direct": {},
"indirect": {}
}
}

View File

@ -0,0 +1,639 @@
port module Main exposing (..)
import Browser
import Chart as C
import Chart.Attributes as CA
import Dict exposing (Dict)
import Element as E
import Element.Background as Background
import Element.Border as Border
import Element.Events
import Element.Font as F
import Element.Input as I
import Html exposing (Html, input, text)
import Html.Attributes exposing (..)
import Json.Decode as Decode exposing (Decoder, fail, field, maybe)
import Json.Decode.Pipeline
import Process
import Task
import Url.Builder
import Url.Parser exposing (..)
-- CONSTANTS
width : Int
width =
800
debounceQueryInputMillis : Float
debounceQueryInputMillis =
400
endpoint : String
endpoint =
"https://sourcegraph.test:3443/.api"
-- MAIN
main : Program () Model Msg
main =
Browser.element
{ init = init
, update = update
, view = view
, subscriptions = subscriptions
}
-- MODEL
type alias DataValue =
{ name : String
, value : Float
}
type alias Filter a =
{ a
| dataPoints : Int
, sortByCount : Bool
, reverse : Bool
, excludeStopWords : Bool
}
type alias Model =
{ query : String
, debounce : Int
, dataPoints : Int
, sortByCount : Bool
, reverse : Bool
, excludeStopWords : Bool
, selectedTab : Tab
, resultsMap : Dict String DataValue
-- Debug client only
, serverless : Bool
}
init : () -> ( Model, Cmd Msg )
init _ =
( { query = "repo:.* content:output((.|\\n)* -> $date) type:commit count:all"
, dataPoints = 30
, sortByCount = True
, reverse = False
, excludeStopWords = False
, selectedTab = Chart
, debounce = 0
, resultsMap = Dict.empty
, serverless = False
}
, Task.perform identity (Task.succeed RunCompute)
)
-- PORTS
type alias RawEvent =
{ data : String
, eventType : Maybe String
, id : Maybe String
}
port receiveEvent : (RawEvent -> msg) -> Sub msg
port openStream : ( String, Maybe String ) -> Cmd msg
-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions _ =
Sub.batch [ receiveEvent eventDecoder ]
eventDecoder : RawEvent -> Msg
eventDecoder event =
case event.eventType of
Just "results" ->
OnResults (resultEventDecoder event.data)
Just "done" ->
ResultStreamDone
_ ->
NoOp
resultEventDecoder : String -> List Result
resultEventDecoder input =
case Decode.decodeString (Decode.list resultDecoder) input of
Ok results ->
results
Err _ ->
[]
-- UPDATE
type Msg
= -- User inputs
OnQueryChanged String
| OnDebounce
| OnDataPoints String
| OnSortByCheckbox Bool
| OnReverseCheckbox Bool
| OnExcludeStopWordsCheckbox Bool
| OnTabSelected Tab
-- Data processing
| RunCompute
| OnResults (List Result)
| ResultStreamDone
| NoOp
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
OnQueryChanged newQuery ->
( { model | query = newQuery, debounce = model.debounce + 1 }
, Task.perform (\_ -> OnDebounce) (Process.sleep debounceQueryInputMillis)
)
OnDebounce ->
if model.debounce - 1 == 0 then
update RunCompute { model | debounce = model.debounce - 1 }
else
( { model | debounce = model.debounce - 1 }, Cmd.none )
OnSortByCheckbox sortByCount ->
( { model | sortByCount = sortByCount }, Cmd.none )
OnReverseCheckbox reverse ->
( { model | reverse = reverse }, Cmd.none )
OnExcludeStopWordsCheckbox excludeStopWords ->
( { model | excludeStopWords = excludeStopWords }, Cmd.none )
OnDataPoints i ->
let
newDataPoints =
case String.toInt i of
Just n ->
n
Nothing ->
0
in
( { model | dataPoints = newDataPoints }, Cmd.none )
OnTabSelected selectedTab ->
( { model | selectedTab = selectedTab }, Cmd.none )
RunCompute ->
if model.serverless then
( { model | resultsMap = exampleResultsMap }, Cmd.none )
else
( { model | resultsMap = Dict.empty }
, openStream ( endpoint ++ Url.Builder.absolute [ "compute", "stream" ] [ Url.Builder.string "q" model.query ], Nothing )
)
OnResults r ->
( { model | resultsMap = List.foldl updateResultsMap model.resultsMap (parseResults r) }
, Cmd.none
)
ResultStreamDone ->
( model, Cmd.none )
NoOp ->
( model, Cmd.none )
-- VIEW
table : List DataValue -> E.Element Msg
table data =
let
headerAttrs =
[ F.bold
, F.size 12
, F.color darkModeFontColor
, E.padding 5
, Border.widthEach { bottom = 1, top = 0, left = 0, right = 0 }
]
in
E.el [ E.padding 100, E.centerX ]
(E.table [ E.width E.fill ]
{ data = data
, columns =
[ { header = E.el headerAttrs (E.text " ")
, width = E.fillPortion 2
, view = \v -> E.el [ E.padding 5 ] (E.el [ E.width E.fill, E.padding 10, Border.rounded 5, Border.width 1 ] (E.text v.name))
}
, { header = E.el (headerAttrs ++ [ F.alignRight ]) (E.text "Count")
, width = E.fillPortion 1
, view =
\v ->
E.el
[ E.centerY
, F.size 12
, F.color darkModeFontColor
, F.alignRight
, E.padding 5
]
(E.text (String.fromFloat v.value))
}
]
}
)
histogram : List DataValue -> E.Element Msg
histogram data =
E.el
[ E.width E.fill
, E.height (E.fill |> E.minimum 400)
, E.centerX
, E.alignTop
, E.padding 30
]
(E.html
(C.chart
[ CA.height 300, CA.width (toFloat width) ]
[ C.bars
[ CA.spacing 0.0 ]
[ C.bar .value [ CA.color "#A112FF", CA.roundTop 0.2 ] ]
data
, C.binLabels .name [ CA.moveDown 25, CA.rotate 45, CA.alignRight ]
, C.barLabels [ CA.moveDown 12, CA.color "white", CA.fontSize 12 ]
]
)
)
dataView : List DataValue -> E.Element Msg
dataView data =
E.row []
[ E.el [ E.padding 10, E.alignLeft, E.width E.fill ]
(E.column [] (List.map (\d -> E.text d.name) data))
]
inputRow : Model -> E.Element Msg
inputRow model =
E.el [ E.centerX, E.width E.fill ]
(E.column [ E.width E.fill ]
[ I.text [ Background.color darkModeTextInputColor ]
{ onChange = OnQueryChanged
, placeholder = Nothing
, text = model.query
, label = I.labelHidden ""
}
, E.row [ E.paddingXY 0 10 ]
[ I.text [ E.width (E.fill |> E.maximum 65), F.center, Background.color darkModeTextInputColor ]
{ onChange = OnDataPoints
, placeholder = Nothing
, text =
case model.dataPoints of
0 ->
""
n ->
String.fromInt n
, label = I.labelHidden ""
}
, I.checkbox [ E.paddingXY 10 0 ]
{ onChange = OnSortByCheckbox
, icon = I.defaultCheckbox
, checked = model.sortByCount
, label = I.labelRight [] (E.text "sort by count")
}
, I.checkbox [ E.paddingXY 10 0 ]
{ onChange = OnReverseCheckbox
, icon = I.defaultCheckbox
, checked = model.reverse
, label = I.labelRight [] (E.text "reverse")
}
, I.checkbox [ E.paddingXY 10 0 ]
{ onChange = OnExcludeStopWordsCheckbox
, icon = I.defaultCheckbox
, checked = model.excludeStopWords
, label = I.labelRight [] (E.text "exclude stop words")
}
]
]
)
type Tab
= Chart
| Table
| Data
color =
{ skyBlue = E.rgb255 0x00 0xCB 0xEC
, vividViolet = E.rgb255 0xA1 0x12 0xFF
, vermillion = E.rgb255 0xFF 0x55 0x43
}
tab : Tab -> Tab -> E.Element Msg
tab thisTab selectedTab =
let
isSelected =
thisTab == selectedTab
padOffset =
if isSelected then
0
else
2
borderWidths =
if isSelected then
{ left = 1, top = 1, right = 1, bottom = 0 }
else
{ bottom = 1, top = 0, left = 0, right = 0 }
corners =
if isSelected then
{ topLeft = 6, topRight = 6, bottomLeft = 0, bottomRight = 0 }
else
{ topLeft = 0, topRight = 0, bottomLeft = 0, bottomRight = 0 }
tabColor =
case selectedTab of
Chart ->
color.vividViolet
Table ->
color.vermillion
Data ->
color.skyBlue
text =
case thisTab of
Chart ->
"Chart"
Table ->
"Table"
Data ->
"Data"
in
E.el
[ Border.widthEach borderWidths
, Border.roundEach corners
, Border.color tabColor
, Element.Events.onClick (OnTabSelected thisTab)
, E.htmlAttribute (Html.Attributes.style "cursor" "pointer")
, E.width E.fill
]
(E.el
[ E.centerX
, E.width E.fill
, E.centerY
, E.paddingEach { left = 30, right = 30, top = 10 + padOffset, bottom = 10 - padOffset }
]
(E.text text)
)
outputRow : Tab -> E.Element Msg
outputRow selectedTab =
E.row [ E.centerX, E.width E.fill ]
[ tab Chart selectedTab
, tab Table selectedTab
, tab Data selectedTab
]
view : Model -> Html Msg
view model =
E.layout
[ E.width E.fill
, F.family [ F.typeface "Fira Code" ]
, F.size 12
, F.color darkModeFontColor
, Background.color darkModeBackgroundColor
]
(E.row [ E.centerX, E.width (E.fill |> E.maximum width) ]
[ E.column [ E.centerX, E.width (E.fill |> E.maximum width), E.paddingXY 20 20 ]
[ inputRow model
, outputRow model.selectedTab
, let
data =
Dict.toList model.resultsMap
|> List.map Tuple.second
|> filterData model
in
case model.selectedTab of
Chart ->
histogram data
Table ->
table data
Data ->
dataView data
]
]
)
-- DATA LOGIC
parseResults : List Result -> List String
parseResults l =
List.filterMap
(\r ->
case r of
Output v ->
String.split "\n" v.value
|> List.filter (not << String.isEmpty)
|> Just
ReplaceInPlace v ->
Just [ v.value ]
)
l
|> List.concat
updateResultsMap : String -> Dict String DataValue -> Dict String DataValue
updateResultsMap textResult =
Dict.update
textResult
(\v ->
case v of
Nothing ->
Just { name = textResult, value = 1 }
Just existing ->
Just { existing | value = existing.value + 1 }
)
filterData : Filter a -> List DataValue -> List DataValue
filterData { dataPoints, sortByCount, reverse, excludeStopWords } data =
let
pipeSort =
if sortByCount then
List.sortWith
(\l r ->
if l.value < r.value then
GT
else if l.value > r.value then
LT
else
EQ
)
else
identity
in
let
pipeReverse =
if reverse then
List.reverse
else
identity
in
let
pipeStopWords =
if excludeStopWords then
List.filter (\{ name } -> not (Dict.member (String.toLower name) Dict.empty))
else
identity
in
data
|> pipeStopWords
|> pipeSort
|> pipeReverse
|> List.take dataPoints
|> pipeReverse
-- STREAMING RESULT TYPES
type Result
= Output TextResult
| ReplaceInPlace TextResult
type alias TextResult =
{ value : String
, repository : Maybe String
, commit : Maybe String
, path : Maybe String
}
-- DECODERS
resultDecoder : Decoder Result
resultDecoder =
field "kind" Decode.string
|> Decode.andThen
(\t ->
case t of
"replace-in-place" ->
textResultDecoder
|> Decode.map ReplaceInPlace
"output" ->
textResultDecoder
|> Decode.map Output
_ ->
fail ("Unrecognized type " ++ t)
)
textResultDecoder : Decoder TextResult
textResultDecoder =
Decode.succeed TextResult
|> Json.Decode.Pipeline.required "value" Decode.string
|> Json.Decode.Pipeline.optional "repository" (maybe Decode.string) Nothing
|> Json.Decode.Pipeline.optional "commit" (maybe Decode.string) Nothing
|> Json.Decode.Pipeline.optional "path" (maybe Decode.string) Nothing
-- STYLING
darkModeBackgroundColor : E.Color
darkModeBackgroundColor =
E.rgb255 0x18 0x1B 0x26
darkModeFontColor : E.Color
darkModeFontColor =
E.rgb255 0xFF 0xFF 0xFF
darkModeTextInputColor : E.Color
darkModeTextInputColor =
E.rgb255 0x1D 0x22 0x2F
-- DEBUG DATA
exampleResultsMap : Dict String DataValue
exampleResultsMap =
[ { name = "Errorf"
, value = 10.0
}
, { name = "Func\nmulti\nline"
, value = 5.0
}
, { name = "Qux"
, value = 2.0
}
]
|> List.map (\d -> ( d.name, d ))
|> Dict.fromList

View File

@ -0,0 +1,5 @@
declare module '*.elm' {
export const Elm: any
}
declare module 'react-elm-components'

View File

@ -231,6 +231,17 @@ const config = {
},
{ test: /\.ya?ml$/, type: 'asset/source' },
{ test: /\.(png|woff2)$/, type: 'asset/resource' },
{
test: /\.elm$/,
exclude: /elm-stuff/,
use: {
loader: 'elm-webpack-loader',
options: {
cwd: path.resolve(ROOT_PATH, 'client/web/src/notebooks/blocks/compute/component'),
report: 'json',
},
},
},
],
},
}

View File

@ -242,6 +242,8 @@
"cross-env": "^7.0.2",
"css-loader": "^5.2.6",
"css-minimizer-webpack-plugin": "^3.0.2",
"elm": "^0.19.1-3",
"elm-webpack-loader": "^8.0.0",
"esbuild": "^0.14.2",
"eslint": "^7.17.0",
"eslint-formatter-lsif": "^1.0.3",
@ -404,6 +406,7 @@
"react-circular-progressbar": "^2.0.3",
"react-dom": "^16.14.0",
"react-dom-confetti": "^0.1.4",
"react-elm-components": "^1.1.0",
"react-focus-lock": "^2.4.1",
"react-grid-layout": "1.3.0",
"react-router": "^5.2.0",

View File

@ -9088,6 +9088,14 @@ create-error-class@^3.0.0:
dependencies:
capture-stack-trace "^1.0.0"
create-react-class@^15.5.3:
version "15.7.0"
resolved "https://registry.npmjs.org/create-react-class/-/create-react-class-15.7.0.tgz#7499d7ca2e69bb51d13faf59bd04f0c65a1d6c1e"
integrity sha512-QZv4sFWG9S5RUvkTYWbflxeZX+JG7Cz0Tn33rQBJ+WFQTqTfUTjMjiv9tnfXazjsO5r0KhPs+AqCjyrQX6h2ng==
dependencies:
loose-envify "^1.3.1"
object-assign "^4.1.1"
create-react-context@0.3.0, create-react-context@^0.3.0:
version "0.3.0"
resolved "https://registry.npmjs.org/create-react-context/-/create-react-context-0.3.0.tgz#546dede9dc422def0d3fc2fe03afe0bc0f4f7d8c"
@ -9122,6 +9130,17 @@ cross-fetch@^3.0.4, cross-fetch@^3.0.6, cross-fetch@^3.1.4:
dependencies:
node-fetch "2.6.1"
cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5:
version "6.0.5"
resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==
dependencies:
nice-try "^1.0.4"
path-key "^2.0.1"
semver "^5.5.0"
shebang-command "^1.2.0"
which "^1.2.9"
cross-spawn@7.0.3, cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
version "7.0.3"
resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
@ -9140,17 +9159,6 @@ cross-spawn@^5.0.1:
shebang-command "^1.2.0"
which "^1.2.9"
cross-spawn@^6.0.0, cross-spawn@^6.0.5:
version "6.0.5"
resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==
dependencies:
nice-try "^1.0.4"
path-key "^2.0.1"
semver "^5.5.0"
shebang-command "^1.2.0"
which "^1.2.9"
crypt@0.0.2:
version "0.0.2"
resolved "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
@ -10826,6 +10834,21 @@ element-resize-detector@^1.2.2:
dependencies:
batch-processor "1.0.0"
elm-webpack-loader@^8.0.0:
version "8.0.0"
resolved "https://registry.npmjs.org/elm-webpack-loader/-/elm-webpack-loader-8.0.0.tgz#73a6be9315fceff27fdc62d46c89520bdffad20a"
integrity sha512-R49j9GOGbZ+PjrktAzYzjUgf6w+RHOIUNWLfOVdc7OoGG52SreEk63l/bLJV31HwLE8PU6KANEnzeZ3238fAUg==
dependencies:
loader-utils "^2.0.0"
node-elm-compiler "^5.0.0"
elm@^0.19.1-3:
version "0.19.1-5"
resolved "https://registry.npmjs.org/elm/-/elm-0.19.1-5.tgz#61f18437222972e20f316f9b2d2c76a781a9991b"
integrity sha512-dyBoPvFiNLvxOStQJdyq28gZEjS/enZXdZ5yyCtNtDEMbFJJVQq4pYNRKvhrKKdlxNot6d96iQe1uczoqO5yvA==
dependencies:
request "^2.88.0"
emittery@^0.8.1:
version "0.8.1"
resolved "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz#bb23cc86d03b30aa75a7f734819dee2e1ba70860"
@ -12217,6 +12240,14 @@ find-cache-dir@^3.2.0, find-cache-dir@^3.3.1:
make-dir "^3.0.2"
pkg-dir "^4.1.0"
find-elm-dependencies@^2.0.4:
version "2.0.4"
resolved "https://registry.npmjs.org/find-elm-dependencies/-/find-elm-dependencies-2.0.4.tgz#0a327fc8c0c0297b54115efbf0a9d6de474cfc89"
integrity sha512-x/4w4fVmlD2X4PD9oQ+yh9EyaQef6OtEULdMGBTuWx0Nkppvo2Z/bAiQioW2n+GdRYKypME2b9OmYTw5tw5qDg==
dependencies:
firstline "^1.2.0"
lodash "^4.17.19"
find-root@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4"
@ -12313,6 +12344,11 @@ first-chunk-stream@3.0.0, first-chunk-stream@^3.0.0:
resolved "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-3.0.0.tgz#06972a66263505ed82b2c4db93c1b5e078a6576a"
integrity sha512-LNRvR4hr/S8cXXkIY5pTgVP7L3tq6LlYWcg9nWBuW7o1NMxKZo6oOVa/6GIekMGI0Iw7uC+HWimMe9u/VAeKqw==
firstline@^1.2.0:
version "1.3.1"
resolved "https://registry.npmjs.org/firstline/-/firstline-1.3.1.tgz#59e84af0fd858fbc6dac0a0ff97fd22a47e58084"
integrity sha512-ycwgqtoxujz1dm0kjkBFOPQMESxB9uKc/PlD951dQDIG+tBXRpYZC2UmJb0gDxopQ1ZX6oyRQN3goRczYu7Deg==
flagged-respawn@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.0.tgz#4e79ae9b2eb38bf86b3bb56bf3e0a56aa5fcabd7"
@ -17567,6 +17603,16 @@ node-dir@^0.1.10:
dependencies:
minimatch "^3.0.2"
node-elm-compiler@^5.0.0:
version "5.0.6"
resolved "https://registry.npmjs.org/node-elm-compiler/-/node-elm-compiler-5.0.6.tgz#d4a6e6c9d9a26dba4211ccd2aeae7d5e34057f0c"
integrity sha512-DWTRQR8b54rvschcZRREdsz7K84lnS8A6YJu8du3QLQ8f204SJbyTaA6NzYYbfUG97OTRKRv/0KZl82cTfpLhA==
dependencies:
cross-spawn "6.0.5"
find-elm-dependencies "^2.0.4"
lodash "^4.17.19"
temp "^0.9.0"
node-fetch@2.6.1:
version "2.6.1"
resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
@ -19824,6 +19870,13 @@ react-draggable@^4.0.0, react-draggable@^4.0.3, react-draggable@^4.4.3:
classnames "^2.2.5"
prop-types "^15.6.0"
react-elm-components@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/react-elm-components/-/react-elm-components-1.1.0.tgz#e36dbd4d80feee9b32c009482542d43b35be0b36"
integrity sha512-L1JUZTfobgHGhNtrWI5MtSHEs2eUux4pupZj47rF6r+BIQ7ec8Gu2mGenl1SgOGvUChyF3kIQn/ti1+d/4X24w==
dependencies:
create-react-class "^15.5.3"
react-error-overlay@^6.0.9:
version "6.0.9"
resolved "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a"
@ -20676,7 +20729,7 @@ replaceall@^0.1.6:
resolved "https://registry.npmjs.org/replaceall/-/replaceall-0.1.6.tgz#81d81ac7aeb72d7f5c4942adf2697a3220688d8e"
integrity sha1-gdgax663LX9cSUKt8ml6MiBojY4=
request@2.88.2, "request@>=2.76.0 <3.0.0", request@^2.83.0, request@^2.88.2, request@~2.88.0:
request@2.88.2, "request@>=2.76.0 <3.0.0", request@^2.83.0, request@^2.88.0, request@^2.88.2, request@~2.88.0:
version "2.88.2"
resolved "https://registry.npmjs.org/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
@ -20891,7 +20944,7 @@ right-pad@^1.0.1:
resolved "https://registry.npmjs.org/right-pad/-/right-pad-1.0.1.tgz#8ca08c2cbb5b55e74dafa96bf7fd1a27d568c8d0"
integrity sha1-jKCMLLtbVedNr6lr9/0aJ9VoyNA=
rimraf@2.6.3, rimraf@^2.2.8, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3:
rimraf@2.6.3, rimraf@^2.2.8, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@~2.6.2:
version "2.6.3"
resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==
@ -22549,6 +22602,14 @@ telejson@^5.3.2:
lodash "^4.17.21"
memoizerific "^1.11.3"
temp@^0.9.0:
version "0.9.4"
resolved "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz#cd20a8580cb63635d0e4e9d4bd989d44286e7620"
integrity sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==
dependencies:
mkdirp "^0.5.1"
rimraf "~2.6.2"
term-size@^1.2.0:
version "1.2.0"
resolved "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69"